Pure deterministic game core in @wizwar/engine: seeded RNG (mulberry32, state in GameState so seed+commands replays identically), sector assembly with rotation, junction merging, and wraparound warps (configurable pairings; the 2p diagram crosses its side openings), movement (3 + one number card), geometric line of sight, deck building from the verified card data (asserts 125/200 totals), and the command-to-event reducer: setup with TRAP! redraw and die-roll first player, punching (no combat round 1, no self-attack, once per turn), damage/death with killer-takes-cards and forced discard, treasure stealing with both victory conditions, pick-up-ends-turn, and end-of-turn draw. Events carry full spatial detail for future replay rendering; private card knowledge rides on visibleTo events with a redaction helper. 21 tests passing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
46 lines
1.5 KiB
TypeScript
46 lines
1.5 KiB
TypeScript
// Deterministic RNG (mulberry32). The generator state lives inside GameState so
|
|
// that replaying the same commands from the same seed reproduces the game
|
|
// exactly — the foundation for async play, spectating, and replays.
|
|
|
|
export interface RngState {
|
|
/** Current 32-bit mulberry32 state; advances by one per value drawn. */
|
|
readonly a: number;
|
|
}
|
|
|
|
export function createRng(seed: number): RngState {
|
|
return { a: seed >>> 0 };
|
|
}
|
|
|
|
/** Draw the next float in [0, 1). Returns the value and the advanced state. */
|
|
export function nextFloat(state: RngState): [number, RngState] {
|
|
const a = (state.a + 0x6d2b79f5) | 0;
|
|
let t = Math.imul(a ^ (a >>> 15), 1 | a);
|
|
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
const value = ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
return [value, { a: a >>> 0 }];
|
|
}
|
|
|
|
/** Draw an integer in [0, n). */
|
|
export function nextInt(state: RngState, n: number): [number, RngState] {
|
|
const [f, next] = nextFloat(state);
|
|
return [Math.floor(f * n), next];
|
|
}
|
|
|
|
/** Roll the Wiz-War die (numbered 1-4). */
|
|
export function rollDie(state: RngState): [number, RngState] {
|
|
const [i, next] = nextInt(state, 4);
|
|
return [i + 1, next];
|
|
}
|
|
|
|
/** Fisher-Yates shuffle. Returns a new array and the advanced state. */
|
|
export function shuffle<T>(state: RngState, items: readonly T[]): [T[], RngState] {
|
|
const arr = [...items];
|
|
let rng = state;
|
|
for (let i = arr.length - 1; i > 0; i--) {
|
|
const [j, next] = nextInt(rng, i + 1);
|
|
rng = next;
|
|
[arr[i], arr[j]] = [arr[j]!, arr[i]!];
|
|
}
|
|
return [arr, rng];
|
|
}
|