From 7c568581a7cca895cbec78fe1785637c185149a9 Mon Sep 17 00:00:00 2001 From: LQ63 Date: Tue, 24 Mar 2026 12:51:14 +0100 Subject: [PATCH] refactor: migrate GameRules.legalMoves signature to GameContext Co-Authored-By: Claude Sonnet 4.6 --- .../scala/de/nowchess/chess/logic/GameRules.scala | 11 ++++++----- .../de/nowchess/chess/logic/GameRulesTest.scala | 14 ++++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) 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 c788651..3622b91 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 @@ -1,6 +1,7 @@ package de.nowchess.chess.logic import de.nowchess.api.board.* +import de.nowchess.chess.logic.GameContext enum PositionStatus: case Normal, InCheck, Mated, Drawn @@ -19,13 +20,13 @@ object GameRules: } /** All (from, to) moves for `color` that do not leave their own king in check. */ - def legalMoves(board: Board, color: Color): Set[(Square, Square)] = - board.pieces + def legalMoves(ctx: GameContext, color: Color): Set[(Square, Square)] = + ctx.board.pieces .collect { case (from, piece) if piece.color == color => from } .flatMap { from => - MoveValidator.legalTargets(board, from) + MoveValidator.legalTargets(ctx.board, from) .filter { to => - val (newBoard, _) = board.withMove(from, to) + val (newBoard, _) = ctx.board.withMove(from, to) !isInCheck(newBoard, color) } .map(to => from -> to) @@ -34,7 +35,7 @@ object GameRules: /** Position status for the side whose turn it is (`color`). */ def gameStatus(board: Board, color: Color): PositionStatus = - val moves = legalMoves(board, color) + val moves = legalMoves(GameContext(board), color) val inCheck = isInCheck(board, color) if moves.isEmpty && inCheck then PositionStatus.Mated else if moves.isEmpty then PositionStatus.Drawn 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 dfcd5f7..8af94e4 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 @@ -1,6 +1,7 @@ package de.nowchess.chess.logic import de.nowchess.api.board.* +import de.nowchess.chess.logic.GameContext import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers @@ -9,6 +10,9 @@ class GameRulesTest extends AnyFunSuite with Matchers: private def sq(f: File, r: Rank): Square = Square(f, r) private def board(entries: (Square, Piece)*): Board = Board(entries.toMap) + /** Wrap a board in a GameContext with no castling rights — for non-castling tests. */ + private def ctx(entries: (Square, Piece)*): GameContext = GameContext(Board(entries.toMap)) + // ──── isInCheck ────────────────────────────────────────────────────── test("isInCheck: king attacked by enemy rook on same rank"): @@ -36,22 +40,20 @@ class GameRulesTest extends AnyFunSuite with Matchers: test("legalMoves: move that exposes own king to rook is excluded"): // White King E1, White Rook E4 (pinned on E-file), Black Rook E8 // Moving the White Rook off the E-file would expose the king - val b = board( + val moves = GameRules.legalMoves(ctx( sq(File.E, Rank.R1) -> Piece.WhiteKing, sq(File.E, Rank.R4) -> Piece.WhiteRook, sq(File.E, Rank.R8) -> Piece.BlackRook - ) - val moves = GameRules.legalMoves(b, Color.White) + ), Color.White) moves should not contain (sq(File.E, Rank.R4) -> sq(File.D, Rank.R4)) test("legalMoves: move that blocks check is included"): // White King E1 in check from Black Rook E8; White Rook A5 can interpose on E5 - val b = board( + val moves = GameRules.legalMoves(ctx( sq(File.E, Rank.R1) -> Piece.WhiteKing, sq(File.A, Rank.R5) -> Piece.WhiteRook, sq(File.E, Rank.R8) -> Piece.BlackRook - ) - val moves = GameRules.legalMoves(b, Color.White) + ), Color.White) moves should contain(sq(File.A, Rank.R5) -> sq(File.E, Rank.R5)) // ──── gameStatus ──────────────────────────────────────────────────────