More bug fixing

This commit is contained in:
Mattia Giambirtone 2024-04-09 19:55:08 +02:00
parent f65d426ccf
commit 89a96eaf52
1 changed files with 58 additions and 35 deletions

View File

@ -315,6 +315,8 @@ proc newChessboard: ChessBoard =
func coordToIndex(row, col: int): int {.inline.} = (row * 8) + col func coordToIndex(row, col: int): int {.inline.} = (row * 8) + col
func `[]`(self: seq[Piece], row, column: Natural): Piece {.inline.} = self[coordToIndex(row, column)] 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 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 = proc newChessboardFromFEN*(fen: string): ChessBoard =
@ -717,6 +719,15 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
of None: of None:
# Unreachable # Unreachable
discard 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: if result.king:
# Short castle # Short castle
@ -734,12 +745,19 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
if otherPiece.color != None: if otherPiece.color != None:
result.king = false result.king = false
break break
if self.isAttacked(location, color.opposite()):
if checkAttacks and self.isAttacked(location, color.opposite()):
result.king = false result.king = false
break 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: if result.queen:
checkAttacks = true
# Long castle # Long castle
var var
location = loc location = loc
@ -755,10 +773,14 @@ proc canCastle*(self: ChessBoard, color: PieceColor = None): tuple[queen, king:
if otherPiece.color != None: if otherPiece.color != None:
result.queen = false result.queen = false
break break
if self.isAttacked(location, color.opposite()):
if checkAttacks and self.isAttacked(location, color.opposite()):
result.queen = false result.queen = false
break break
if location == longCastleKing() + loc:
checkAttacks = false
proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] = proc getCheckResolutions(self: ChessBoard, color: PieceColor): seq[Location] =
## Returns the squares that need to be covered to ## Returns the squares that need to be covered to
@ -803,31 +825,32 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
## location ## location
var var
piece = self.grid[location.row, location.col] piece = self.grid[location.row, location.col]
targets: seq[Location] = @[] directions: seq[Location] = @[]
doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}" doAssert piece.kind == Pawn, &"generatePawnMoves called on a {piece.kind}"
# Pawns can move forward one square # 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 # Only if the square is empty though
if forward.isValid() and self.grid[forward.row, forward.col].color == None: if forward.isValid() and self.grid[forward].color == None:
targets.add(forward) directions.add(piece.color.topSide())
# If the pawn is on its first rank, it can push two squares # If the pawn is on its first rank, it can push two squares
if location.row == piece.getStartRow(): if location.row == piece.getStartRow():
let double = location + piece.color.doublePush() let double = location + piece.color.doublePush()
# Check that both squares are empty # 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: if double.isValid() and self.grid[forward].color == None and self.grid[double].color == None:
targets.add(double) directions.add(piece.color.doublePush())
let enPassantPawn = self.getEnPassantTarget() + piece.color.opposite().topSide() let enPassantPawn = self.getEnPassantTarget() + piece.color.opposite().topSide()
# They can also move one square on either of their # They can also move one square on either of their
# forward diagonals, but only for captures and en passant # forward diagonals, but only for captures and en passant
for diagonal in [location + piece.color.topRightDiagonal(), location + piece.color.topLeftDiagonal()]: for diagonal in [piece.color.topRightDiagonal(), piece.color.topLeftDiagonal()]:
if diagonal.isValid(): let target = location + diagonal
let otherPiece = self.grid[diagonal.row, diagonal.col] if target.isValid():
if diagonal == self.position.enPassantSquare and self.grid[enPassantPawn.row, enPassantPawn.col].color == piece.color.opposite(): 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 # Ensure en passant doesn't create a check
let king = self.getKing(piece.color) let king = self.getKing(piece.color)
var ok = true var ok = true
if king.row == location.row: if king.row == location.row:
var current = location + piece.color.rightSide() var current = location
while true: while true:
current = current + piece.color.rightSide() current = current + piece.color.rightSide()
if not current.isValid(): if not current.isValid():
@ -837,28 +860,30 @@ proc generatePawnMoves(self: ChessBoard, location: Location): seq[Move] =
break break
if p.color == None: if p.color == None:
continue 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]: if p.color == piece.color.opposite() and p.kind in [Queen, Rook]:
ok = false ok = false
break
if ok: if ok:
targets.add(diagonal) directions.add(diagonal)
elif otherPiece.color == piece.color.opposite() and otherPiece.kind != King: elif otherPiece.color == piece.color.opposite() and otherPiece.kind != King: # Can't capture the king!
targets.add(diagonal) directions.add(diagonal)
# Check for pins # Check for pins
let pinned = self.getPinnedDirections(location) let pinned = self.getPinnedDirections(location)
if pinned.len() > 0: if pinned.len() > 0:
var newTargets: seq[Location] = @[] var newDirections: seq[Location] = @[]
for target in targets: for direction in directions:
if target in pinned: if direction in pinned:
newTargets.add(target) newDirections.add(direction)
targets = newTargets directions = newDirections
let checked = self.inCheck() let checked = self.inCheck()
let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color) let resolutions = if not checked: @[] else: self.getCheckResolutions(piece.color)
var targetPiece: Piece var targetPiece: Piece
for target in targets: for direction in directions:
let target = location + direction
if checked and target notin resolutions: if checked and target notin resolutions:
continue continue
targetPiece = self.grid[target.row, target.col] targetPiece = self.grid[target]
var flags: uint16 = Default.uint16 var flags: uint16 = Default.uint16
if targetPiece.color != None: if targetPiece.color != None:
flags = flags or Capture.uint16 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)) self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
else: else:
discard discard
self.updateAttackedSquares()
if move.isCapture(): if move.isCapture():
# Get rid of captured pieces # Get rid of captured pieces
@ -2079,15 +2105,11 @@ proc handlePositionCommand(board: var ChessBoard, command: seq[string]) =
echo board.pretty() echo board.pretty()
proc handleUCICommand(board: var ChessBoard, command: seq[string]): bool = proc handleUCICommand(board: var ChessBoard, command: seq[string]) =
if len(command) != 1:
echo "error: uci: invalid number of arguments"
return false
echo "id name Nimfish 0.1" echo "id name Nimfish 0.1"
echo "id author Nocturn9x & Contributors (see LICENSE)" echo "id author Nocturn9x & Contributors (see LICENSE)"
# TODO # TODO
echo "uciok" echo "uciok"
return true
const HELP_TEXT = """Nimfish help menu: const HELP_TEXT = """Nimfish help menu:
@ -2117,11 +2139,12 @@ const HELP_TEXT = """Nimfish help menu:
- move <move>: Perform the given move in algebraic notation - move <move>: Perform the given move in algebraic notation
- castle: Print castling rights for each side - castle: Print castling rights for each side
- check: Print if the current side to move is in check - 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 - turn: Print which side is to move
- ep: Print the current en passant target - ep: Print the current en passant target
- pretty: Shorthand for "position pretty" - pretty: Shorthand for "position pretty"
- print: Shorthand for "position print" - print: Shorthand for "position print"
- fen: Shorthand for "position fen" (print only!)
- get <square>: Get the piece on the given square - get <square>: Get the piece on the given square
- uci: enter UCI mode (WIP) - uci: enter UCI mode (WIP)
""" """
@ -2148,8 +2171,8 @@ proc main: int =
case cmd[0]: case cmd[0]:
of "uci": of "uci":
if handleUCICommand(board, cmd): handleUCICommand(board, cmd)
uciMode = true uciMode = true
of "clear": of "clear":
echo "\x1Bc" echo "\x1Bc"
of "help": of "help":
@ -2160,9 +2183,9 @@ proc main: int =
handlePositionCommand(board, cmd) handlePositionCommand(board, cmd)
of "move": of "move":
handleMoveCommand(board, cmd) handleMoveCommand(board, cmd)
of "pretty", "print": of "pretty", "print", "fen":
handlePositionCommand(board, @["position", cmd[0]]) handlePositionCommand(board, @["position", cmd[0]])
of "undo": of "undo", "u":
board.undoLastMove() board.undoLastMove()
of "turn": of "turn":
echo &"Active color: {board.getActiveColor()}" echo &"Active color: {board.getActiveColor()}"