refactor(core): NCS-22 update GameEngine to use GameContext and integrate Rule module
Build & Test (NowChessSystems) TeamCity build failed
Build & Test (NowChessSystems) TeamCity build failed
This commit is contained in:
@@ -31,3 +31,9 @@ trait RuleSet:
|
||||
|
||||
/** True if halfMoveClock >= 100 (50-move rule). */
|
||||
def isFiftyMoveRule(context: GameContext): Boolean
|
||||
|
||||
/** Apply a legal move to produce the next game context.
|
||||
* 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
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package de.nowchess.rules
|
||||
|
||||
import de.nowchess.api.game.GameContext
|
||||
import de.nowchess.api.board.{Board, Color, Square, PieceType, Piece}
|
||||
import de.nowchess.api.board.{Board, CastlingRights, Color, File, Rank, Square, PieceType, Piece}
|
||||
import de.nowchess.api.move.{Move, MoveType, PromotionPiece}
|
||||
import scala.annotation.tailrec
|
||||
|
||||
@@ -274,6 +274,95 @@ object StandardRules extends RuleSet:
|
||||
val nextContext = context.withBoard(nextBoard)
|
||||
isCheck(nextContext)
|
||||
|
||||
// ── Move application ───────────────────────────────────────────────
|
||||
|
||||
override def applyMove(context: GameContext, move: Move): GameContext =
|
||||
val color = context.turn
|
||||
val board = context.board
|
||||
|
||||
val newBoard = move.moveType match
|
||||
case MoveType.CastleKingside => applyCastle(board, color, kingside = true)
|
||||
case MoveType.CastleQueenside => applyCastle(board, color, kingside = false)
|
||||
case MoveType.EnPassant => applyEnPassant(board, move, color)
|
||||
case MoveType.Promotion(pp) => applyPromotion(board, move, color, pp)
|
||||
case MoveType.Normal => board.applyMove(move)
|
||||
|
||||
val newCastlingRights = updateCastlingRights(context.castlingRights, board, move, color)
|
||||
val newEnPassantSquare = computeEnPassantSquare(board, move, color)
|
||||
val isCapture = board.pieceAt(move.to).isDefined || move.moveType == MoveType.EnPassant
|
||||
val isPawnMove = board.pieceAt(move.from).exists(_.pieceType == PieceType.Pawn)
|
||||
val newClock = if isPawnMove || isCapture then 0 else context.halfMoveClock + 1
|
||||
|
||||
context
|
||||
.withBoard(newBoard)
|
||||
.withTurn(color.opposite)
|
||||
.withCastlingRights(newCastlingRights)
|
||||
.withEnPassantSquare(newEnPassantSquare)
|
||||
.withHalfMoveClock(newClock)
|
||||
.withMove(move)
|
||||
|
||||
private def applyCastle(board: Board, color: Color, kingside: Boolean): Board =
|
||||
val rank = if color == Color.White then Rank.R1 else Rank.R8
|
||||
val (kingFrom, kingTo, rookFrom, rookTo) =
|
||||
if kingside then
|
||||
(Square(File.E, rank), Square(File.G, rank), Square(File.H, rank), Square(File.F, rank))
|
||||
else
|
||||
(Square(File.E, rank), Square(File.C, rank), Square(File.A, rank), Square(File.D, rank))
|
||||
val king = board.pieceAt(kingFrom).getOrElse(Piece(color, PieceType.King))
|
||||
val rook = board.pieceAt(rookFrom).getOrElse(Piece(color, PieceType.Rook))
|
||||
board
|
||||
.removed(kingFrom).removed(rookFrom)
|
||||
.updated(kingTo, king)
|
||||
.updated(rookTo, rook)
|
||||
|
||||
private def applyEnPassant(board: Board, move: Move, color: Color): Board =
|
||||
val capturedRank = move.from.rank // the captured pawn is on the same rank as the moving pawn
|
||||
val capturedSquare = Square(move.to.file, capturedRank)
|
||||
board.applyMove(move).removed(capturedSquare)
|
||||
|
||||
private def applyPromotion(board: Board, move: Move, color: Color, pp: PromotionPiece): Board =
|
||||
val promotedType = pp match
|
||||
case PromotionPiece.Queen => PieceType.Queen
|
||||
case PromotionPiece.Rook => PieceType.Rook
|
||||
case PromotionPiece.Bishop => PieceType.Bishop
|
||||
case PromotionPiece.Knight => PieceType.Knight
|
||||
board.removed(move.from).updated(move.to, Piece(color, promotedType))
|
||||
|
||||
private def updateCastlingRights(rights: CastlingRights, board: Board, move: Move, color: Color): CastlingRights =
|
||||
val piece = board.pieceAt(move.from)
|
||||
val isKingMove = piece.exists(_.pieceType == PieceType.King)
|
||||
val isRookMove = piece.exists(_.pieceType == PieceType.Rook)
|
||||
|
||||
// Helper to check if a square is a rook's starting square
|
||||
val whiteKingsideRook = Square(File.H, Rank.R1)
|
||||
val whiteQueensideRook = Square(File.A, Rank.R1)
|
||||
val blackKingsideRook = Square(File.H, Rank.R8)
|
||||
val blackQueensideRook = Square(File.A, Rank.R8)
|
||||
|
||||
var r = rights
|
||||
if isKingMove then r = r.revokeColor(color)
|
||||
else if isRookMove then
|
||||
if move.from == whiteKingsideRook then r = r.revokeKingSide(Color.White)
|
||||
if move.from == whiteQueensideRook then r = r.revokeQueenSide(Color.White)
|
||||
if move.from == blackKingsideRook then r = r.revokeKingSide(Color.Black)
|
||||
if move.from == blackQueensideRook then r = r.revokeQueenSide(Color.Black)
|
||||
// Also revoke if a rook is captured
|
||||
if move.to == whiteKingsideRook then r = r.revokeKingSide(Color.White)
|
||||
if move.to == whiteQueensideRook then r = r.revokeQueenSide(Color.White)
|
||||
if move.to == blackKingsideRook then r = r.revokeKingSide(Color.Black)
|
||||
if move.to == blackQueensideRook then r = r.revokeQueenSide(Color.Black)
|
||||
r
|
||||
|
||||
private def computeEnPassantSquare(board: Board, move: Move, color: Color): Option[Square] =
|
||||
val piece = board.pieceAt(move.from)
|
||||
val isDoublePawnPush = piece.exists(_.pieceType == PieceType.Pawn) &&
|
||||
math.abs(move.to.rank.ordinal - move.from.rank.ordinal) == 2
|
||||
if isDoublePawnPush then
|
||||
// EP square is the square the pawn passed through
|
||||
val epRankOrd = (move.from.rank.ordinal + move.to.rank.ordinal) / 2
|
||||
Some(Square(move.from.file, Rank.values(epRankOrd)))
|
||||
else None
|
||||
|
||||
// ── Insufficient material ──────────────────────────────────────────
|
||||
|
||||
private def insufficientMaterial(board: Board): Boolean =
|
||||
|
||||
Reference in New Issue
Block a user