runarberg / random-go-stone-placements

Generate a random starting position for your go games
https://runarberg.github.io/random-go-stone-placements
MIT License
1 stars 0 forks source link

Accept text as seed #13

Open sabu36 opened 4 years ago

sabu36 commented 4 years ago

Something like:

[checkbox] Generate from: [sample-text]

runarberg commented 4 years ago

I think loosing the checkbox and marking the field as optional would be sufficient (and easier):

Random seed (optional) ╭─────────────────────────────────╮
╰─────────────────────────────────╯

runarberg commented 4 years ago

This will likely require own implementation of the pseudo random generator. Here is a Stack Overflow answer with a few possibilities

sabu36 commented 4 years ago

Yes, making it optional makes sense.

The text can be used as a memorable title when creating a game.

From the Stack Overflow answer: "A good hash function will generate very different results even when two strings are similar."

This might not be a good property flavor-wise for our purpose. It would be nice if "flying panda" and "surfing panda" had some resemblance.

sabu36 commented 4 years ago

One idea:

Divide the stones into groups and use one of the words for each group.

sabu36 commented 4 years ago
class Rng {
  rngs  // rng for each word
  idx

  generate() {
    const idxCurrent = this.idx;
    this.idx = (this.idx + 1) % this.rngs.length;
    return this.rngs[idxCurrent].generate();
  }
}

const rng = new Rng(words);

How can we set up so that we just replace Math.random() with rng.generate() (or similar name) in js/generators?

Also, the class name Rng is not very descriptive. Do you have any suggestion?

As for the actual RNG, the one by David Bau, which is mentioned in the same Stack Overflow thread, already takes a string. What do you think?

runarberg commented 4 years ago

Rng is a pretty common name for Random Number Generator, but I think Random might be better.

I see two possibilities:

(a) is less coding, but (b) makes writing unit tests much easier. I’m partial to (b) as I like pure functions (among other; functions that always return the same value given the same input) and I like well tested code.

runarberg commented 4 years ago

As for the library, I don’t like how it extends the global Math object. It is generally considered bad JavaScript to extend native objects. I would also prefer to remain buildless[1]. That would mean pulling a library that would work equally in the browser and in node. And using it would be as simple as:

import random from "https://cdn.jsdelivr.net/npm/some-rng-lib";

  1. In JavaScript of the past 10 years, we have had to build or compile our code for it to work in the browser. This is not needed any more since node.js 13 started supporting native modules. This is the reason I provide the static types with js comments /** @type SomeType */ as opposed to the more commonly used .ts files.
runarberg commented 4 years ago

Looking for a good library I think d3-random might be a good candidate.

sabu36 commented 4 years ago

Thank you very much for the detailed explanations.

I appreciate your invite to be a collaborator but I feel safer staying the same as I still have shaky understanding of what is going on.


Notes on pure function for self: * `const newItem = lodash.cloneDeep(item);` * `const newItem = Object.assign({}, item, {price});` * Non-destructive array methods: `concat`, `filter`, `map`, `reduce`, `slice` * With spread syntax: `const signInUser = user => ({...user, isSignedIn: true});` * Identity comparison (without looking inside): `===`
runarberg commented 4 years ago

