Add 3 focused tests for GameEngine load/export functionality:
- loadGame with PgnParser validates PGN parsing and undo/redo state
- loadGame with FenParser validates position loading without move replay
- exportGame with PgnExporter validates PGN output
Fix: PgnParser now extends GameContextImport trait for consistency with FenParser.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add imports for GameContextImport and GameContextExport
- Add loadGame(importer, input) method to load game from importer
- Add exportGame(exporter) method to export current game context
- Remove entire loadPgn method (replaced by loadGame)
- Remove GameEngineLoadPgnTest (tests old loadPgn API)
loadGame supports both move replay and direct position loading:
- If no moves, sets position directly via loadPosition
- If moves exist, replays through command system for undo/redo support
- Notifies PgnLoadedEvent on success
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Removed unused CastleSide import from PgnExporter
- Updated TerminalUI and GUIObserver to use GameContext
- Temporarily disabled GUI FEN/PGN import-export (requires full rework with GameContext)
- Deleted unused logic files and GameController per spec
Note: GameEngine still needs final refactoring to use RuleSet + GameContext.
Core architecture (api -> rule -> core) is structurally complete.
Task 6: Updated all GameEvent case classes to use context: GameContext instead of separate board/history/turn
Task 7: Deleted old logic files and restored as compatibility layer in modules/api
Task 8: Verified build - main source compilation succeeds
Changes:
- Updated Observer.scala events: all GameEvent subclasses now accept GameContext
- Restored GameHistory and HistoryMove to modules/api as compatibility types
- Restored logic files (GameRules, MoveValidator, etc.) with updated imports
- Updated GameEngine to use currentContext helper for event creation
- Updated GameController to convert CastleSide to String for HistoryMove
- Updated PgnParser/PgnExporter to work with String representation of castling
- Added modules:rule to settings.gradle.kts for dependency resolution
- All main source code compiles successfully; tests expected to need refactoring
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Summary
- Implements the FIDE 50-move draw rule: a player may claim a draw if no pawn move or capture has occurred in the last
50 full moves (100 half-moves)
- Draw is not automatic — the eligible player must claim it via a TUI menu shown at the start of their turn
- halfMoveClock: Int is threaded through processMove and gameLoop; resets on pawn move, capture, or en passant;
increments on all other moves
Changes
- GameController.scala: extended MoveResult.Moved and MoveResult.MovedInCheck with newHalfMoveClock: Int; added
MoveResult.DrawClaimed; added halfMoveClock parameter to processMove and gameLoop; TUI menu shown when clock ≥ 100
- Main.scala: initial gameLoop call passes halfMoveClock = 0
- GameControllerTest.scala: updated all existing pattern matches; added 10 new tests covering clock reset, clock
increment, draw claim, and TUI menu behaviour
Test plan
- processMove: 'draw' with halfMoveClock = 100 → DrawClaimed
- processMove: 'draw' with halfMoveClock = 99 → InvalidFormat
- Pawn move / capture / en passant → clock resets to 0
- Quiet piece move → clock increments by 1
- MovedInCheck carries updated clock
- TUI menu appears when clock ≥ 100; option 1 claims draw, option 2 continues
- No TUI menu when clock < 100
- All 197 tests passing
Co-authored-by: LQ63 <lkhermann@web.de>
Reviewed-on: #9
Co-authored-by: Leon Hermann <lq@blackhole.local>
Co-committed-by: Leon Hermann <lq@blackhole.local>
- 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>