yummy.cricket/rainbow-quox/script/rand.ts

123 lines
3.6 KiB
TypeScript

// https://stackoverflow.com/questions/521295#47593316
export type State = [number, number, number, number];
export interface Randy {
state: State;
int(): number; // whole int32 range
int(x: number): number; // [0, x)
int(x: number, y: number): number; // [x, y)
float(): number; // [0, 1)
float(x: number): number; // [0, x)
float(x: number, y: number): number; // [x, y)
choice<A>(array: A[]): A;
boolean(): boolean;
}
export function fromHex(s: string): State {
const a = s.substring(0, 8);
const b = s.substring(8, 16);
const c = s.substring(16, 24);
const d = s.substring(24, 32);
return [h(a), h(b), h(c), h(d)];
function h(x: string) { return parseInt(x, 16); }
}
export function toHex([a, b, c, d]: State): string {
return `${h(a)}${h(b)}${h(c)}${h(d)}`;
function h(x: number) { return x.toString(16); }
}
const UINT_MAX = 4294967296;
export class Rand implements Randy {
#a: number; #b: number; #c: number; #d: number;
constructor();
constructor([a, b, c, d]: State);
constructor(str: string);
constructor(st?: State | string) {
const [a, b, c, d] =
st === '' ? s4() :
typeof st === 'string' ? Rand.stateFrom(st) :
st ?? s4();
this.#a = a; this.#b = b; this.#c = c; this.#d = d;
for (let i = 0; i < 20; ++i) this.#next();
function s() { return (Math.random() * 2**32) >>> 0; }
function s4() { return [s(), s(), s(), s()] as const; }
}
get state(): State { return [this.#a, this.#b, this.#c, this.#d]; }
get stateString() { return toHex(this.state); }
static stateFrom(str: string): State {
let h1 = 1779033703, h2 = 3144134277,
h3 = 1013904242, h4 = 2773480762;
for (let i = 0, k; i < str.length; i++) {
k = str.charCodeAt(i);
h1 = h2 ^ Math.imul(h1 ^ k, 597399067);
h2 = h3 ^ Math.imul(h2 ^ k, 2869860233);
h3 = h4 ^ Math.imul(h3 ^ k, 951274213);
h4 = h1 ^ Math.imul(h4 ^ k, 2716044179);
}
h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067);
h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233);
h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213);
h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179);
h1 ^= (h2 ^ h3 ^ h4); h2 ^= h1; h3 ^= h1; h4 ^= h1;
return [h1>>>0, h2>>>0, h3>>>0, h4>>>0];
}
int(): number; // whole int32 range
int(to: number): number; // [0, x)
int(from: number, to: number): number; // [x, y)
int(x?: number, y?: number): number {
if (x === undefined) return this.#next();
return Math.floor(y === undefined ? this.float(x) : this.float(x, y));
}
float(): number; // [0, 1)
float(x: number): number; // [0, x)
float(x: number, y: number): number; // [x, y)
float(x?: number, y?: number): number {
const in01 = this.#next() / UINT_MAX;
if (x === undefined) return in01;
const [lo, hi] = y === undefined? [0, x] : [x, y];
return lo + in01 * (hi - lo);
}
choice<A>(array: A[]): A {
return array[this.int(array.length)]!;
}
boolean(): boolean { return this.float() > 0.5; }
alphaNum(len?: number): string {
let res = "";
// [todo] is there a better way to make a string in js
if (len === undefined || len <= 0) len = 16; // idk
for (let i = 0; i < len; ++i)
res += this.choice(Array.from("abcdefghijklmnopqrstuvwxyz0123456789"));
return res;
}
#next(): number {
this.#a |= 0; this.#b |= 0; this.#c |= 0; this.#d |= 0;
const t = (this.#a + this.#b | 0) + this.#d | 0;
this.#d = this.#d + 1 | 0;
this.#a = this.#b ^ this.#b >>> 9;
this.#b = this.#c + (this.#c << 3) | 0;
this.#c = (this.#c << 21 | this.#c >>> 11);
this.#c = this.#c + t | 0;
return (t >>> 0);
}
}