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
|
|
@ -73,6 +73,12 @@ export type GameResult =
|
|||
| "draw-50"
|
||||
| "draw-3fold"
|
||||
| "draw-insufficient"
|
||||
// Preset-defined terminal states. The regular checkmate result always
|
||||
// implicitly means "the side to move loses" (standard chess), but
|
||||
// variants like capture-to-win or last-piece-standing need to name
|
||||
// the winner explicitly because the side to move may be the WINNER.
|
||||
| "white-wins"
|
||||
| "black-wins"
|
||||
| "ongoing";
|
||||
|
||||
export class ChessEngine {
|
||||
|
|
@ -343,10 +349,30 @@ export class ChessEngine {
|
|||
def?.onDeactivate?.(this);
|
||||
}
|
||||
|
||||
// Post-move preset hooks: fire against EVERY still-active preset
|
||||
// (regardless of scope). Scope-aware behaviour is the preset's
|
||||
// responsibility — king-heals affects the non-mover, poisoned-squares
|
||||
// affects the mover, so a single engine-level scope filter can't
|
||||
// serve both.
|
||||
for (const entry of this.activePresets.list()) {
|
||||
const def = PRESET_REGISTRY.get(entry.id);
|
||||
def?.onAfterMove?.(this, color);
|
||||
}
|
||||
|
||||
return this.checkGameResult();
|
||||
}
|
||||
|
||||
checkGameResult(): GameResult {
|
||||
// Preset override path: run onCheckGameResult on every active preset
|
||||
// in registration order. First non-undefined return wins. This lets
|
||||
// capture-to-win and last-piece-standing redefine "game over"
|
||||
// without touching engine internals.
|
||||
for (const entry of this.activePresets.list()) {
|
||||
const def = PRESET_REGISTRY.get(entry.id);
|
||||
const override = def?.onCheckGameResult?.(this);
|
||||
if (override !== undefined) return override;
|
||||
}
|
||||
|
||||
const nextColor = this.getCurrentTurn();
|
||||
if (isCheckmate(this.session, nextColor)) return "checkmate";
|
||||
if (isStalemate(this.session, nextColor)) return "stalemate";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue