Files
slaguru666andClaude Opus 4.8 100e514c4d Mapwright v0.5.0 — procedural battle map generator for Foundry VTT
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>
2026-06-17 15:05:25 +01:00

89 lines
2.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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;
}
}