3 changed files with 86 additions and 0 deletions
@ -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; |
|||
}; |
|||
} |
|||
@ -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>; |
|||
} |
|||
@ -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…
Reference in new issue