e4cf143e9f
CI / changes (pull_request) Successful in 1s
CI / unit (pull_request) Successful in 10s
CI / integration (pull_request) Successful in 15s
CI / ui (pull_request) Successful in 1m9s
CI / conformance (pull_request) Successful in 9s
CI / gate (pull_request) Successful in 0s
CI / deploy (pull_request) Successful in 1m39s
The offline vs_ai game engine — a faithful TS port of backend/internal/engine that drives a whole local game with no backend. Composes with the move generator (#188) and robot strategy (#189) from Phase A; not yet wired into the UI (Phase B2/B3). - ui/src/lib/localgame/ruleset.ts: static per-variant tile values / bag counts / blank count, mirrored from rules.go (offline scoring is self-contained; online uses the server alphabet). Pinned by ruleset.parity.test.ts against a Go fixture. - bag.ts: the tile bag (fill from counts/blanks, draw-from-end, return+reshuffle) on a deterministic in-house PRNG — a game replays from its seed, not bit-identical to a server game (per plan). - board.ts: the mutable board, satisfying the validator/generator read view + set(). - engine.ts: LocalGame — deal / play (reusing validate.ts) / pass / exchange / resign, scoreless(6) & out-of-tiles end detection, end-of-game rack penalties, winner; mirrors game.go. The end-game math is exported as pure functions, pinned against the Go engine (engine.parity.test.ts, 9 constructed positions). - engine.test.ts: a full-loop smoke — two robots play a whole vs_ai game to completion via generateMoves + decide, and it is reproducible from the seed. - backend: movegen now dumps the per-variant rulesets; a new in-package engine emitter (endfixture_test.go, env-gated) produces the end-game golden. Pure additive library code; no runtime behavior change (unused at runtime, bundle unchanged).
36 lines
1.2 KiB
TypeScript
36 lines
1.2 KiB
TypeScript
import { describe, it, expect } from 'vitest';
|
|
import { readFileSync } from 'node:fs';
|
|
import { applyEndAdjustment, winner, type EndReason } from './engine';
|
|
import { RULESETS } from './ruleset';
|
|
import type { Variant } from '../model';
|
|
|
|
// The offline engine must settle unplayed racks and decide the winner exactly as the Go engine
|
|
// does, so a local game finishes with the same scores. Golden from the in-package emitter
|
|
// (backend/internal/engine/endfixture_test.go).
|
|
interface EndCase {
|
|
name: string;
|
|
variant: Variant;
|
|
reason: EndReason;
|
|
hands: number[][];
|
|
scores: number[];
|
|
resigned: boolean[];
|
|
toMove: number;
|
|
scoresAfter: number[];
|
|
winner: number;
|
|
}
|
|
|
|
const fx = JSON.parse(
|
|
readFileSync(new URL('./testdata/endgame.json', import.meta.url), 'utf8'),
|
|
) as { cases: EndCase[] };
|
|
|
|
describe('offline engine end-game parity vs Go', () => {
|
|
for (const c of fx.cases) {
|
|
it(c.name, () => {
|
|
const values = RULESETS[c.variant].values;
|
|
const after = applyEndAdjustment(c.reason, c.hands, c.scores, c.toMove, values);
|
|
expect(after).toEqual(c.scoresAfter);
|
|
expect(winner(true, c.reason, after, c.resigned)).toBe(c.winner);
|
|
});
|
|
}
|
|
});
|