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
|
||||
|
||||
Reference in New Issue
Block a user