I actually ended up making a build step and am now serving from the gh-pages branch (#15). This way it is easier to add libraries and we are serving a little more optimized code for the users. But the dev build is still (almost) uncompleted. The only thing we change are bare imports (e.g. import { randomUniform } from d3-random;) will be altered to resolved location (import { randomUniform } from '/node_modules/d3-random/index.js;). In the future we can look forward to import maps so we won’t even need that.

I will probably go ahead and implement the seed feature this evening (my time). After that implementing and testing new generators would be even easier. I’m already thinking about normal-handicap. Where stones probability of being drawn at an intersection is normally distributed with the mean on the next traditional handicap star point and a user defined standard deviation.

sabu36 commented 4 years ago

I'll work on the following.

In adaptive-weights.js, domino-shuffle.js, (part of) utils.js:

sabu36 commented 4 years ago

@runarberg You may have already realized this but it dawned on me that, since we may use different distributions at different parts, we need to pass around seeders (?) instead of rngs themselves.

Also, d3.randomLcg seems to only take numbers, so it looks like we need a way to convert string into number.

If we have wordToNum() as a converter, maybe something like:

class Seeder {
  constructor(words) {
    this.lcgs = words.map((wd) => d3.randomLcg(wordToNum(wd)));
    this.idx = 0;
  }
  next() {
    const idxCurrent = this.idx;
    this.idx = (this.idx + 1) % this.lcgs.length;
    return this.lcgs[idxCurrent];
  }
}

const words = ["sea", "sjór", "海"];
config.seeder = new Seeder(words);
sabu36 commented 4 years ago

I don't know if this is practical but one idea is to hash (?) each word as an image in some reference font to make hashing invariant to alphabetic/character ordering, perhaps using grid larger than pixel to reduce computation and emphasize the general directions of where strokes are going.

sabu36 commented 4 years ago

(Scratch my last idea.)

In the explanation for d3.randomLcg() in d3-random:

A seed can be specified as a real number in the interval [0,1) or as any integer. In the latter case, only the lower 32 bits are considered.

For converting string to number, one example I found is murmurhash:

take a JavaScript string (and a seed), and quickly create a non-cryptographic 32-bit hash

runarberg commented 3 years ago

Yeah I like that. Last week I conjured something up that hashes a string, slices the last bits and divides by the maximum of that giving a number between 0 and 1:

Number.parseInt(hash(word).toString(16).slice(-8), 16) / 0xffff_ffff;

But if D3 can do that internally and we can use a library for the hashing, thats even better :wink:

sabu36 commented 3 years ago

It's your call but I also like your solution.

runarberg commented 3 years ago

@sabu36 I like your idea of using murmurhash with D3-random. I’ve re-assigned this to you if you want to give it a stab.

I’ve already started, but didn’t get really far (I’ve had a busy couple of weeks). If you like to look at my work (might be useful for styling) see commit fa7335677.

sabu36 commented 3 years ago

Starting from where you left off--I couldn't figure out how to import murmurhash so used your solution--uniform.js now accepts seed.

So it seems to be working. d13dac5

runarberg commented 3 years ago

You can import modules installed with npm install --save <module> by using bare names:

import d3Random from "d3-random";
import hash from "murmurhash";

This is non-standard as of yet. But I’ve configured a dev-server and the bundler to under stand this. Basically it will map the module using the dependency field in package.json and then locate it inside the node_modules directory.

sabu36 commented 3 years ago

@runarberg

I still can't figure out how to import d3-random and murmurhash without errors. (For murmurhash, if it's difficult to fix, it seems we can just use your toHash() + toFraction().)

  1. They seem to not have default export:

    Uncaught SyntaxError: import not found: default

  2. When I tried importing individually:

    import { randomLcg, randomUniform } from "d3-random"; ^^^^^^^^^ SyntaxError: The requested module 'd3-random' is expected to be of type CommonJS, which does not support named exports. CommonJS modules can be imported by importing the default export.

  3. When I tried require('murmurhash'), in browser's Web Console:

    ReferenceError: require is not defined

I don't know if this is relevant but this answer lists some options to make require() work.

  1. Also, I wonder if either of the suggestions in 2nd error message below should be done:

    Could not find a declaration file for module 'd3-random'. '/home/runner/work/random-go-stone-placements/random-go-stone-placements/node_modules/d3-random/dist/d3-random.js' implicitly has an 'any' type.

Try npm install @types/d3-random if it exists or add a new declaration (.d.ts) file containing declare module 'd3-random';

sabu36 commented 3 years ago

I realized that, for poems, splitting with a space only works for Japanese. It might be textarea we want.

One can look for an opponent with the first part of a poem, letting the challenger complete it, renku-style.

headlights sweep the wall
i pour another
one
tomorrow
is another day