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