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 2f59cde..d08f0fd 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 @@ -2,7 +2,7 @@ package de.nowchess.chess.controller import scala.io.StdIn import de.nowchess.api.board.{Board, Color, Piece} -import de.nowchess.chess.logic.{MoveValidator, GameRules, PositionStatus} +import de.nowchess.chess.logic.{GameContext, MoveValidator, GameRules, PositionStatus} import de.nowchess.chess.view.Renderer // --------------------------------------------------------------------------- @@ -49,7 +49,7 @@ object GameController: MoveResult.IllegalMove else val (newBoard, captured) = board.withMove(from, to) - GameRules.gameStatus(newBoard, turn.opposite) match + GameRules.gameStatus(GameContext(newBoard), turn.opposite) match case PositionStatus.Normal => MoveResult.Moved(newBoard, captured, turn.opposite) case PositionStatus.InCheck => MoveResult.MovedInCheck(newBoard, captured, turn.opposite) case PositionStatus.Mated => MoveResult.Checkmate(turn) diff --git a/modules/core/src/main/scala/de/nowchess/chess/logic/GameRules.scala b/modules/core/src/main/scala/de/nowchess/chess/logic/GameRules.scala index 97b7bb7..a59a872 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/logic/GameRules.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/logic/GameRules.scala @@ -38,9 +38,9 @@ object GameRules: .toSet /** Position status for the side whose turn it is (`color`). */ - def gameStatus(board: Board, color: Color): PositionStatus = - val moves = legalMoves(GameContext(board), color) - val inCheck = isInCheck(board, color) + def gameStatus(ctx: GameContext, color: Color): PositionStatus = + val moves = legalMoves(ctx, color) + val inCheck = isInCheck(ctx.board, color) if moves.isEmpty && inCheck then PositionStatus.Mated else if moves.isEmpty then PositionStatus.Drawn else if inCheck then PositionStatus.InCheck diff --git a/modules/core/src/test/scala/de/nowchess/chess/logic/GameRulesTest.scala b/modules/core/src/test/scala/de/nowchess/chess/logic/GameRulesTest.scala index 48f285b..752f7ad 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/logic/GameRulesTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/logic/GameRulesTest.scala @@ -62,33 +62,30 @@ class GameRulesTest extends AnyFunSuite with Matchers: test("gameStatus: checkmate returns Mated"): // White Qh8, Ka6; Black Ka8 // Qh8 attacks Ka8 along rank 8; all escape squares covered (spec-verified position) - val b = board( + GameRules.gameStatus(ctx( sq(File.H, Rank.R8) -> Piece.WhiteQueen, sq(File.A, Rank.R6) -> Piece.WhiteKing, sq(File.A, Rank.R8) -> Piece.BlackKing - ) - GameRules.gameStatus(b, Color.Black) shouldBe PositionStatus.Mated + ), Color.Black) shouldBe PositionStatus.Mated test("gameStatus: stalemate returns Drawn"): // White Qb6, Kc6; Black Ka8 // Black king has no legal moves and is not in check (spec-verified position) - val b = board( + GameRules.gameStatus(ctx( sq(File.B, Rank.R6) -> Piece.WhiteQueen, sq(File.C, Rank.R6) -> Piece.WhiteKing, sq(File.A, Rank.R8) -> Piece.BlackKing - ) - GameRules.gameStatus(b, Color.Black) shouldBe PositionStatus.Drawn + ), Color.Black) shouldBe PositionStatus.Drawn test("gameStatus: king in check with legal escape returns InCheck"): // White Ra8 attacks Black Ke8 along rank 8; king can escape to d7, e7, f7 - val b = board( + GameRules.gameStatus(ctx( sq(File.A, Rank.R8) -> Piece.WhiteRook, sq(File.E, Rank.R8) -> Piece.BlackKing - ) - GameRules.gameStatus(b, Color.Black) shouldBe PositionStatus.InCheck + ), Color.Black) shouldBe PositionStatus.InCheck test("gameStatus: normal starting position returns Normal"): - GameRules.gameStatus(Board.initial, Color.White) shouldBe PositionStatus.Normal + GameRules.gameStatus(GameContext(Board.initial), Color.White) shouldBe PositionStatus.Normal test("legalMoves: includes castling destination when available"): val c = GameContext( @@ -114,3 +111,21 @@ class GameRulesTest extends AnyFunSuite with Matchers: blackCastling = CastlingRights.None ) GameRules.legalMoves(c, Color.White) should not contain (sq(File.E, Rank.R1) -> sq(File.G, Rank.R1)) + + test("gameStatus: returns Normal (not Drawn) when castling is the only legal move"): + // White King e1, Rook h1 (kingside castling available). + // Black Rooks d2 and f2 box the king: d1 attacked by d2, e2 attacked by both, + // f1 attacked by f2. King cannot move to any adjacent square without entering + // an attacked square or an enemy piece. Only legal move: castle to g1. + val c = GameContext( + board = board( + sq(File.E, Rank.R1) -> Piece.WhiteKing, + sq(File.H, Rank.R1) -> Piece.WhiteRook, + sq(File.D, Rank.R2) -> Piece.BlackRook, + sq(File.F, Rank.R2) -> Piece.BlackRook, + sq(File.A, Rank.R8) -> Piece.BlackKing + ), + whiteCastling = CastlingRights(kingSide = true, queenSide = false), + blackCastling = CastlingRights.None + ) + GameRules.gameStatus(c, Color.White) shouldBe PositionStatus.Normal