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>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user