feat(offline): local game engine (Phase B1)
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
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).
This commit is contained in:
+246
@@ -0,0 +1,246 @@
|
||||
{
|
||||
"cases": [
|
||||
{
|
||||
"name": "out-basic",
|
||||
"variant": "scrabble_en",
|
||||
"reason": "out_of_tiles",
|
||||
"hands": [
|
||||
[],
|
||||
[
|
||||
0,
|
||||
1,
|
||||
2
|
||||
]
|
||||
],
|
||||
"scores": [
|
||||
50,
|
||||
40
|
||||
],
|
||||
"resigned": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"toMove": 0,
|
||||
"scoresAfter": [
|
||||
57,
|
||||
33
|
||||
],
|
||||
"winner": 0
|
||||
},
|
||||
{
|
||||
"name": "out-blank",
|
||||
"variant": "scrabble_en",
|
||||
"reason": "out_of_tiles",
|
||||
"hands": [
|
||||
[],
|
||||
[
|
||||
255,
|
||||
0
|
||||
]
|
||||
],
|
||||
"scores": [
|
||||
30,
|
||||
30
|
||||
],
|
||||
"resigned": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"toMove": 0,
|
||||
"scoresAfter": [
|
||||
31,
|
||||
29
|
||||
],
|
||||
"winner": 0
|
||||
},
|
||||
{
|
||||
"name": "out-erudit-yo",
|
||||
"variant": "erudit_ru",
|
||||
"reason": "out_of_tiles",
|
||||
"hands": [
|
||||
[],
|
||||
[
|
||||
6,
|
||||
32
|
||||
]
|
||||
],
|
||||
"scores": [
|
||||
10,
|
||||
10
|
||||
],
|
||||
"resigned": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"toMove": 0,
|
||||
"scoresAfter": [
|
||||
13,
|
||||
7
|
||||
],
|
||||
"winner": 0
|
||||
},
|
||||
{
|
||||
"name": "out-tie",
|
||||
"variant": "scrabble_en",
|
||||
"reason": "out_of_tiles",
|
||||
"hands": [
|
||||
[],
|
||||
[]
|
||||
],
|
||||
"scores": [
|
||||
30,
|
||||
30
|
||||
],
|
||||
"resigned": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"toMove": 0,
|
||||
"scoresAfter": [
|
||||
30,
|
||||
30
|
||||
],
|
||||
"winner": -1
|
||||
},
|
||||
{
|
||||
"name": "out-3p",
|
||||
"variant": "scrabble_ru",
|
||||
"reason": "out_of_tiles",
|
||||
"hands": [
|
||||
[],
|
||||
[
|
||||
0,
|
||||
1
|
||||
],
|
||||
[
|
||||
2
|
||||
]
|
||||
],
|
||||
"scores": [
|
||||
10,
|
||||
10,
|
||||
10
|
||||
],
|
||||
"resigned": [
|
||||
false,
|
||||
false,
|
||||
false
|
||||
],
|
||||
"toMove": 0,
|
||||
"scoresAfter": [
|
||||
15,
|
||||
6,
|
||||
9
|
||||
],
|
||||
"winner": 0
|
||||
},
|
||||
{
|
||||
"name": "scoreless",
|
||||
"variant": "scrabble_en",
|
||||
"reason": "scoreless",
|
||||
"hands": [
|
||||
[
|
||||
0,
|
||||
1
|
||||
],
|
||||
[
|
||||
2,
|
||||
3
|
||||
]
|
||||
],
|
||||
"scores": [
|
||||
20,
|
||||
20
|
||||
],
|
||||
"resigned": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"toMove": 0,
|
||||
"scoresAfter": [
|
||||
16,
|
||||
15
|
||||
],
|
||||
"winner": 0
|
||||
},
|
||||
{
|
||||
"name": "resign-2p",
|
||||
"variant": "scrabble_en",
|
||||
"reason": "resign",
|
||||
"hands": [
|
||||
[],
|
||||
[
|
||||
0
|
||||
]
|
||||
],
|
||||
"scores": [
|
||||
100,
|
||||
10
|
||||
],
|
||||
"resigned": [
|
||||
true,
|
||||
false
|
||||
],
|
||||
"toMove": 1,
|
||||
"scoresAfter": [
|
||||
100,
|
||||
10
|
||||
],
|
||||
"winner": 1
|
||||
},
|
||||
{
|
||||
"name": "resign-3p",
|
||||
"variant": "scrabble_en",
|
||||
"reason": "resign",
|
||||
"hands": [
|
||||
[],
|
||||
[],
|
||||
[]
|
||||
],
|
||||
"scores": [
|
||||
50,
|
||||
60,
|
||||
40
|
||||
],
|
||||
"resigned": [
|
||||
false,
|
||||
true,
|
||||
false
|
||||
],
|
||||
"toMove": 0,
|
||||
"scoresAfter": [
|
||||
50,
|
||||
60,
|
||||
40
|
||||
],
|
||||
"winner": 0
|
||||
},
|
||||
{
|
||||
"name": "aborted",
|
||||
"variant": "scrabble_en",
|
||||
"reason": "aborted",
|
||||
"hands": [
|
||||
[
|
||||
0
|
||||
],
|
||||
[
|
||||
1
|
||||
]
|
||||
],
|
||||
"scores": [
|
||||
40,
|
||||
30
|
||||
],
|
||||
"resigned": [
|
||||
false,
|
||||
false
|
||||
],
|
||||
"toMove": 0,
|
||||
"scoresAfter": [
|
||||
40,
|
||||
30
|
||||
],
|
||||
"winner": -1
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user