Summary - Curried candidateMoves, legalMoves, and applyMove in the RuleSet trait to separate (context) as the world being operated on from the computation parameter - Updated DefaultRules overrides and all internal call sites - Updated all external call sites: GameEngine, PgnParser, PgnExporter, ChessBoardView, and all affected tests Test plan - All existing tests pass (./gradlew build) - No behaviour changes — pure style refactoring, existing test suite is the regression guard Co-authored-by: LQ63 <lkhermann@web.de> Reviewed-on: #18 Reviewed-by: Janis <janis-e@gmx.de> Co-authored-by: Leon Hermann <lq@blackhole.local> Co-committed-by: Leon Hermann <lq@blackhole.local>
This commit was merged in pull request #18.
This commit is contained in:
@@ -88,7 +88,7 @@ class GameEngine(
|
||||
case Some(piece) if piece.color != currentContext.turn =>
|
||||
notifyObservers(InvalidMoveEvent(currentContext, "That is not your piece."))
|
||||
case Some(piece) =>
|
||||
val legal = ruleSet.legalMoves(currentContext, from)
|
||||
val legal = ruleSet.legalMoves(currentContext)(from)
|
||||
// Find all legal moves going to `to`
|
||||
val candidates = legal.filter(_.to == to)
|
||||
candidates match
|
||||
@@ -119,7 +119,7 @@ class GameEngine(
|
||||
pendingPromotion = None
|
||||
val move = Move(pending.from, pending.to, MoveType.Promotion(piece))
|
||||
// Verify it's actually legal
|
||||
val legal = ruleSet.legalMoves(currentContext, pending.from)
|
||||
val legal = ruleSet.legalMoves(currentContext)(pending.from)
|
||||
if legal.contains(move) then
|
||||
executeMove(move)
|
||||
else
|
||||
@@ -203,7 +203,7 @@ class GameEngine(
|
||||
|
||||
private def executeMove(move: Move): Unit =
|
||||
val contextBefore = currentContext
|
||||
val nextContext = ruleSet.applyMove(currentContext, move)
|
||||
val nextContext = ruleSet.applyMove(currentContext)(move)
|
||||
val captured = computeCaptured(currentContext, move)
|
||||
|
||||
val cmd = MoveCommand(
|
||||
|
||||
+6
-6
@@ -89,8 +89,8 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers:
|
||||
val promotionMove = Move(sq("e2"), sq("e8"), MoveType.Promotion(PromotionPiece.Queen))
|
||||
|
||||
val permissiveRules = new RuleSet:
|
||||
def candidateMoves(context: GameContext, square: Square): List[Move] = legalMoves(context, square)
|
||||
def legalMoves(context: GameContext, square: Square): List[Move] =
|
||||
def candidateMoves(context: GameContext)(square: Square): List[Move] = legalMoves(context)(square)
|
||||
def legalMoves(context: GameContext)(square: Square): List[Move] =
|
||||
if square == sq("e2") then List(promotionMove) else List.empty
|
||||
def allLegalMoves(context: GameContext): List[Move] = List(promotionMove)
|
||||
def isCheck(context: GameContext): Boolean = false
|
||||
@@ -98,7 +98,7 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers:
|
||||
def isStalemate(context: GameContext): Boolean = false
|
||||
def isInsufficientMaterial(context: GameContext): Boolean = false
|
||||
def isFiftyMoveRule(context: GameContext): Boolean = false
|
||||
def applyMove(context: GameContext, move: Move): GameContext = DefaultRules.applyMove(context, move)
|
||||
def applyMove(context: GameContext)(move: Move): GameContext = DefaultRules.applyMove(context)(move)
|
||||
|
||||
val engine = new GameEngine(ruleSet = permissiveRules)
|
||||
val importer = new GameContextImport:
|
||||
@@ -111,15 +111,15 @@ class GameEngineIntegrationTest extends AnyFunSuite with Matchers:
|
||||
test("loadGame replay restores previous context when promotion cannot be completed"):
|
||||
val promotionMove = Move(sq("e2"), sq("e8"), MoveType.Promotion(PromotionPiece.Queen))
|
||||
val noLegalMoves = new RuleSet:
|
||||
def candidateMoves(context: GameContext, square: Square): List[Move] = List.empty
|
||||
def legalMoves(context: GameContext, square: Square): List[Move] = List.empty
|
||||
def candidateMoves(context: GameContext)(square: Square): List[Move] = List.empty
|
||||
def legalMoves(context: GameContext)(square: Square): List[Move] = List.empty
|
||||
def allLegalMoves(context: GameContext): List[Move] = List.empty
|
||||
def isCheck(context: GameContext): Boolean = false
|
||||
def isCheckmate(context: GameContext): Boolean = false
|
||||
def isStalemate(context: GameContext): Boolean = false
|
||||
def isInsufficientMaterial(context: GameContext): Boolean = false
|
||||
def isFiftyMoveRule(context: GameContext): Boolean = false
|
||||
def applyMove(context: GameContext, move: Move): GameContext = context
|
||||
def applyMove(context: GameContext)(move: Move): GameContext = context
|
||||
|
||||
val engine = new GameEngine(ruleSet = noLegalMoves)
|
||||
engine.processUserInput("e2e4")
|
||||
|
||||
@@ -153,10 +153,10 @@ class GameEnginePromotionTest extends AnyFunSuite with Matchers:
|
||||
// This makes completePromotion unable to find Move(from, to, Promotion(Queen)),
|
||||
// triggering the "Error completing promotion." branch.
|
||||
val delegatingRuleSet: RuleSet = new RuleSet:
|
||||
def candidateMoves(context: GameContext, square: Square): List[Move] =
|
||||
DefaultRules.candidateMoves(context, square)
|
||||
def legalMoves(context: GameContext, square: Square): List[Move] =
|
||||
DefaultRules.legalMoves(context, square).map { m =>
|
||||
def candidateMoves(context: GameContext)(square: Square): List[Move] =
|
||||
DefaultRules.candidateMoves(context)(square)
|
||||
def legalMoves(context: GameContext)(square: Square): List[Move] =
|
||||
DefaultRules.legalMoves(context)(square).map { m =>
|
||||
m.moveType match
|
||||
case MoveType.Promotion(_) => Move(m.from, m.to, MoveType.Normal())
|
||||
case _ => m
|
||||
@@ -173,8 +173,8 @@ class GameEnginePromotionTest extends AnyFunSuite with Matchers:
|
||||
DefaultRules.isInsufficientMaterial(context)
|
||||
def isFiftyMoveRule(context: GameContext): Boolean =
|
||||
DefaultRules.isFiftyMoveRule(context)
|
||||
def applyMove(context: GameContext, move: Move): GameContext =
|
||||
DefaultRules.applyMove(context, move)
|
||||
def applyMove(context: GameContext)(move: Move): GameContext =
|
||||
DefaultRules.applyMove(context)(move)
|
||||
|
||||
val promotionBoard = FenParser.parseBoard("8/4P3/4k3/8/8/8/8/8").get
|
||||
val initialCtx = GameContext.initial.withBoard(promotionBoard).withTurn(Color.White)
|
||||
|
||||
@@ -30,7 +30,7 @@ object PgnExporter extends GameContextExport:
|
||||
var ctx = GameContext.initial
|
||||
val sanMoves = moves.map { move =>
|
||||
val algebraic = moveToAlgebraic(move, ctx.board)
|
||||
ctx = DefaultRules.applyMove(ctx, move)
|
||||
ctx = DefaultRules.applyMove(ctx)(move)
|
||||
algebraic
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ object PgnParser extends GameContextImport:
|
||||
* Returns Left(error message) if validation fails or move replay encounters an issue. */
|
||||
def importGameContext(input: String): Either[String, GameContext] =
|
||||
validatePgn(input).flatMap { game =>
|
||||
Right(game.moves.foldLeft(GameContext.initial)(DefaultRules.applyMove))
|
||||
Right(game.moves.foldLeft(GameContext.initial)((ctx, move) => DefaultRules.applyMove(ctx)(move)))
|
||||
}
|
||||
|
||||
/** Parse a complete PGN text into a PgnGame with headers and moves.
|
||||
@@ -59,7 +59,7 @@ object PgnParser extends GameContextImport:
|
||||
parseAlgebraicMove(token, ctx, color) match
|
||||
case None => state
|
||||
case Some(move) =>
|
||||
val nextCtx = DefaultRules.applyMove(ctx, move)
|
||||
val nextCtx = DefaultRules.applyMove(ctx)(move)
|
||||
(nextCtx, color.opposite, acc :+ move)
|
||||
moves
|
||||
|
||||
@@ -77,12 +77,12 @@ object PgnParser extends GameContextImport:
|
||||
case "O-O" | "O-O+" | "O-O#" =>
|
||||
val rank = if color == Color.White then Rank.R1 else Rank.R8
|
||||
val move = Move(Square(File.E, rank), Square(File.G, rank), MoveType.CastleKingside)
|
||||
Option.when(DefaultRules.legalMoves(ctx, Square(File.E, rank)).contains(move))(move)
|
||||
Option.when(DefaultRules.legalMoves(ctx)(Square(File.E, rank)).contains(move))(move)
|
||||
|
||||
case "O-O-O" | "O-O-O+" | "O-O-O#" =>
|
||||
val rank = if color == Color.White then Rank.R1 else Rank.R8
|
||||
val move = Move(Square(File.E, rank), Square(File.C, rank), MoveType.CastleQueenside)
|
||||
Option.when(DefaultRules.legalMoves(ctx, Square(File.E, rank)).contains(move))(move)
|
||||
Option.when(DefaultRules.legalMoves(ctx)(Square(File.E, rank)).contains(move))(move)
|
||||
|
||||
case _ =>
|
||||
parseRegularMove(notation, ctx, color)
|
||||
@@ -176,7 +176,7 @@ object PgnParser extends GameContextImport:
|
||||
parseAlgebraicMove(token, ctx, color) match
|
||||
case None => Left(s"Illegal or impossible move: '$token'")
|
||||
case Some(move) =>
|
||||
val nextCtx = DefaultRules.applyMove(ctx, move)
|
||||
val nextCtx = DefaultRules.applyMove(ctx)(move)
|
||||
Right((nextCtx, color.opposite, moves :+ move))
|
||||
}
|
||||
}.map(_._3)
|
||||
|
||||
@@ -9,10 +9,10 @@ import de.nowchess.api.move.Move
|
||||
*/
|
||||
trait RuleSet:
|
||||
/** All pseudo-legal moves for the piece on `square` (ignores check). */
|
||||
def candidateMoves(context: GameContext, square: Square): List[Move]
|
||||
def candidateMoves(context: GameContext)(square: Square): List[Move]
|
||||
|
||||
/** Legal moves for `square`: candidates that don't leave own king in check. */
|
||||
def legalMoves(context: GameContext, square: Square): List[Move]
|
||||
def legalMoves(context: GameContext)(square: Square): List[Move]
|
||||
|
||||
/** All legal moves for the side to move. */
|
||||
def allLegalMoves(context: GameContext): List[Move]
|
||||
@@ -36,4 +36,4 @@ trait RuleSet:
|
||||
* Handles all special move types: castling, en passant, promotion.
|
||||
* Updates castling rights, en passant square, half-move clock, turn, and move history.
|
||||
*/
|
||||
def applyMove(context: GameContext, move: Move): GameContext
|
||||
def applyMove(context: GameContext)(move: Move): GameContext
|
||||
|
||||
@@ -26,7 +26,7 @@ object DefaultRules extends RuleSet:
|
||||
|
||||
// ── Public API ─────────────────────────────────────────────────────
|
||||
|
||||
override def candidateMoves(context: GameContext, square: Square): List[Move] =
|
||||
override def candidateMoves(context: GameContext)(square: Square): List[Move] =
|
||||
context.board.pieceAt(square).fold(List.empty[Move]) { piece =>
|
||||
if piece.color != context.turn then List.empty[Move]
|
||||
else piece.pieceType match
|
||||
@@ -38,13 +38,13 @@ object DefaultRules extends RuleSet:
|
||||
case PieceType.King => kingCandidates(context, square, piece.color)
|
||||
}
|
||||
|
||||
override def legalMoves(context: GameContext, square: Square): List[Move] =
|
||||
candidateMoves(context, square).filter { move =>
|
||||
override def legalMoves(context: GameContext)(square: Square): List[Move] =
|
||||
candidateMoves(context)(square).filter { move =>
|
||||
!leavesKingInCheck(context, move)
|
||||
}
|
||||
|
||||
override def allLegalMoves(context: GameContext): List[Move] =
|
||||
Square.all.flatMap(sq => legalMoves(context, sq)).toList
|
||||
Square.all.flatMap(sq => legalMoves(context)(sq)).toList
|
||||
|
||||
override def isCheck(context: GameContext): Boolean =
|
||||
kingSquare(context.board, context.turn)
|
||||
@@ -284,7 +284,7 @@ object DefaultRules extends RuleSet:
|
||||
|
||||
// ── Move application ───────────────────────────────────────────────
|
||||
|
||||
override def applyMove(context: GameContext, move: Move): GameContext =
|
||||
override def applyMove(context: GameContext)(move: Move): GameContext =
|
||||
val color = context.turn
|
||||
val board = context.board
|
||||
|
||||
|
||||
+29
-29
@@ -52,14 +52,14 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
|
||||
test("applyMove toggles turn and records move"):
|
||||
val move = Move(sq("e2"), sq("e4"))
|
||||
val next = DefaultRules.applyMove(GameContext.initial, move)
|
||||
val next = DefaultRules.applyMove(GameContext.initial)(move)
|
||||
|
||||
next.turn shouldBe Color.Black
|
||||
next.moves.lastOption shouldBe Some(move)
|
||||
|
||||
test("applyMove sets en passant square after double pawn push"):
|
||||
val move = Move(sq("e2"), sq("e4"))
|
||||
val next = DefaultRules.applyMove(GameContext.initial, move)
|
||||
val next = DefaultRules.applyMove(GameContext.initial)(move)
|
||||
|
||||
next.enPassantSquare shouldBe Some(sq("e3"))
|
||||
|
||||
@@ -67,7 +67,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("4k3/8/8/8/8/8/4P3/4K3 w - d6 3 1")
|
||||
val move = Move(sq("e2"), sq("e3"))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.enPassantSquare shouldBe None
|
||||
|
||||
@@ -75,7 +75,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("4k3/8/8/8/8/8/4P3/4K3 w - - 12 1")
|
||||
val move = Move(sq("e2"), sq("e4"))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.halfMoveClock shouldBe 0
|
||||
|
||||
@@ -83,7 +83,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("4k3/8/8/8/8/8/8/4K1N1 w - - 7 1")
|
||||
val move = Move(sq("g1"), sq("f3"))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.halfMoveClock shouldBe 8
|
||||
|
||||
@@ -91,7 +91,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("r3k3/8/8/8/8/8/8/R3K3 w Qq - 9 1")
|
||||
val move = Move(sq("a1"), sq("a8"), MoveType.Normal(isCapture = true))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.halfMoveClock shouldBe 0
|
||||
next.board.pieceAt(sq("a8")) shouldBe Some(Piece(Color.White, PieceType.Rook))
|
||||
@@ -100,7 +100,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1")
|
||||
val move = Move(sq("e1"), sq("e2"))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.castlingRights.whiteKingSide shouldBe false
|
||||
next.castlingRights.whiteQueenSide shouldBe false
|
||||
@@ -111,7 +111,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("r3k2r/8/8/8/8/8/8/4K2R w KQkq - 0 1")
|
||||
val move = Move(sq("h1"), sq("h2"))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.castlingRights.whiteKingSide shouldBe false
|
||||
next.castlingRights.whiteQueenSide shouldBe true
|
||||
@@ -120,7 +120,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("r3k3/8/8/8/8/8/8/R3K3 w Qq - 2 1")
|
||||
val move = Move(sq("a1"), sq("a8"), MoveType.Normal(isCapture = true))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.castlingRights.blackQueenSide shouldBe false
|
||||
|
||||
@@ -128,7 +128,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("4k2r/8/8/8/8/8/8/R3K2R w KQk - 0 1")
|
||||
val move = Move(sq("e1"), sq("g1"), MoveType.CastleKingside)
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.board.pieceAt(sq("g1")) shouldBe Some(Piece(Color.White, PieceType.King))
|
||||
next.board.pieceAt(sq("f1")) shouldBe Some(Piece(Color.White, PieceType.Rook))
|
||||
@@ -139,7 +139,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("r3k3/8/8/8/8/8/8/R3K2R w KQq - 0 1")
|
||||
val move = Move(sq("e1"), sq("c1"), MoveType.CastleQueenside)
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.board.pieceAt(sq("c1")) shouldBe Some(Piece(Color.White, PieceType.King))
|
||||
next.board.pieceAt(sq("d1")) shouldBe Some(Piece(Color.White, PieceType.Rook))
|
||||
@@ -150,7 +150,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("k7/8/8/3pP3/8/8/8/7K w - d6 0 1")
|
||||
val move = Move(sq("e5"), sq("d6"), MoveType.EnPassant)
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.board.pieceAt(sq("d6")) shouldBe Some(Piece(Color.White, PieceType.Pawn))
|
||||
next.board.pieceAt(sq("d5")) shouldBe None
|
||||
@@ -160,7 +160,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("4k3/P7/8/8/8/8/8/4K3 w - - 0 1")
|
||||
val move = Move(sq("a7"), sq("a8"), MoveType.Promotion(PromotionPiece.Knight))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.board.pieceAt(sq("a8")) shouldBe Some(Piece(Color.White, PieceType.Knight))
|
||||
next.board.pieceAt(sq("a7")) shouldBe None
|
||||
@@ -168,12 +168,12 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
test("candidateMoves returns empty for opponent piece on selected square"):
|
||||
val context = GameContext.initial.withTurn(Color.Black)
|
||||
|
||||
DefaultRules.candidateMoves(context, sq("e2")) shouldBe empty
|
||||
DefaultRules.candidateMoves(context)(sq("e2")) shouldBe empty
|
||||
|
||||
test("legalMoves keeps king safe by filtering pinned bishop moves"):
|
||||
val context = contextFromFen("8/8/8/8/8/8/r1B1K3/8 w - - 0 1")
|
||||
|
||||
val bishopMoves = DefaultRules.legalMoves(context, sq("c2"))
|
||||
val bishopMoves = DefaultRules.legalMoves(context)(sq("c2"))
|
||||
|
||||
bishopMoves shouldBe empty
|
||||
|
||||
@@ -181,7 +181,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("r3k2r/8/8/8/8/8/8/R3K2R w KQkq - 0 1")
|
||||
val move = Move(sq("e1"), sq("g1"), MoveType.CastleKingside)
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.castlingRights.whiteKingSide shouldBe false
|
||||
next.castlingRights.whiteQueenSide shouldBe false
|
||||
@@ -198,8 +198,8 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
moves = List.empty
|
||||
)
|
||||
|
||||
val afterA1Capture = DefaultRules.applyMove(context, Move(sq("a8"), sq("a1"), MoveType.Normal(isCapture = true)))
|
||||
val afterH1Capture = DefaultRules.applyMove(afterA1Capture, Move(sq("a1"), sq("h1"), MoveType.Normal(isCapture = true)))
|
||||
val afterA1Capture = DefaultRules.applyMove(context)(Move(sq("a8"), sq("a1"), MoveType.Normal(isCapture = true)))
|
||||
val afterH1Capture = DefaultRules.applyMove(afterA1Capture)(Move(sq("a1"), sq("h1"), MoveType.Normal(isCapture = true)))
|
||||
|
||||
afterH1Capture.castlingRights.whiteKingSide shouldBe false
|
||||
afterH1Capture.castlingRights.whiteQueenSide shouldBe false
|
||||
@@ -212,21 +212,21 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
test("candidateMoves for rook includes enemy capture move"):
|
||||
val context = contextFromFen("4k3/8/8/8/8/8/4K3/R6r w - - 0 1")
|
||||
|
||||
val rookMoves = DefaultRules.candidateMoves(context, sq("a1"))
|
||||
val rookMoves = DefaultRules.candidateMoves(context)(sq("a1"))
|
||||
|
||||
rookMoves.exists(m => m.to == sq("h1") && m.moveType == MoveType.Normal(isCapture = true)) shouldBe true
|
||||
|
||||
test("candidateMoves for knight includes enemy capture move"):
|
||||
val context = contextFromFen("4k3/8/8/8/8/3p4/5N2/4K3 w - - 0 1")
|
||||
|
||||
val knightMoves = DefaultRules.candidateMoves(context, sq("f2"))
|
||||
val knightMoves = DefaultRules.candidateMoves(context)(sq("f2"))
|
||||
|
||||
knightMoves.exists(m => m.to == sq("d3") && m.moveType == MoveType.Normal(isCapture = true)) shouldBe true
|
||||
|
||||
test("candidateMoves includes black kingside and queenside castling options"):
|
||||
val context = contextFromFen("r3k2r/8/8/8/8/8/8/4K3 b kq - 0 1")
|
||||
|
||||
val kingMoves = DefaultRules.candidateMoves(context, sq("e8"))
|
||||
val kingMoves = DefaultRules.candidateMoves(context)(sq("e8"))
|
||||
|
||||
kingMoves.exists(_.moveType == MoveType.CastleKingside) shouldBe true
|
||||
kingMoves.exists(_.moveType == MoveType.CastleQueenside) shouldBe true
|
||||
@@ -235,7 +235,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("r3k2r/8/8/8/8/8/8/4K3 b kq - 0 1")
|
||||
val move = Move(sq("e8"), sq("g8"), MoveType.CastleKingside)
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.board.pieceAt(sq("g8")) shouldBe Some(Piece(Color.Black, PieceType.King))
|
||||
next.board.pieceAt(sq("f8")) shouldBe Some(Piece(Color.Black, PieceType.Rook))
|
||||
@@ -246,7 +246,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("r3k2r/8/8/8/8/8/8/4K3 b kq - 0 1")
|
||||
val move = Move(sq("h8"), sq("h7"))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.castlingRights.blackKingSide shouldBe false
|
||||
next.castlingRights.blackQueenSide shouldBe true
|
||||
@@ -255,7 +255,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("r3k2r/8/8/8/8/8/8/4K3 b kq - 0 1")
|
||||
val move = Move(sq("a8"), sq("a7"))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.castlingRights.blackKingSide shouldBe true
|
||||
next.castlingRights.blackQueenSide shouldBe false
|
||||
@@ -264,7 +264,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("4k2r/8/8/8/8/8/8/4K2R w Kk - 0 1")
|
||||
val move = Move(sq("h1"), sq("h8"), MoveType.Normal(isCapture = true))
|
||||
|
||||
val next = DefaultRules.applyMove(context, move)
|
||||
val next = DefaultRules.applyMove(context)(move)
|
||||
|
||||
next.castlingRights.blackKingSide shouldBe false
|
||||
|
||||
@@ -272,7 +272,7 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
val context = contextFromFen("4k3/8/8/8/8/8/p7/4K3 b - - 0 1")
|
||||
val to = sq("a1")
|
||||
|
||||
val pawnMoves = DefaultRules.candidateMoves(context, sq("a2"))
|
||||
val pawnMoves = DefaultRules.candidateMoves(context)(sq("a2"))
|
||||
val promotions = pawnMoves.collect { case Move(_, `to`, MoveType.Promotion(piece)) => piece }
|
||||
|
||||
promotions.toSet shouldBe Set(
|
||||
@@ -285,9 +285,9 @@ class DefaultRulesStateTransitionsTest extends AnyFunSuite with Matchers:
|
||||
test("applyMove promotion supports queen rook and bishop targets"):
|
||||
val base = contextFromFen("4k3/P7/8/8/8/8/8/4K3 w - - 0 1")
|
||||
|
||||
val queen = DefaultRules.applyMove(base, Move(sq("a7"), sq("a8"), MoveType.Promotion(PromotionPiece.Queen)))
|
||||
val rook = DefaultRules.applyMove(base, Move(sq("a7"), sq("a8"), MoveType.Promotion(PromotionPiece.Rook)))
|
||||
val bishop = DefaultRules.applyMove(base, Move(sq("a7"), sq("a8"), MoveType.Promotion(PromotionPiece.Bishop)))
|
||||
val queen = DefaultRules.applyMove(base)(Move(sq("a7"), sq("a8"), MoveType.Promotion(PromotionPiece.Queen)))
|
||||
val rook = DefaultRules.applyMove(base)(Move(sq("a7"), sq("a8"), MoveType.Promotion(PromotionPiece.Rook)))
|
||||
val bishop = DefaultRules.applyMove(base)(Move(sq("a7"), sq("a8"), MoveType.Promotion(PromotionPiece.Bishop)))
|
||||
|
||||
queen.board.pieceAt(sq("a8")) shouldBe Some(Piece(Color.White, PieceType.Queen))
|
||||
rook.board.pieceAt(sq("a8")) shouldBe Some(Piece(Color.White, PieceType.Rook))
|
||||
|
||||
@@ -178,7 +178,7 @@ class ChessBoardView(val stage: Stage, private val engine: GameEngine) extends B
|
||||
selectedSquare = Some(clickedSquare)
|
||||
highlightSquare(rank, file, PieceSprites.SquareColors.Selected)
|
||||
|
||||
val legalDests = engine.ruleSet.legalMoves(engine.context, clickedSquare)
|
||||
val legalDests = engine.ruleSet.legalMoves(engine.context)(clickedSquare)
|
||||
.collect { case move if move.from == clickedSquare => move.to }
|
||||
legalDests.foreach { sq =>
|
||||
highlightSquare(sq.rank.ordinal, sq.file.ordinal, PieceSprites.SquareColors.ValidMove)
|
||||
|
||||
Reference in New Issue
Block a user