From 217f14f899458bc6b59af5210a655093a8f274dc Mon Sep 17 00:00:00 2001 From: Leon Hermann Date: Mon, 6 Apr 2026 21:03:17 +0200 Subject: [PATCH] refactor: NCS-19 Currying (#18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Reviewed-on: https://git.janis-eccarius.de/NowChess/NowChessSystems/pulls/18 Reviewed-by: Janis Co-authored-by: Leon Hermann Co-committed-by: Leon Hermann --- .../de/nowchess/chess/engine/GameEngine.scala | 6 +- .../engine/GameEngineIntegrationTest.scala | 12 ++-- .../engine/GameEnginePromotionTest.scala | 12 ++-- .../de/nowchess/io/pgn/PgnExporter.scala | 2 +- .../scala/de/nowchess/io/pgn/PgnParser.scala | 10 ++-- .../scala/de/nowchess/rules/RuleSet.scala | 6 +- .../de/nowchess/rules/sets/DefaultRules.scala | 10 ++-- .../DefaultRulesStateTransitionsTest.scala | 58 +++++++++---------- .../de/nowchess/ui/gui/ChessBoardView.scala | 2 +- 9 files changed, 59 insertions(+), 59 deletions(-) diff --git a/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala b/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala index 207a2a5..50d3772 100644 --- a/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala +++ b/modules/core/src/main/scala/de/nowchess/chess/engine/GameEngine.scala @@ -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( diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala index b77086c..599e2b6 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEngineIntegrationTest.scala @@ -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") diff --git a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala index 078d4f4..984b51e 100644 --- a/modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala +++ b/modules/core/src/test/scala/de/nowchess/chess/engine/GameEnginePromotionTest.scala @@ -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) diff --git a/modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala b/modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala index 5592caa..42ccb8e 100644 --- a/modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala +++ b/modules/io/src/main/scala/de/nowchess/io/pgn/PgnExporter.scala @@ -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 } diff --git a/modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala b/modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala index 1665ca6..1fd201b 100644 --- a/modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala +++ b/modules/io/src/main/scala/de/nowchess/io/pgn/PgnParser.scala @@ -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) diff --git a/modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala b/modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala index 35497ca..1386478 100644 --- a/modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala +++ b/modules/rule/src/main/scala/de/nowchess/rules/RuleSet.scala @@ -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 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 618c8c2..dc78274 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 @@ -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 diff --git a/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala b/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala index 0d122c0..5f06164 100644 --- a/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala +++ b/modules/rule/src/test/scala/de/nowchess/rule/DefaultRulesStateTransitionsTest.scala @@ -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)) diff --git a/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala b/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala index e46ac69..720ac1a 100644 --- a/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala +++ b/modules/ui/src/main/scala/de/nowchess/ui/gui/ChessBoardView.scala @@ -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)