From 417a475d8498462c95de422e920a8825d5afb9ef Mon Sep 17 00:00:00 2001 From: LQ63 Date: Tue, 24 Mar 2026 12:55:00 +0100 Subject: [PATCH] feat: include castling moves in GameRules.legalMoves Switch legalMoves to the context-aware MoveValidator.legalTargets(ctx, from) so castling destinations are included, and simulate castle moves via withCastle when filtering for self-check. Co-Authored-By: Claude Sonnet 4.6 --- .../de/nowchess/chess/logic/GameRules.scala | 8 ++++-- .../nowchess/chess/logic/GameRulesTest.scala | 26 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 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 3622b91..97b7bb7 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 @@ -24,9 +24,13 @@ object GameRules: ctx.board.pieces .collect { case (from, piece) if piece.color == color => from } .flatMap { from => - MoveValidator.legalTargets(ctx.board, from) + MoveValidator.legalTargets(ctx, from) // context-aware: includes castling .filter { to => - val (newBoard, _) = ctx.board.withMove(from, to) + val newBoard = + if MoveValidator.isCastle(ctx.board, from, to) then + ctx.board.withCastle(color, MoveValidator.castleSide(from, to)) + else + ctx.board.withMove(from, to)._1 !isInCheck(newBoard, color) } .map(to => from -> to) 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 8af94e4..48f285b 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.api.game.CastlingRights import de.nowchess.chess.logic.GameContext import org.scalatest.funsuite.AnyFunSuite import org.scalatest.matchers.should.Matchers @@ -88,3 +89,28 @@ class GameRulesTest extends AnyFunSuite with Matchers: test("gameStatus: normal starting position returns Normal"): GameRules.gameStatus(Board.initial, Color.White) shouldBe PositionStatus.Normal + + test("legalMoves: includes castling destination when available"): + val c = GameContext( + board = board( + sq(File.E, Rank.R1) -> Piece.WhiteKing, + sq(File.H, Rank.R1) -> Piece.WhiteRook, + sq(File.H, Rank.R8) -> Piece.BlackKing + ), + whiteCastling = CastlingRights.Both, + blackCastling = CastlingRights.None + ) + GameRules.legalMoves(c, Color.White) should contain(sq(File.E, Rank.R1) -> sq(File.G, Rank.R1)) + + test("legalMoves: excludes castling when king is in check"): + val c = GameContext( + board = board( + sq(File.E, Rank.R1) -> Piece.WhiteKing, + sq(File.H, Rank.R1) -> Piece.WhiteRook, + sq(File.E, Rank.R8) -> Piece.BlackRook, + sq(File.A, Rank.R8) -> Piece.BlackKing + ), + whiteCastling = CastlingRights.Both, + blackCastling = CastlingRights.None + ) + GameRules.legalMoves(c, Color.White) should not contain (sq(File.E, Rank.R1) -> sq(File.G, Rank.R1))