diff --git a/src/game/chase/rng.ts b/src/game/chase/rng.ts new file mode 100644 index 0000000..dddaf37 --- /dev/null +++ b/src/game/chase/rng.ts @@ -0,0 +1,45 @@ +export type Rng = () => number; + +const DEFAULT_SEED = 0x9e3779b9; + +function hashString(input: string): number { + let hash = 2166136261; + for (let i = 0; i < input.length; i += 1) { + hash ^= input.charCodeAt(i); + hash = Math.imul(hash, 16777619); + } + return hash >>> 0; +} + +export function normalizeSeed(input?: string | number): number { + if (typeof input === "number" && Number.isFinite(input)) { + return input >>> 0; + } + + if (typeof input === "string") { + const trimmed = input.trim(); + if (trimmed.length === 0) { + return DEFAULT_SEED; + } + + const numericSeed = Number(trimmed); + if (Number.isFinite(numericSeed)) { + return numericSeed >>> 0; + } + + return hashString(trimmed); + } + + return DEFAULT_SEED; +} + +export function createRng(seed: number): Rng { + let state = normalizeSeed(seed); + + return () => { + state = (state + 0x6d2b79f5) >>> 0; + let t = Math.imul(state ^ (state >>> 15), 1 | state); + t ^= t + Math.imul(t ^ (t >>> 7), 61 | t); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} diff --git a/src/game/chase/types.ts b/src/game/chase/types.ts new file mode 100644 index 0000000..1b1d48c --- /dev/null +++ b/src/game/chase/types.ts @@ -0,0 +1,14 @@ +export type NodeId = string; + +export type Difficulty = "easy" | "normal" | "hard"; + +export type GameStatus = "idle" | "running" | "won" | "lost"; + +export interface GraphNode { + id: NodeId; + neighbors: NodeId[]; +} + +export interface GameGraph { + nodes: Record; +} diff --git a/tests/chase/rng.test.ts b/tests/chase/rng.test.ts new file mode 100644 index 0000000..33ccec6 --- /dev/null +++ b/tests/chase/rng.test.ts @@ -0,0 +1,27 @@ +import { describe, expect, it } from "vitest"; +import { createRng, normalizeSeed } from "../../src/game/chase/rng"; + +describe("chase rng", () => { + it("generates the same sequence for the same seed", () => { + const seed = 12345; + const rngA = createRng(seed); + const rngB = createRng(seed); + + const sequenceA = Array.from({ length: 5 }, () => rngA()); + const sequenceB = Array.from({ length: 5 }, () => rngB()); + + expect(sequenceA).toEqual(sequenceB); + }); + + it("normalizeSeed returns a number for undefined input", () => { + expect(typeof normalizeSeed(undefined)).toBe("number"); + }); + + it("normalizeSeed returns a number for empty string", () => { + expect(typeof normalizeSeed("")).toBe("number"); + }); + + it("normalizeSeed returns a number for non-numeric string", () => { + expect(typeof normalizeSeed("abc")).toBe("number"); + }); +});