From 89a96eaf52a7ef710a5c61130e4759325551a6da Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Tue, 9 Apr 2024 19:55:08 +0200 Subject: [PATCH] More bug fixing --- src/Chess/board.nim | 93 ++++++++++++++++++++++++++++----------------- 1 file changed, 58 insertions(+), 35 deletions(-) diff --git a/src/Chess/board.nim b/src/Chess/board.nim index 11242fe..05537e6 100644 --- a/src/Chess/board.nim +++ b/src/Chess/board.nim @@ -315,6 +315,8 @@ proc newChessboard: ChessBoard = func coordToIndex(row, col: int): int {.inline.} = (row * 8) + col func `[]`(self: seq[Piece], row, column: Natural): Piece {.inline.} = self[coordToIndex(row, column)] proc `[]=`(self: var seq[Piece], row, column: Natural, piece: Piece) {.inline.} = self[coordToIndex(row, column)] = piece +func `[]`(self: seq[Piece], loc: Location): Piece {.inline.} = self[loc.row, loc.col] +proc `[]=`(self: var seq[Piece], loc: Location, piece: Piece) {.inline.} = self[loc.row, loc.col] = piece proc newChessboardFromFEN*(fen: string): ChessBoard = @@ -717,6 +719,15 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king: of None: # Unreachable discard + + # We only need to check for attacked squares up until + # the point where the king arrives on the target castling + # square, but we _also_ need to make sure the path is free + # of obstacles for the rook to move past the king. This variable + # becomes false once the king has arrived on its target square so + # that we don't prevent castling when it would otherwise be allowed + # (for an example see r3k2r/p1pNqpb1/bn2pnp1/3P4/1p2P3/2N2Q1p/PPPBBPPP/R3K2R b KQkq - 0 1) + var checkAttacks = true if result.king: # Short castle @@ -734,12 +745,19 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king: if otherPiece.color != None: result.king = false break - if self.isAttacked(location, color.opposite()): + + if checkAttacks and self.isAttacked(location, color.opposite()): result.king = false break - + + # King has arrived at the target square: we no longer + # need to check whether subsequent squares are free from + # attacks + if location == shortCastleKing() + loc: + checkAttacks = false if result.queen: + checkAttacks = true # Long castle var location = loc @@ -755,10 +773,14 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king: if otherPiece.color != None: result.queen = false break - if self.isAttacked(location, color.opposite()): + + if checkAttacks and self.isAttacked(location, color.opposite()): result.queen = false break + if location == longCastleKing() + loc: + checkAttacks = false + proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] = ## Returns the squares that need to be covered to @@ -803,31 +825,32 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] = ## location var piece = self.grid[location.row, location.col] - targets: seq[Location] = @[] + directions: seq[Location] = @[] doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}" # Pawns can move forward one square - let forward = piece.color.topSide() + location + let forward = location + piece.color.topSide() # Only if the square is empty though - if forward.isValid() and self.grid[forward.row, forward.col].color == None: - targets.add(forward) + if forward.isValid() and self.grid[forward].color == None: + directions.add(piece.color.topSide()) # If the pawn is on its first rank, it can push two squares if location.row == piece.getStartRow(): let double = location + piece.color.doublePush() # Check that both squares are empty - if double.isValid() and self.grid[forward.row, forward.col].color == None and self.grid[double.row, double.col].color == None: - targets.add(double) + if double.isValid() and self.grid[forward].color == None and self.grid[double].color == None: + directions.add(piece.color.doublePush()) let enPassantPawn = self.getEnPassantTarget() + piece.color.opposite().topSide() # They can also move one square on either of their # forward diagonals, but only for captures and en passant - for diagonal in [location + piece.color.topRightDiagonal(), location + piece.color.topLeftDiagonal()]: - if diagonal.isValid(): - let otherPiece = self.grid[diagonal.row, diagonal.col] - if diagonal == self.position.enPassantSquare and self.grid[enPassantPawn.row, enPassantPawn.col].color == piece.color.opposite(): + for diagonal in [piece.color.topRightDiagonal(), piece.color.topLeftDiagonal()]: + let target = location + diagonal + if target.isValid(): + let otherPiece = self.grid[target] + if target == self.position.enPassantSquare and self.grid[enPassantPawn].color == piece.color.opposite(): # Ensure en passant doesn't create a check let king = self.getKing(piece.color) var ok = true if king.row == location.row: - var current = location + piece.color.rightSide() + var current = location while true: current = current + piece.color.rightSide() if not current.isValid(): @@ -837,28 +860,30 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] = break if p.color == None: continue - # Bishops can't create checks through en passant + # Bishops can't create checks through en passant (neither can knights) if p.color == piece.color.opposite() and p.kind in [Queen, Rook]: ok = false + break if ok: - targets.add(diagonal) - elif otherPiece.color == piece.color.opposite() and otherPiece.kind != King: - targets.add(diagonal) + directions.add(diagonal) + elif otherPiece.color == piece.color.opposite() and otherPiece.kind != King: # Can't capture the king! + directions.add(diagonal) # Check for pins let pinned = self.getPinnedDirections(location) if pinned.len() > 0: - var newTargets: seq[Location] = @[] - for target in targets: - if target in pinned: - newTargets.add(target) - targets = newTargets + var newDirections: seq[Location] = @[] + for direction in directions: + if direction in pinned: + newDirections.add(direction) + directions = newDirections let checked = self.inCheck() let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color) var targetPiece: Piece - for target in targets: + for direction in directions: + let target = location + direction if checked and target notin resolutions: continue - targetPiece = self.grid[target.row, target.col] + targetPiece = self.grid[target] var flags: uint16 = Default.uint16 if targetPiece.color != None: flags = flags or Capture.uint16 @@ -1557,6 +1582,7 @@ proc doMove(self: ChessBoard, move: Move) = self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color)) else: discard + self.updateAttackedSquares() if move.isCapture(): # Get rid of captured pieces @@ -2079,15 +2105,11 @@ proc handlePositionCommand(board: var ChessBoard, command: seq[string]) = echo board.pretty() -proc handleUCICommand(board: var ChessBoard, command: seq[string]): bool = - if len(command) != 1: - echo "error: uci: invalid number of arguments" - return false +proc handleUCICommand(board: var ChessBoard, command: seq[string]) = echo "id name Nimfish 0.1" echo "id author Nocturn9x & Contributors (see LICENSE)" # TODO echo "uciok" - return true const HELP_TEXT = """Nimfish help menu: @@ -2117,11 +2139,12 @@ const HELP_TEXT = """Nimfish help menu: - move : Perform the given move in algebraic notation - castle: Print castling rights for each side - check: Print if the current side to move is in check - - undo: Undoes the last move that was performed. Can be used in succession + - undo, u: Undoes the last move that was performed. Can be used in succession - turn: Print which side is to move - ep: Print the current en passant target - pretty: Shorthand for "position pretty" - print: Shorthand for "position print" + - fen: Shorthand for "position fen" (print only!) - get : Get the piece on the given square - uci: enter UCI mode (WIP) """ @@ -2148,8 +2171,8 @@ proc main: int = case cmd[0]: of "uci": - if handleUCICommand(board, cmd): - uciMode = true + handleUCICommand(board, cmd) + uciMode = true of "clear": echo "\x1Bc" of "help": @@ -2160,9 +2183,9 @@ proc main: int = handlePositionCommand(board, cmd) of "move": handleMoveCommand(board, cmd) - of "pretty", "print": + of "pretty", "print", "fen": handlePositionCommand(board, @["position", cmd[0]]) - of "undo": + of "undo", "u": board.undoLastMove() of "turn": echo &"Active color: {board.getActiveColor()}"