feat(chess): flesh out all six remaining stub presets
Implements king-heals, poisoned-squares, capture-to-win,
last-piece-standing, explosive-rook, and queen-splits via the new
preset hook infrastructure. All six were registered but no-op stubs
with "// Full integration in ChessEngine (P3.11)" comments that
never got followed up.
New hooks on PresetDef
~~~~~~~~~~~~~~~~~~~~~~~
- onAfterMove(engine, moverColor)
Fires after applyMove`s turn switch but before game-result check.
Scope filtering is preset-side because rules have different
notions of "who does the hook target" (king-heals targets the
non-mover; poisoned-squares targets everyone on a poisoned
square).
- onCheckGameResult(engine) -> GameResult | undefined
Lets a preset override the engine`s default terminal-position
logic. First non-undefined return wins in registration order.
GameResult type extended with white-wins / black-wins so variant
rules can name the winner explicitly (checkmate implicitly means
"side to move loses" — insufficient for capture-to-win which can
end mid-move with either side winning).
Preset implementations
~~~~~~~~~~~~~~~~~~~~~~~
- king-heals: onAfterMove heals non-mover`s king +1 HP (max 3) when
not in check. Requires piece-hp.
- poisoned-squares: onAfterMove damages every piece standing on
d4/e4/d5/e5. Retracts pieces hitting 0 HP. Requires piece-hp.
Exports POISONED_SQUARES set for the UI overlay.
- capture-to-win: onBeforeCapture records the capturer`s color on
the game entity via Winner fact. onCheckGameResult converts it
into white-wins/black-wins. onDeactivate clears the Winner fact.
- last-piece-standing: onCheckGameResult counts pieces by color.
Zero on one side -> the other wins. Override returns "ongoing"
otherwise, suppressing default checkmate.
- explosive-rook: onBeforeCapture intercepts rook captures,
detonates all pieces within Chebyshev distance 2 on the rank
and file of the target (diagonals spared), moves the rook onto
the target square. Does not chain.
- queen-splits: onBeforeCapture intercepts queen captures,
retracts target and queen, spawns rook on target square and
bishop on first empty clockwise-from-N neighbour. Bishop is
forfeit if all 8 neighbours are occupied.
Board.tsx gains a per-SQUARE overlay registry (separate from per-
piece) so poisoned-squares can render a pulsing green tint on the
four central squares. The poisoned-squares.ui.tsx module registers
its overlay at load time via ui-overlays-index.ts.
GameView shows "White wins!"/"Black wins!" for the new result
variants; confetti fires on any decisive result; useChessEngine
and useMultiplayerGame play the checkmate sound on decisive results.
Server-side: GameEndReason extended with "variant-win" so the wire
protocol can signal decisive variant results without conflating
them with checkmate. MoveResult.gameOver exposes the correct
winner (not the mover) for white-wins/black-wins.
Testing
~~~~~~~
New fleshed-presets.test.ts: 13 integration tests covering each
preset`s canonical behaviour (healing cap, denial on check, poison
decrement + kill, first-capture-wins, annihilation override,
detonation AoE, queen fission spawn). 874 tests pass (+13); all 3
E2E specs green.
This commit is contained in:
parent
2739cc2689
commit
1e292d6c00
18 changed files with 1077 additions and 21 deletions
|
|
@ -66,7 +66,9 @@ export type GameEndReason =
|
|||
| "stalemate"
|
||||
| "50-move"
|
||||
| "threefold"
|
||||
| "insufficient";
|
||||
| "insufficient"
|
||||
// Preset-defined decisive result; see GameResult variants.
|
||||
| "variant-win";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// GameSession
|
||||
|
|
@ -328,6 +330,13 @@ function mapGameResult(
|
|||
return { winner: "draw", reason: "threefold" };
|
||||
case "draw-insufficient":
|
||||
return { winner: "draw", reason: "insufficient" };
|
||||
// Preset-defined decisive results. The engine names the winner
|
||||
// explicitly because variant rules don't always pair "winner"
|
||||
// with "side to move" the way standard checkmate does.
|
||||
case "white-wins":
|
||||
return { winner: "white", reason: "variant-win" };
|
||||
case "black-wins":
|
||||
return { winner: "black", reason: "variant-win" };
|
||||
default: {
|
||||
// Exhaustiveness guard — if GameResult ever grows a variant, TS
|
||||
// will flag this by failing the never-cast.
|
||||
|
|
|
|||
|
|
@ -59,6 +59,10 @@ export const GameEndReasonSchema = z.enum([
|
|||
"threefold",
|
||||
"insufficient",
|
||||
"player_left",
|
||||
// Preset-defined decisive result (first-blood, annihilation, etc.).
|
||||
// The UI reads the winner separately; this reason tag just signals
|
||||
// "variant rule ended the game" to clients that care.
|
||||
"variant-win",
|
||||
]);
|
||||
export type GameEndReason = z.infer<typeof GameEndReasonSchema>;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue