Browse Source

feat(chase): add deterministic rng and core chase types

Made-with: Cursor
master
npmrun 2 weeks ago
parent
commit
49e6e7b6d1
  1. 45
      src/game/chase/rng.ts
  2. 14
      src/game/chase/types.ts
  3. 27
      tests/chase/rng.test.ts

45
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;
};
}

14
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<NodeId, GraphNode>;
}

27
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");
});
});
Loading…
Cancel
Save