/** * Pseudo random number generator * using the xorshift32 method. */ export class Xorshift32 { /** * State holds a single uint32. * It's useful for staying within modulo 2**32. * * @type {Uint32Array} */ #state; get state() { return this.#state; } /** @param {number} seed */ constructor(seed) { if (seed === undefined) { const maxInt32 = 2 ** 32; seed = Math.floor(Math.random() * (maxInt32 - 1)) + 1; } seed = seed | 0; this.#state = Uint32Array.of(seed); } /** @protected Shuffle the internal state. */ shuffle() { this.#state[0] ^= this.#state[0] << 13; this.#state[0] ^= this.#state[0] >>> 17; this.#state[0] ^= this.#state[0] << 5; // We could also do something like this: // x ^= x << 13; // x ^= x >>> 17; // x ^= x << 5; // return x; // And even though bitwise operations coerce numbers // into int32 (except >>> which converts into uint32). // But using Uint32Array ensures the number stays // uint32 all the way through, thus avoiding the pitfalls // of potentially dipping into negative number territory } /** * Get a random number and shuffle the internal state. * @returns {number} a pseudo-random positive integer. */ next() { this.shuffle(); return this.#state[0]; } /** @param {number} x @returns {number} a positive integer lower than x */ lowerThan(x) { return this.next() % x; } /** @param {number} x @returns {number} a positive integer lower than or equal to x */ lowerThanOrEqual(x) { return this.next() % (x + 1); } /** * @template T * @param {T[]} arr - The array to pick from. * @returns {T} One element from the array. * @return {} */ randomElement(arr) { if (arr instanceof Set) { arr = [...arr]; } const idx = this.lowerThan(arr.length); return arr[idx]; } /** * @param {...} ... pick random function argument * @returns {} */ oneOf(...args) { const idx = this.lowerThan(args.length); return args[idx]; } /** * @param {number} lowerThanOrEqual a positive integer * @param {number} greaterThanOrEqual a positive integer greater than lowerThanOrEqual * @returns {number} a pseudo-random integer */ within(greaterThanOrEqual, lowerThanOrEqual) { const range = lowerThanOrEqual - greaterThanOrEqual; const num = this.lowerThanOrEqual(range); return num + greaterThanOrEqual; } }