fuzzyworld

explodorama / files / src / sim / rng.ts

rng.ts

Blame: the commit that last changed each line · file · history

5551eb37 2026-10-20 Pavel Safronov1/**
2 * Seeded randomness. Every random choice in the game comes from a stream derived from the run
3 * seed by purpose and level (PLAN.md §12), so an extra bomb in one place never changes what
4 * happens somewhere else, and the same seed always gives the same levels.
5 */
6
7/** FNV-1a over the parts joined by `:`. A 32-bit unsigned integer. */
8export function hashSeed(...parts: (string | number)[]): number {
9 let h = 0x811c9dc5
10 for (const ch of parts.join(':')) {
11 h ^= ch.codePointAt(0)!
12 h = Math.imul(h, 0x01000193)
13 }
14 return h >>> 0
15}
16
17/**
18 * mulberry32. Its whole state is one 32-bit number, kept in `state`, so a stream can be stored
19 * in the game state and resumed exactly.
20 */
21export class Rng {
22 constructor(public state: number) {}
23
24 /** A float in [0, 1). */
25 next(): number {
26 this.state = (this.state + 0x6d2b79f5) >>> 0
27 let t = this.state
28 t = Math.imul(t ^ (t >>> 15), t | 1)
29 t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
30 return ((t ^ (t >>> 14)) >>> 0) / 4294967296
31 }
32
33 /** An integer in [0, n). */
34 int(n: number): number {
35 return Math.floor(this.next() * n)
36 }
37}
38
39export function stream(seed: number, purpose: string, level: number): Rng {
40 return new Rng(hashSeed(seed, purpose, level))
41}