diff --git a/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala b/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala index 64e8d3a..4e3b47d 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/controller/GameController.scala @@ -63,7 +63,8 @@ object GameController: case PromotionPiece.Bishop => PieceType.Bishop case PromotionPiece.Knight => PieceType.Knight val newBoard = boardAfterMove.updated(to, Piece(turn, promotedPieceType)) - val newHistory = history.addMove(from, to, None, Some(piece)) + // Promotion is always a pawn move → clock resets + val newHistory = history.addMove(from, to, None, Some(piece), wasPawnMove = true) toMoveResult(newBoard, newHistory, captured, turn) // --------------------------------------------------------------------------- @@ -91,7 +92,9 @@ object GameController: val capturedSq = EnPassantCalculator.capturedPawnSquare(to, turn) (b.removed(capturedSq), board.pieceAt(capturedSq)) else (b, cap) - val newHistory = history.addMove(from, to, castleOpt) + val wasPawnMove = board.pieceAt(from).exists(_.pieceType == PieceType.Pawn) + val wasCapture = captured.isDefined + val newHistory = history.addMove(from, to, castleOpt, wasPawnMove = wasPawnMove, wasCapture = wasCapture) toMoveResult(newBoard, newHistory, captured, turn) private def toMoveResult(newBoard: Board, newHistory: GameHistory, captured: Option[Piece], turn: Color): MoveResult = diff --git a/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala b/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala index c379d4a..3ec0330 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/controller/GameControllerTest.scala @@ -488,3 +488,39 @@ class GameControllerTest extends AnyFunSuite with Matchers: PromotionPiece.Knight, Color.White ) result should be (MoveResult.Stalemate) + + // ──── half-move clock propagation ──────────────────────────────────── + + test("processMove: non-pawn non-capture increments halfMoveClock"): + // g1f3 is a knight move — not a pawn, not a capture + processMove(Board.initial, GameHistory.empty, Color.White, "g1f3") match + case MoveResult.Moved(_, newHistory, _, _) => + newHistory.halfMoveClock shouldBe 1 + case other => fail(s"Expected Moved, got $other") + + test("processMove: pawn move resets halfMoveClock to 0"): + processMove(Board.initial, GameHistory.empty, Color.White, "e2e4") match + case MoveResult.Moved(_, newHistory, _, _) => + newHistory.halfMoveClock shouldBe 0 + case other => fail(s"Expected Moved, got $other") + + test("processMove: capture resets halfMoveClock to 0"): + // White pawn on e5, Black pawn on d6 — exd6 is a capture + val board = Board(Map( + sq(File.E, Rank.R5) -> Piece.WhitePawn, + sq(File.D, Rank.R6) -> Piece.BlackPawn, + sq(File.E, Rank.R1) -> Piece.WhiteKing, + sq(File.E, Rank.R8) -> Piece.BlackKing + )) + val history = GameHistory(halfMoveClock = 10) + processMove(board, history, Color.White, "e5d6") match + case MoveResult.Moved(_, newHistory, _, _) => + newHistory.halfMoveClock shouldBe 0 + case other => fail(s"Expected Moved, got $other") + + test("processMove: clock carries from previous history on non-pawn non-capture"): + val history = GameHistory(halfMoveClock = 5) + processMove(Board.initial, history, Color.White, "g1f3") match + case MoveResult.Moved(_, newHistory, _, _) => + newHistory.halfMoveClock shouldBe 6 + case other => fail(s"Expected Moved, got $other")