/** * Seeded pseudo-random number generation. * * Pure, dependency-free, and Foundry-agnostic so it can be unit-tested in Node. * Same seed string always produces the same map. */ /** Hash an arbitrary string into a 32-bit unsigned integer seed (xfnv1a). */ export function hashSeed(input) { const str = String(input ?? ""); let h = 2166136261 >>> 0; for (let i = 0; i < str.length; i += 1) { h ^= str.charCodeAt(i); h = Math.imul(h, 16777619); } // Final avalanche so similar strings diverge. h += h << 13; h ^= h >>> 7; h += h << 3; h ^= h >>> 17; h += h << 5; return h >>> 0; } /** mulberry32 — small, fast, good-enough PRNG. Returns a function -> float in [0,1). */ export function mulberry32(seed) { let a = seed >>> 0; return function next() { a |= 0; a = (a + 0x6d2b79f5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } /** * Convenience wrapper around a raw float generator with the helpers a * generator actually needs. */ export class Rng { constructor(seed) { this._next = mulberry32(typeof seed === "number" ? seed : hashSeed(seed)); } /** Float in [0, 1). */ float() { return this._next(); } /** Float in [min, max). */ range(min, max) { return min + this._next() * (max - min); } /** Integer in [min, max] inclusive. */ int(min, max) { return Math.floor(min + this._next() * (max - min + 1)); } /** True with probability p (0..1). */ chance(p) { return this._next() < p; } /** Pick a uniformly random element from a non-empty array. */ pick(arr) { return arr[Math.floor(this._next() * arr.length)]; } /** Pick by weight. items: [{weight, ...}] -> returns one item. */ weighted(items) { const total = items.reduce((sum, it) => sum + (it.weight ?? 1), 0); let roll = this._next() * total; for (const it of items) { roll -= it.weight ?? 1; if (roll < 0) return it; } return items[items.length - 1]; } /** In-place Fisher–Yates shuffle, returns the same array. */ shuffle(arr) { for (let i = arr.length - 1; i > 0; i -= 1) { const j = Math.floor(this._next() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; } return arr; } }