refactor(core): enhance castling logic to include rook movement and improve safety checks
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+13
-13
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]!!}")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user