docs: extend 50-move rule spec with FEN and PGN integration

This commit is contained in:
LQ63
2026-03-31 22:46:29 +02:00
parent 381c3f06a1
commit f61ffce22a
@@ -130,7 +130,67 @@ This fires immediately after the `MoveExecutedEvent` (or `CheckDetectedEvent`) f
---
## Section 4: Files Changed
## Section 4: FEN Integration
`FenExporter.gameStateToFen` and `FenParser.parseFen` already handle `halfMoveClock` at the `GameState` level — no changes to those files are needed.
The bridge between `GameHistory.halfMoveClock` and `GameState.halfMoveClock` is a caller responsibility:
**FEN export (writing):** When constructing a `GameState` for FEN export, pass `halfMoveClock = history.halfMoveClock`. Since `GameEngine` already exposes `def history: GameHistory`, this works automatically once the field is populated:
```scala
GameState(
piecePlacement = FenExporter.boardToFen(engine.board),
activeColor = engine.turn,
...,
halfMoveClock = engine.history.halfMoveClock,
...
)
```
**FEN import (reading):** When loading from a parsed `GameState`, initialise the engine with a `GameHistory` carrying the parsed clock:
```scala
val gs = FenParser.parseFen(fenString).get
new GameEngine(
initialBoard = FenParser.parseBoard(gs.piecePlacement).get,
initialHistory = GameHistory(halfMoveClock = gs.halfMoveClock),
initialTurn = gs.activeColor
)
```
A round-trip test is added to `FenExporterTest` / `FenParserTest` verifying that a non-zero clock survives export → import.
---
## Section 5: PGN Integration
`PgnExporter.exportGame` currently hardcodes `" *"` as the game termination marker. PGN standard requires the marker to match the `Result` header (`1-0`, `0-1`, `1/2-1/2`, or `*`).
### Change to `PgnExporter`
Replace the hardcoded `" *"` with the value from the `Result` header:
```scala
val termination = headers.getOrElse("Result", "*")
moveLines.mkString(" ") + s" $termination"
```
### Draw claim result
When `DrawClaimedEvent` is handled by a caller that exports PGN, it should pass:
```scala
Map("Result" -> "1/2-1/2", ...)
```
The move text will then end with `1/2-1/2`, which is correct per PGN standard for a drawn game.
A test is added to `PgnExporterTest` verifying that `exportGame` with `"Result" -> "1/2-1/2"` produces a move text ending in `1/2-1/2`.
---
## Section 6: Files Changed
| File | Change |
|------|--------|
@@ -138,15 +198,18 @@ This fires immediately after the `MoveExecutedEvent` (or `CheckDetectedEvent`) f
| `modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala` | Compute and pass flags in `applyNormalMove` and `completePromotion` |
| `modules/core/src/main/scala/de/nowchess/chess/observer/Observer.scala` | Add `FiftyMoveRuleAvailableEvent` and `DrawClaimedEvent` |
| `modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala` | Handle `"draw"` input; fire `FiftyMoveRuleAvailableEvent` after eligible moves |
| `modules/core/src/main/scala/de/nowchess/chess/notation/PgnExporter.scala` | Derive termination marker from `Result` header instead of hardcoding `*` |
| `modules/core/src/test/scala/de/nowchess/chess/logic/GameHistoryTest.scala` | New test suite for clock update rules |
| `modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala` | Tests for clock values in `applyNormalMove` and `completePromotion` |
| `modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineTest.scala` | Tests for `"draw"` command and `FiftyMoveRuleAvailableEvent` |
| `modules/core/src/test/scala/de/nowchess/chess/notation/PgnExporterTest.scala` | Test for `1/2-1/2` termination marker |
| `modules/core/src/test/scala/de/nowchess/chess/notation/FenExporterTest.scala` | Round-trip test: non-zero `halfMoveClock` survives FEN export → import |
`EnPassantCalculator`, `CastlingRightsCalculator`, `MoveValidator`, `GameRules`, and their tests are **not** touched.
---
## Section 5: Testing
## Section 7: Testing
### `GameHistoryTest`
- Clock starts at 0
@@ -167,3 +230,10 @@ This fires immediately after the `MoveExecutedEvent` (or `CheckDetectedEvent`) f
- `processUserInput("draw")` fires `InvalidMoveEvent` when `halfMoveClock < 100`
- A successful non-pawn, non-capture move that brings the clock to exactly 100 fires `FiftyMoveRuleAvailableEvent`
- A successful move that does not reach 100 does not fire `FiftyMoveRuleAvailableEvent`
### `PgnExporterTest`
- `exportGame` with `"Result" -> "1/2-1/2"` produces move text ending in `1/2-1/2`
- `exportGame` with no `Result` header still produces `*` as before (backward-compatible)
### `FenExporterTest`
- Round-trip: a `GameHistory` with `halfMoveClock = 42` exported to FEN and re-parsed yields `halfMoveClock = 42`