51 lines
1.4 KiB
TypeScript
51 lines
1.4 KiB
TypeScript
/** A 32-bit unsigned integer seed. */
|
|
export type Seed = number;
|
|
|
|
export interface Rng {
|
|
/** Float in [0, 1). */
|
|
next(): number;
|
|
/** Integer in [min, max], inclusive. */
|
|
int(min: number, max: number): number;
|
|
/** A uniformly chosen element. Throws on an empty list. */
|
|
pick<T>(list: readonly T[]): T;
|
|
}
|
|
|
|
/** Seeded PRNG (mulberry32). The same seed always yields the same sequence. */
|
|
export function createRng(seed: Seed): Rng {
|
|
let state = seed >>> 0;
|
|
|
|
const next = () => {
|
|
state = (state + 0x6d2b79f5) >>> 0;
|
|
let t = state;
|
|
t = Math.imul(t ^ (t >>> 15), t | 1);
|
|
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
|
|
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
};
|
|
|
|
return {
|
|
next,
|
|
int(min, max) {
|
|
return min + Math.floor(next() * (max - min + 1));
|
|
},
|
|
pick(list) {
|
|
if (list.length === 0) throw new Error("Cannot pick from an empty list");
|
|
return list[Math.floor(next() * list.length)]!;
|
|
},
|
|
};
|
|
}
|
|
|
|
/** Derives an independent child seed from a parent seed and a label (FNV-1a, then a final mix). */
|
|
export function deriveSeed(parent: Seed, label: string): Seed {
|
|
let hash = 0x811c9dc5 ^ (parent >>> 0);
|
|
for (let i = 0; i < label.length; i++) {
|
|
hash ^= label.charCodeAt(i);
|
|
hash = Math.imul(hash, 0x01000193);
|
|
}
|
|
hash ^= hash >>> 16;
|
|
hash = Math.imul(hash, 0x85ebca6b);
|
|
hash ^= hash >>> 13;
|
|
hash = Math.imul(hash, 0xc2b2ae35);
|
|
hash ^= hash >>> 16;
|
|
return hash >>> 0;
|
|
}
|