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).
45 lines
1.6 KiB
TypeScript
45 lines
1.6 KiB
TypeScript
// The mutable local-game board: a 15x15 row-major grid of placed tiles (alphabet-index letter
|
|
// plus a blank flag). It satisfies the read view the validator and generator need (GenBoard,
|
|
// which extends validate.ts's Board) and adds set() so the engine can apply a play. The board is
|
|
// alphabet-agnostic — a cell's letter is a variant index, meaningful with the variant's ruleset.
|
|
|
|
import { BOARD_SIZE } from '../premiums';
|
|
import type { Cell } from '../dict/validate';
|
|
import type { GenBoard } from '../dict/generate';
|
|
|
|
/** LocalBoard is the engine's in-memory board; it reads as a GenBoard and applies plays via set. */
|
|
export class LocalBoard implements GenBoard {
|
|
readonly rows = BOARD_SIZE;
|
|
readonly cols = BOARD_SIZE;
|
|
private readonly grid: (Cell | null)[];
|
|
private count = 0;
|
|
|
|
constructor() {
|
|
this.grid = new Array<Cell | null>(BOARD_SIZE * BOARD_SIZE).fill(null);
|
|
}
|
|
|
|
inBounds(row: number, col: number): boolean {
|
|
return row >= 0 && row < this.rows && col >= 0 && col < this.cols;
|
|
}
|
|
|
|
filled(row: number, col: number): boolean {
|
|
return this.inBounds(row, col) && this.grid[row * this.cols + col] !== null;
|
|
}
|
|
|
|
cellAt(row: number, col: number): Cell {
|
|
return this.grid[row * this.cols + col]!;
|
|
}
|
|
|
|
isEmpty(): boolean {
|
|
return this.count === 0;
|
|
}
|
|
|
|
/** set places a tile at (row, col). It assumes the square was empty (a play only lays tiles
|
|
* on empty squares), keeping the filled count exact for isEmpty. */
|
|
set(row: number, col: number, letter: number, blank: boolean): void {
|
|
const i = row * this.cols + col;
|
|
if (this.grid[i] === null) this.count++;
|
|
this.grid[i] = { letter, blank };
|
|
}
|
|
}
|