feat: migrate GameRules.gameStatus to GameContext; add false-stalemate test
- gameStatus now accepts GameContext instead of Board so legalMoves (which already requires GameContext for castling) is called directly without discarding castling rights - All four existing gameStatus call sites in GameRulesTest migrated to ctx(...) - New test: castling as sole legal move returns Normal, not Drawn - GameController.processMove updated to wrap newBoard in GameContext at the gameStatus call site (full ctx propagation deferred to Task 6) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user