- Add EnPassantCalculator to derive the en passant target square from GameHistory, detect en passant captures, and
compute the captured pawn's square
- Extend MoveValidator.legalTargets to include the en passant diagonal square in pawn legal targets
- Extend GameController.processMove to remove the captured pawn from the board when an en passant capture is played
Details
En passant is derived purely from the last HistoryMove — no new state is introduced. If the last move was a double
pawn push, the target square is the square the pawn passed through. The board mutation follows the same pattern as
castling: board.withMove moves the capturing pawn, then board.removed removes the captured pawn from its actual square
(which differs from the destination square).
Test Plan
- EnPassantCalculatorTest — 14 unit tests covering target derivation, captured square calculation, and capture
detection for both colors
- MoveValidatorTest — 5 new tests: ep target included/excluded based on history, adjacency filter, both colors, case _
branch coverage
- GameControllerTest — 2 integration tests: white and black en passant capture removes pawn from board and returns
correct captured piece
- 100% scoverage (line/branch/method) confirmed
Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #8
Reviewed-by: Janis <janis-e@gmx.de>
Co-authored-by: Leon Hermann <lq@blackhole.local>
Co-committed-by: Leon Hermann <lq@blackhole.local>
## Summary
- Introduces `GameContext` wrapper (board + castling rights) threading through the entire engine pipeline
- Extends `MoveValidator` with `castlingTargets`, context-aware `legalTargets`/`isLegal` overloads, and helpers (`isCastle`, `castleSide`)
- Updates `GameRules.legalMoves` and `gameStatus` to use `GameContext`, preventing false stalemate when castling is the only legal move
- Adds castle detection and atomic execution (`withCastle`) to `GameController.processMove`, plus full rights revocation via source- and
destination-square tables
## Test Plan
- [ ] 142 tests passing, 100% statement and branch coverage on `modules/core`
- [ ] White/Black kingside (e1g1/e8g8) and queenside (e1c1/e8c8) castling moves execute correctly
- [ ] All six legality conditions enforced (rights flags, home squares, empty transit, king not in check, transit/landing squares not attacked)
- [ ] Rights revoked on king moves, own rook moves, castle moves, and enemy rook captures
- [ ] False stalemate correctly prevented when castling is the only escape
Co-authored-by: LQ63 <lkhermann@web.de>
Co-authored-by: Janis <janis.e.20@gmx.de>
Reviewed-on: #1
Reviewed-by: Janis <janis-e@gmx.de>
Co-authored-by: Leon Hermann <lq@blackhole.local>
Co-committed-by: Leon Hermann <lq@blackhole.local>
- Add BlackKing/WhiteKing to capture board in 'legal capture returns Moved'
so the position is not treated as stalemate after the capture.
- Move WhiteKing from A3 to C3 in three MovedInCheck tests so it no
longer blocks the rook's path along file A.
- Remove Console.withOut(System.out) from withInput so it no longer
overrides the ByteArrayOutputStream installed by captureOutput.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace stub branches with GameRules.gameStatus dispatch in processMove
and fill in MovedInCheck/Checkmate/Stalemate cases in gameLoop.
Document 6 pre-existing test bugs in docs/unresolved.md.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>