Buildings (modern/fantasy, multi-floor, footprint shapes), caves, outdoor biomes, and town/village + city-block settlements. Auto-places Foundry walls, doors, windows, lighting; furniture as locked tiles; live preview. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
89 lines
2.3 KiB
JavaScript
89 lines
2.3 KiB
JavaScript
/**
|
||
* 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;
|
||
}
|
||
}
|