From 4cf39e3e9744bf58401c65625e2db211e151369f Mon Sep 17 00:00:00 2001 From: Janis Date: Sun, 5 Apr 2026 19:17:23 +0200 Subject: [PATCH] refactor(core): enhance castling logic to include rook movement and improve safety checks --- .../chess/engine/GameEngineOutcomesTest.scala | 121 ++++++++---------- .../chess/engine/GameEngineScenarioTest.scala | 4 +- .../engine/GameEngineSpecialMovesTest.scala | 26 ++-- .../de/nowchess/io/pgn/PgnParserTest.scala | 4 +- modules/rule/build.gradle.kts | 1 + .../de/nowchess/rules/sets/DefaultRules.scala | 23 +++- .../de/nowchess/rule/DefaultRulesTest.scala | 38 +++--- 7 files changed, 111 insertions(+), 106 deletions(-) diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineOutcomesTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineOutcomesTest.scala index 8db7aae..ce628c7 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineOutcomesTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineOutcomesTest.scala @@ -14,37 +14,30 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers: val observer = new EngineTestHelpers.MockObserver() engine.subscribe(observer) - // Fool's Mate position (after 2 moves: 1. f3 e5 2. g4 Qh5#) - // FEN after moves but before final checkmate move - EngineTestHelpers.loadFen(engine, "rnbqkbnr/pppp1ppp/8/4p2Q/6P1/5P2/PPPPP2P/RNB1KB1R b KQkq - 0 2") + engine.processUserInput("f2f3") + engine.processUserInput("e7e5") + engine.processUserInput("g2g4") observer.clear() - // Black queen to h5 is checkmate - engine.processUserInput("d8h4") // or the actual final move + engine.processUserInput("d8h4") - val hasCheckmate = observer.hasEvent[CheckmateEvent] - if !hasCheckmate then - // If not quite checkmate, try a different position - val engine2 = EngineTestHelpers.makeEngine() - val observer2 = new EngineTestHelpers.MockObserver() - engine2.subscribe(observer2) - // Simplest checkmate: king in corner vs queen and king - EngineTestHelpers.loadFen(engine2, "k7/8/8/8/8/8/8/K6Q w - - 0 1") - observer2.clear() - engine2.processUserInput("h1h8") - observer2.hasEvent[CheckmateEvent] shouldBe true + observer.hasEvent[CheckmateEvent] shouldBe true - test("checkmate with black winner"): + test("checkmate with white winner"): val engine = EngineTestHelpers.makeEngine() val observer = new EngineTestHelpers.MockObserver() engine.subscribe(observer) - // FEN: Scholar's mate position (white king checkmated by black) - // After: 1. e4 e5 2. Bc4 Nc6 3. Qh5 Nf6 4. Qxf7# - EngineTestHelpers.loadFen(engine, "r1bqkb1r/pppp1Qpp/2n2n2/4p3/2B1P3/8/PPPP1PPP/RNB1K1NR b KQkq - 0 4") + engine.processUserInput("e2e4") + engine.processUserInput("e7e5") + engine.processUserInput("f1c4") + engine.processUserInput("b8c6") + engine.processUserInput("d1h5") + engine.processUserInput("g8f6") observer.clear() - // Black is already checkmated here; verify the event + engine.processUserInput("h5f7") + val evt = observer.getEvent[CheckmateEvent] evt.isDefined shouldBe true evt.get.winner shouldBe Color.White @@ -56,26 +49,46 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers: val observer = new EngineTestHelpers.MockObserver() engine.subscribe(observer) - // FEN: black king h8, white king f6, white queen g7 (stalemate) - EngineTestHelpers.loadFen(engine, "7k/6Q1/5K2/8/8/8/8/8 b - - 0 1") + val moves = List( + "e2e3", "a7a5", + "d1h5", "a8a6", + "h5a5", "h7h5", + "h2h4", "a6h6", + "a5c7", "f7f6", + "c7d7", "e8f7", + "d7b7", "d8d3", + "b7b8", "d3h7", + "b8c8", "f7g6" + ) + moves.foreach(engine.processUserInput) observer.clear() - // Black to move but has no legal moves and is not in check - // This should trigger stalemate detection on the next move attempt - val hasStalemate = observer.hasEvent[StalemateEvent] - hasStalemate shouldBe true + engine.processUserInput("c8e6") + + observer.hasEvent[StalemateEvent] shouldBe true test("stalemate when king has no moves and no pieces"): val engine = EngineTestHelpers.makeEngine() val observer = new EngineTestHelpers.MockObserver() engine.subscribe(observer) - // FEN: king on a8, white king on b7, white queen on a7 (stalemate) - EngineTestHelpers.loadFen(engine, "k7/KQ6/8/8/8/8/8/8 b - - 0 1") - observer.clear() + val moves = List( + "e2e3", "a7a5", + "d1h5", "a8a6", + "h5a5", "h7h5", + "h2h4", "a6h6", + "a5c7", "f7f6", + "c7d7", "e8f7", + "d7b7", "d8d3", + "b7b8", "d3h7", + "b8c8", "f7g6", + "c8e6" + ) - val hasStalemate = observer.hasEvent[StalemateEvent] - hasStalemate shouldBe true + moves.foreach(engine.processUserInput) + + observer.hasEvent[StalemateEvent] shouldBe true + engine.turn shouldBe Color.White // ── Check detection ──────────────────────────────────────────── @@ -84,11 +97,13 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers: val observer = new EngineTestHelpers.MockObserver() engine.subscribe(observer) - // FEN: white rook e4, black king e8, empty between - EngineTestHelpers.loadFen(engine, "4k3/8/8/8/4R3/8/8/8 w - - 0 1") + engine.processUserInput("e2e4") + engine.processUserInput("e7e5") + engine.processUserInput("f1c4") + engine.processUserInput("g8f6") observer.clear() - engine.processUserInput("e4e8") // rook gives check + engine.processUserInput("c4f7") observer.hasEvent[CheckDetectedEvent] shouldBe true @@ -97,35 +112,11 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers: val observer = new EngineTestHelpers.MockObserver() engine.subscribe(observer) - // FEN: white knight on d4, black king on f5 - EngineTestHelpers.loadFen(engine, "8/8/5k2/8/3N4/8/8/8 w - - 0 1") + EngineTestHelpers.loadFen(engine, "8/4k3/8/8/3N4/8/8/4K3 w - - 0 1") observer.clear() - engine.processUserInput("d4e6") // knight gives check to king on f5... actually no - // Let me use correct knight move: d4 to f5 gives check to king - engine.processUserInput("d4f3") // this won't give check, wrong position + engine.processUserInput("d4f5") - // Better: set up a position where knight move does give check - val engine2 = EngineTestHelpers.makeEngine() - val observer2 = new EngineTestHelpers.MockObserver() - engine2.subscribe(observer2) - - EngineTestHelpers.loadFen(engine2, "8/8/8/8/3N4/5k2/8/8 w - - 0 1") - observer2.clear() - - engine2.processUserInput("d4f3") // actually d4 to f3 isn't a knight move, let me fix - - // Use correct knight moves - val engine3 = EngineTestHelpers.makeEngine() - val observer3 = new EngineTestHelpers.MockObserver() - engine3.subscribe(observer3) - - EngineTestHelpers.loadFen(engine3, "8/8/4k3/8/3N4/8/8/8 w - - 0 1") - observer3.clear() - - // Knight from d4 can go to: c6, e6, f5, f3, e2, c2, b3, b5 - // King is on e6, so Ne6 won't work (occupied), but Nf5 or other moves won't give check - // Let me just verify queen check works observer.hasEvent[CheckDetectedEvent] shouldBe true // ── Fifty-move rule ──────────────────────────────────────────── @@ -138,7 +129,7 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers: EngineTestHelpers.loadFen(engine, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 99 1") observer.clear() - engine.processUserInput("a2a3") + engine.processUserInput("g1f3") observer.hasEvent[FiftyMoveRuleAvailableEvent] shouldBe true @@ -154,9 +145,9 @@ class GameEngineOutcomesTest extends AnyFunSuite with Matchers: test("fifty-move rule clock resets on capture"): val engine = EngineTestHelpers.makeEngine() - // FEN: white pawn on e5, black pawn on d4, clock at 50 - EngineTestHelpers.loadFen(engine, "8/8/8/4P3/3p4/8/8/8 w - - 50 1") - engine.processUserInput("e5d4") // capture + // FEN: white pawn on e5, black pawn on d6, clock at 50 + EngineTestHelpers.loadFen(engine, "4k3/8/3p4/4P3/8/8/8/4K3 w - - 50 1") + engine.processUserInput("e5d6") // Clock should reset to 0 after capture engine.context.halfMoveClock shouldBe 0 diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala index 5536ab4..f5565a7 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineScenarioTest.scala @@ -154,8 +154,8 @@ class GameEngineScenarioTest extends AnyFunSuite with Matchers: EngineTestHelpers.loadFen(engine, "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 99 1") observer.clear() - // Make a pawn move (non-capture, non-pawn-move would reset clock, but we're testing the event) - engine.processUserInput("a2a3") + // Use a legal non-pawn non-capture move so the clock increments to 100. + engine.processUserInput("g1f3") observer.hasEvent[FiftyMoveRuleAvailableEvent] shouldBe true diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineSpecialMovesTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineSpecialMovesTest.scala index d5ad5fb..f74ce87 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineSpecialMovesTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineSpecialMovesTest.scala @@ -43,7 +43,7 @@ class GameEngineSpecialMovesTest extends AnyFunSuite with Matchers: val observer = new EngineTestHelpers.MockObserver() engine.subscribe(observer) - EngineTestHelpers.loadFen(engine, "k7/8/8/8/8/8/8/R3K3 w q - 0 1") + EngineTestHelpers.loadFen(engine, "k7/8/8/8/8/8/8/R3K3 w Q - 0 1") observer.clear() engine.processUserInput("e1c1") @@ -105,7 +105,7 @@ class GameEngineSpecialMovesTest extends AnyFunSuite with Matchers: test("completePromotion to Queen executes move"): val engine = EngineTestHelpers.makeEngine() - EngineTestHelpers.loadFen(engine, "8/4P3/4k3/8/8/8/8/8 w - - 0 1") + EngineTestHelpers.loadFen(engine, "8/4P3/8/8/8/8/k7/8 w - - 0 1") engine.processUserInput("e7e8") engine.completePromotion(PromotionPiece.Queen) @@ -115,7 +115,7 @@ class GameEngineSpecialMovesTest extends AnyFunSuite with Matchers: test("completePromotion to Rook executes move"): val engine = EngineTestHelpers.makeEngine() - EngineTestHelpers.loadFen(engine, "8/4P3/4k3/8/8/8/8/8 w - - 0 1") + EngineTestHelpers.loadFen(engine, "8/4P3/8/8/8/8/k7/8 w - - 0 1") engine.processUserInput("e7e8") engine.completePromotion(PromotionPiece.Rook) @@ -125,7 +125,7 @@ class GameEngineSpecialMovesTest extends AnyFunSuite with Matchers: test("completePromotion to Bishop executes move"): val engine = EngineTestHelpers.makeEngine() - EngineTestHelpers.loadFen(engine, "8/4P3/4k3/8/8/8/8/8 w - - 0 1") + EngineTestHelpers.loadFen(engine, "8/4P3/8/8/8/8/k7/8 w - - 0 1") engine.processUserInput("e7e8") engine.completePromotion(PromotionPiece.Bishop) @@ -135,7 +135,7 @@ class GameEngineSpecialMovesTest extends AnyFunSuite with Matchers: test("completePromotion to Knight executes move"): val engine = EngineTestHelpers.makeEngine() - EngineTestHelpers.loadFen(engine, "8/4P3/4k3/8/8/8/8/8 w - - 0 1") + EngineTestHelpers.loadFen(engine, "8/4P3/8/8/8/8/k7/8 w - - 0 1") engine.processUserInput("e7e8") engine.completePromotion(PromotionPiece.Knight) @@ -147,8 +147,8 @@ class GameEngineSpecialMovesTest extends AnyFunSuite with Matchers: val observer = new EngineTestHelpers.MockObserver() engine.subscribe(observer) - // FEN: white pawn e7, white bishop b4, black king d5 - EngineTestHelpers.loadFen(engine, "8/4P3/8/3k4/1B6/8/8/8 w - - 0 1") + // FEN: white pawn e7, black king e6, white king e1 + EngineTestHelpers.loadFen(engine, "8/4P3/4k3/8/8/8/8/4K3 w - - 0 1") observer.clear() engine.processUserInput("e7e8") @@ -156,16 +156,16 @@ class GameEngineSpecialMovesTest extends AnyFunSuite with Matchers: observer.hasEvent[CheckDetectedEvent] shouldBe true - test("promotion to Rook with checkmate emits CheckmateEvent"): + test("promotion to Queen with checkmate emits CheckmateEvent"): val engine = EngineTestHelpers.makeEngine() val observer = new EngineTestHelpers.MockObserver() engine.subscribe(observer) - // FEN: white pawn e7, white queen d6, black king f8 (trapped) - EngineTestHelpers.loadFen(engine, "5k2/4P3/3Q4/8/8/8/8/8 w - - 0 1") + // FEN: known promotion-mate pattern + EngineTestHelpers.loadFen(engine, "k7/7P/1K6/8/8/8/8/8 w - - 0 1") observer.clear() - engine.processUserInput("e7e8") + engine.processUserInput("h7h8") engine.completePromotion(PromotionPiece.Queen) observer.hasEvent[CheckmateEvent] shouldBe true @@ -193,7 +193,7 @@ class GameEngineSpecialMovesTest extends AnyFunSuite with Matchers: engine.processUserInput("e2e1") engine.isPendingPromotion shouldBe true - engine.processUserInput("q") // complete with Queen + engine.completePromotion(PromotionPiece.Queen) engine.isPendingPromotion shouldBe false engine.turn shouldBe Color.White @@ -203,7 +203,7 @@ class GameEngineSpecialMovesTest extends AnyFunSuite with Matchers: test("pawn promotion with capture executes"): val engine = EngineTestHelpers.makeEngine() - EngineTestHelpers.loadFen(engine, "1n6/4P3/4k3/8/8/8/8/8 w - - 0 1") + EngineTestHelpers.loadFen(engine, "3n4/4P3/4k3/8/8/8/8/4K3 w - - 0 1") engine.processUserInput("e7d8") engine.isPendingPromotion shouldBe true diff --git a/modules/io/src/test/scala/de/nowchess/io/pgn/PgnParserTest.scala b/modules/io/src/test/scala/de/nowchess/io/pgn/PgnParserTest.scala index 41bae9a..00b4ffc 100644 --- a/modules/io/src/test/scala/de/nowchess/io/pgn/PgnParserTest.scala +++ b/modules/io/src/test/scala/de/nowchess/io/pgn/PgnParserTest.scala @@ -50,7 +50,7 @@ class PgnParserTest extends AnyFunSuite with Matchers: test("parse queenside castling O-O-O"): val pgn = """[Event "Test"] -1. d4 d5 2. Nc3 Nc6 3. Bf4 Bf5 4. O-O-O""" +1. d4 d5 2. Nc3 Nc6 3. Bf4 Bf5 4. Qd2 Qd7 5. O-O-O""" val game = PgnParser.parsePgn(pgn) game.isDefined shouldBe true val lastMove = game.get.moves.last @@ -71,7 +71,7 @@ class PgnParserTest extends AnyFunSuite with Matchers: test("parse black queenside castling"): val pgn = """[Event "Test"] -1. d4 d5 2. Nc3 Nc6 3. Bf4 Bf5 4. O-O-O O-O-O""" +1. d4 d5 2. Nc3 Nc6 3. Bf4 Bf5 4. Qd2 Qd7 5. O-O-O O-O-O""" val game = PgnParser.parsePgn(pgn) game.isDefined shouldBe true val lastMove = game.get.moves.last diff --git a/modules/rule/build.gradle.kts b/modules/rule/build.gradle.kts index c006936..07a8017 100644 --- a/modules/rule/build.gradle.kts +++ b/modules/rule/build.gradle.kts @@ -40,6 +40,7 @@ dependencies { implementation(project(":modules:api")) + testImplementation(project(":modules:io")) testImplementation(platform("org.junit:junit-bom:5.13.4")) testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.scalatest:scalatest_3:${versions["SCALATEST"]!!}") diff --git a/modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala b/modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala index d439c22..a8bd367 100644 --- a/modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala +++ b/modules/rule/src/main/scala/de/nowchess/rules/sets/DefaultRules.scala @@ -139,9 +139,9 @@ object DefaultRules extends RuleSet: else val moves = scala.collection.mutable.ListBuffer[Move]() addCastleMove(context, moves, context.castlingRights.whiteKingSide, - "e1", "g1", "f1", MoveType.CastleKingside) + "e1", "g1", "f1", "h1", MoveType.CastleKingside) addCastleMove(context, moves, context.castlingRights.whiteQueenSide, - "e1", "c1", "d1", MoveType.CastleQueenside) + "e1", "c1", "d1", "a1", MoveType.CastleQueenside) moves.toList private def blackCastles(context: GameContext, from: Square): List[Move] = @@ -150,9 +150,9 @@ object DefaultRules extends RuleSet: else val moves = scala.collection.mutable.ListBuffer[Move]() addCastleMove(context, moves, context.castlingRights.blackKingSide, - "e8", "g8", "f8", MoveType.CastleKingside) + "e8", "g8", "f8", "h8", MoveType.CastleKingside) addCastleMove(context, moves, context.castlingRights.blackQueenSide, - "e8", "c8", "d8", MoveType.CastleQueenside) + "e8", "c8", "d8", "a8", MoveType.CastleQueenside) moves.toList private def addCastleMove( @@ -162,6 +162,7 @@ object DefaultRules extends RuleSet: kingFromAlg: String, kingToAlg: String, middleAlg: String, + rookFromAlg: String, moveType: MoveType ): Unit = if castlingRight then @@ -169,8 +170,20 @@ object DefaultRules extends RuleSet: if squaresEmpty(context.board, clearSqs) then for kf <- Square.fromAlgebraic(kingFromAlg) + km <- Square.fromAlgebraic(middleAlg) kt <- Square.fromAlgebraic(kingToAlg) - do moves += Move(kf, kt, moveType) + rf <- Square.fromAlgebraic(rookFromAlg) + do + val color = context.turn + val kingPresent = context.board.pieceAt(kf).exists(p => p.color == color && p.pieceType == PieceType.King) + val rookPresent = context.board.pieceAt(rf).exists(p => p.color == color && p.pieceType == PieceType.Rook) + val squaresSafe = + !isAttackedBy(context.board, kf, color.opposite) && + !isAttackedBy(context.board, km, color.opposite) && + !isAttackedBy(context.board, kt, color.opposite) + + if kingPresent && rookPresent && squaresSafe then + moves += Move(kf, kt, moveType) private def squaresEmpty(board: Board, squares: List[Square]): Boolean = squares.forall(sq => board.pieceAt(sq).isEmpty) diff --git a/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala b/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala index 995db06..93d6e8d 100644 --- a/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala +++ b/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesTest.scala @@ -10,20 +10,20 @@ import org.scalatest.matchers.should.Matchers class DefaultRulesTest extends AnyFunSuite with Matchers: - private val rules = DefaultRules() + private val rules = DefaultRules // ── Pawn moves ────────────────────────────────────────────────── test("pawn can move forward one square"): val fen = "8/8/8/8/8/8/4P3/8 w - - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val pawnMoves = moves.filter(m => m.from == Square(File.E, Rank.R2)) pawnMoves.exists(m => m.to == Square(File.E, Rank.R3)) shouldBe true test("pawn can move forward two squares from starting position"): val context = GameContext.initial - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val e2Moves = moves.filter(m => m.from == Square(File.E, Rank.R2)) e2Moves.exists(m => m.to == Square(File.E, Rank.R4)) shouldBe true @@ -31,7 +31,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: // FEN: white pawn e4, black pawn d5 val fen = "8/8/8/3p4/4P3/8/8/8 w - - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val captures = moves.filter(m => m.from == Square(File.E, Rank.R4) && m.moveType.isInstanceOf[MoveType.Normal]) captures.exists(m => m.to == Square(File.D, Rank.R5)) shouldBe true @@ -39,7 +39,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: // FEN: white pawn on e4 val fen = "8/8/8/8/4P3/8/8/8 w - - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val pawnMoves = moves.filter(m => m.from == Square(File.E, Rank.R4)) pawnMoves.exists(m => m.to == Square(File.E, Rank.R3)) shouldBe false @@ -49,16 +49,16 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: // FEN: white king e1, black rook e8, white tries to move away val fen = "4r3/8/8/8/8/8/8/4K3 w - - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) // King must move; e2 should be valid but d1 might be blocked by rook if still on same file moves.filter(m => m.from == Square(File.E, Rank.R1)).nonEmpty shouldBe true test("king cannot move to square attacked by opponent"): - // FEN: white king e1, black rook on e2 - val fen = "8/8/8/8/8/8/4r3/4K3 w - - 0 1" + // FEN: white king e1, black rook e2 defended by black king e3 + val fen = "8/8/8/8/8/4k3/4r3/4K3 w - - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) // King cannot move to e2 (occupied and attacked) val kingMovesToE2 = moves.filter(m => m.from == Square(File.E, Rank.R1) && m.to == Square(File.E, Rank.R2)) @@ -69,7 +69,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: test("castling kingside is legal when king and rook unmoved and path clear"): val fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK2R w KQkq - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val castles = moves.filter(m => m.moveType == MoveType.CastleKingside) castles.nonEmpty shouldBe true @@ -77,7 +77,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: test("castling queenside is legal when king and rook unmoved and path clear"): val fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/R3K2R w KQkq - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val castles = moves.filter(m => m.moveType == MoveType.CastleQueenside) castles.nonEmpty shouldBe true @@ -86,16 +86,16 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: // FEN: king and rook in position, but castling rights disabled val fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQK2R w - - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val castles = moves.filter(m => m.moveType == MoveType.CastleKingside) castles.isEmpty shouldBe true test("castling is illegal when king is in check"): // FEN: white king e1 in check from black rook e8 - val fen = "4r3/8/8/8/8/8/PPPPPPPP/RNBQK2R w KQkq - 0 1" + val fen = "4r3/8/8/8/8/8/8/R3K2R w KQ - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val castles = moves.filter(m => m.moveType == MoveType.CastleKingside || m.moveType == MoveType.CastleQueenside) castles.isEmpty shouldBe true @@ -104,7 +104,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: // FEN: white king e1, white rook h1, white bishop f1 (blocks f-file) val fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBR1 w KQkq - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val castles = moves.filter(m => m.moveType == MoveType.CastleKingside) castles.isEmpty shouldBe true @@ -115,7 +115,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: // FEN: white pawn e5, black pawn d5 (just double-pushed), en passant square d6 val fen = "k7/8/8/3pP3/8/8/8/7K w - d6 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val epMoves = moves.filter(m => m.moveType == MoveType.EnPassant) epMoves.exists(m => m.to == Square(File.D, Rank.R6)) shouldBe true @@ -124,7 +124,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: // FEN: white pawn e5, black pawn d5, but no en passant square val fen = "k7/8/8/3pP3/8/8/8/7K w - - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) val epMoves = moves.filter(m => m.moveType == MoveType.EnPassant) epMoves.isEmpty shouldBe true @@ -135,7 +135,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: // FEN: white king e1, white bishop d2 (pinned), black rook a2 val fen = "8/8/8/8/8/8/r1B1K3/8 w - - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) // Bishop on d2 is pinned by rook on a2; it cannot move val bishopMoves = moves.filter(m => m.from == Square(File.C, Rank.R2)) @@ -146,7 +146,7 @@ class DefaultRulesTest extends AnyFunSuite with Matchers: // Actually, this is complex. Let's use: white king e1, black rook e8, white pawn blocks on e2 val fen = "4r3/8/8/8/8/8/4P3/4K3 w - - 0 1" val context = FenParser.parseFen(fen).fold(_ => fail(), identity) - val moves = rules.generateMoves(context) + val moves = rules.allLegalMoves(context) // White is in check; only moves that block or move the king are legal moves.nonEmpty shouldBe true