- packages/engine: 시드 RNG(sfc32), GameDefinition 타입, 무작위 대국·정보 유출 테스트 도구 - packages/shared: WS 프로토콜 스키마, 닉네임·방 코드 규칙, 게임 카탈로그 - packages/games/omok: 한국식·렌주·자유룰, 재귀 금수 판정, 무르기·무승부·시계 (테스트 27개) - apps/server: SQLite 스키마, 게스트·디스코드 로그인, 세션, 방 관리(저장·복구·타이머·재접속), WS 게이트웨이, HTTP API Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
37 lines
1.2 KiB
TypeScript
37 lines
1.2 KiB
TypeScript
import { describe, expect, test } from 'bun:test';
|
|
import { SeededRng, createSeed, seedFromHex, seedToHex } from './rng';
|
|
|
|
describe('SeededRng', () => {
|
|
test('is deterministic for the same seed', () => {
|
|
const a = SeededRng.fromSeed([1, 2, 3, 4]);
|
|
const b = SeededRng.fromSeed([1, 2, 3, 4]);
|
|
for (let i = 0; i < 100; i++) expect(a.nextUint32()).toBe(b.nextUint32());
|
|
});
|
|
|
|
test('state can be saved and resumed', () => {
|
|
const a = SeededRng.fromSeed([9, 8, 7, 6]);
|
|
a.int(10);
|
|
const resumed = new SeededRng(a.state());
|
|
expect(resumed.int(1000)).toBe(a.int(1000));
|
|
});
|
|
|
|
test('int stays in range and is roughly uniform', () => {
|
|
const r = SeededRng.fromSeed([5, 5, 5, 5]);
|
|
const counts = new Array(6).fill(0);
|
|
for (let i = 0; i < 60000; i++) counts[r.int(6)]++;
|
|
for (const c of counts) expect(Math.abs(c - 10000)).toBeLessThan(500);
|
|
});
|
|
|
|
test('shuffle is a permutation', () => {
|
|
const r = SeededRng.fromSeed(createSeed());
|
|
const arr = Array.from({ length: 52 }, (_, i) => i);
|
|
const s = r.shuffle(arr);
|
|
expect(s.slice().sort((x, y) => x - y)).toEqual(arr);
|
|
});
|
|
|
|
test('hex round trip', () => {
|
|
const seed = createSeed();
|
|
expect(seedFromHex(seedToHex(seed))).toEqual(seed);
|
|
});
|
|
});
|