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 `[]`(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 <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 <square>: 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()}"