Bug fixes to checks, pins and more. Reworking pawn movegen
This commit is contained in:
parent
d5bcd15c48
commit
fe987576c3
|
@ -37,7 +37,7 @@ type
|
||||||
# The current position
|
# The current position
|
||||||
position*: Position
|
position*: Position
|
||||||
# List of all previously reached positions
|
# List of all previously reached positions
|
||||||
positions: seq[Position]
|
positions*: seq[Position]
|
||||||
|
|
||||||
|
|
||||||
# A bunch of simple utility functions and forward declarations
|
# A bunch of simple utility functions and forward declarations
|
||||||
|
@ -53,7 +53,7 @@ proc removePiece(self: Chessboard, square: Square)
|
||||||
proc update*(self: Chessboard)
|
proc update*(self: Chessboard)
|
||||||
func inCheck*(self: Chessboard): bool {.inline.}
|
func inCheck*(self: Chessboard): bool {.inline.}
|
||||||
proc fromChar*(c: char): Piece
|
proc fromChar*(c: char): Piece
|
||||||
|
proc updateChecksAndPins*(self: Chessboard)
|
||||||
|
|
||||||
|
|
||||||
func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
||||||
|
@ -148,13 +148,13 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||||
of '-':
|
of '-':
|
||||||
discard
|
discard
|
||||||
of 'K':
|
of 'K':
|
||||||
discard
|
result.position.castlingAvailability.white.king = true
|
||||||
of 'Q':
|
of 'Q':
|
||||||
discard
|
result.position.castlingAvailability.white.queen = true
|
||||||
of 'k':
|
of 'k':
|
||||||
discard
|
result.position.castlingAvailability.black.king = true
|
||||||
of 'q':
|
of 'q':
|
||||||
discard
|
result.position.castlingAvailability.black.queen = true
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||||
of 3:
|
of 3:
|
||||||
|
@ -187,6 +187,7 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||||
inc(index)
|
inc(index)
|
||||||
|
result.updateChecksAndPins()
|
||||||
|
|
||||||
|
|
||||||
proc newDefaultChessboard*: Chessboard {.inline.} =
|
proc newDefaultChessboard*: Chessboard {.inline.} =
|
||||||
|
@ -233,8 +234,7 @@ func getOccupancy(self: Chessboard): Bitboard {.inline.} =
|
||||||
|
|
||||||
|
|
||||||
func getPawnAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
func getPawnAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||||
## Returns the attack bitboard for the given square from
|
## Returns the locations of the pawns attacking the given square
|
||||||
## the pawns of the given side
|
|
||||||
let
|
let
|
||||||
sq = square.toBitboard()
|
sq = square.toBitboard()
|
||||||
pawns = self.getBitboard(Pawn, attacker)
|
pawns = self.getBitboard(Pawn, attacker)
|
||||||
|
@ -244,8 +244,7 @@ func getPawnAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bit
|
||||||
|
|
||||||
|
|
||||||
func getKingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
func getKingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||||
## Returns the attack bitboard for the given square from
|
## Returns the location of the king if it is attacking the given square
|
||||||
## the king of the given side
|
|
||||||
result = Bitboard(0)
|
result = Bitboard(0)
|
||||||
let
|
let
|
||||||
king = self.getBitboard(King, attacker)
|
king = self.getBitboard(King, attacker)
|
||||||
|
@ -254,8 +253,7 @@ func getKingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bit
|
||||||
|
|
||||||
|
|
||||||
func getKnightAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
func getKnightAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||||
## Returns the attack bitboard for the given square from
|
## Returns the locations of the knights attacking the given square
|
||||||
## the knights of the given side
|
|
||||||
let
|
let
|
||||||
knights = self.getBitboard(Knight, attacker)
|
knights = self.getBitboard(Knight, attacker)
|
||||||
result = Bitboard(0)
|
result = Bitboard(0)
|
||||||
|
@ -266,24 +264,27 @@ func getKnightAttacks(self: Chessboard, square: Square, attacker: PieceColor): B
|
||||||
|
|
||||||
|
|
||||||
proc getSlidingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
proc getSlidingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||||
## Returns the attack bitboard for the given square from
|
## Returns the locations of the sliding pieces attacking the given square
|
||||||
## the sliding pieces of the given side
|
|
||||||
let
|
let
|
||||||
queens = self.getBitboard(Queen, attacker)
|
queens = self.getBitboard(Queen, attacker)
|
||||||
rooks = self.getBitboard(Rook, attacker) or queens
|
rooks = self.getBitboard(Rook, attacker) or queens
|
||||||
bishops = self.getBitboard(Bishop, attacker) or queens
|
bishops = self.getBitboard(Bishop, attacker) or queens
|
||||||
|
occupancy = self.getOccupancy()
|
||||||
|
squareBB = square.toBitboard()
|
||||||
result = Bitboard(0)
|
result = Bitboard(0)
|
||||||
for rook in rooks:
|
for rook in rooks:
|
||||||
let blockers = Rook.getRelevantBlockers(square)
|
let
|
||||||
let rookBB = rook.toBitboard()
|
blockers = occupancy and Rook.getRelevantBlockers(rook)
|
||||||
if (getRookMoves(square, blockers) and rookBB) != 0:
|
moves = getRookMoves(rook, blockers)
|
||||||
result = result or rookBB
|
# Attack set intersects our chosen square
|
||||||
|
if (moves and squareBB) != 0:
|
||||||
|
result = result or rook.toBitboard()
|
||||||
for bishop in bishops:
|
for bishop in bishops:
|
||||||
let
|
let
|
||||||
blockers = Bishop.getRelevantBlockers(square)
|
blockers = occupancy and Bishop.getRelevantBlockers(bishop)
|
||||||
bishopBB = bishop.toBitboard()
|
moves = getBishopMoves(bishop, blockers)
|
||||||
if (getBishopMoves(square, blockers) and bishopBB) != 0:
|
if (moves and squareBB) != 0:
|
||||||
result = result or bishopBB
|
result = result or bishop.toBitboard()
|
||||||
|
|
||||||
|
|
||||||
proc getAttacksTo*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
proc getAttacksTo*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||||
|
@ -296,7 +297,53 @@ proc getAttacksTo*(self: Chessboard, square: Square, attacker: PieceColor): Bitb
|
||||||
result = result or self.getSlidingAttacks(square, attacker)
|
result = result or self.getSlidingAttacks(square, attacker)
|
||||||
|
|
||||||
|
|
||||||
proc updateChecksAndPins(self: Chessboard) =
|
proc isOccupancyAttacked*(self: Chessboard, square: Square, occupancy: Bitboard): bool =
|
||||||
|
## Returns whether the given square would be attacked by the
|
||||||
|
## enemy side if the board had the given occupancy. This function
|
||||||
|
## is necessary mostly to make sure sliding attacks can check the
|
||||||
|
## king properly: due to how we generate our attack bitboards, if
|
||||||
|
## the king moved backwards along a ray from a slider we would not
|
||||||
|
## consider it to be in check (because the ray stops at the first
|
||||||
|
## blocker). In order to fix that, in generateKingMoves() we use this
|
||||||
|
## function and pass in the board's occupancy without the moving king so
|
||||||
|
## that we can pick the correct magic bitboard and ray. Also, since this
|
||||||
|
## function doesn't need to generate all the attacks to know whether a
|
||||||
|
## given square is unsafe, it can short circuit at the first attack and
|
||||||
|
## exit early, unlike getAttacksTo
|
||||||
|
let
|
||||||
|
sideToMove = self.position.sideToMove
|
||||||
|
nonSideToMove = sideToMove.opposite()
|
||||||
|
knights = self.getBitboard(Knight, nonSideToMove)
|
||||||
|
|
||||||
|
# Let's do the cheap ones first (the ones which are precomputed)
|
||||||
|
if (getKnightAttacks(square) and knights) != 0:
|
||||||
|
return true
|
||||||
|
|
||||||
|
let king = self.getBitboard(King, nonSideToMove)
|
||||||
|
|
||||||
|
if (getKingAttacks(square) and king) != 0:
|
||||||
|
return true
|
||||||
|
|
||||||
|
let
|
||||||
|
queens = self.getBitboard(Queen, nonSideToMove)
|
||||||
|
bishops = self.getBitboard(Bishop, nonSideToMove) or queens
|
||||||
|
|
||||||
|
if (getBishopMoves(square, occupancy) and bishops) != 0:
|
||||||
|
return true
|
||||||
|
|
||||||
|
let rooks = self.getBitboard(Rook, nonSideToMove) or queens
|
||||||
|
|
||||||
|
if (getRookMoves(square, occupancy) and rooks) != 0:
|
||||||
|
return true
|
||||||
|
|
||||||
|
# TODO: Precompute pawn moves as well?
|
||||||
|
let pawns = self.getBitboard(Pawn, nonSideToMove)
|
||||||
|
|
||||||
|
if (self.getPawnAttacks(square, nonSideToMove) and pawns) != 0:
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
proc updateChecksAndPins*(self: Chessboard) =
|
||||||
## Updates internal metadata about checks and
|
## Updates internal metadata about checks and
|
||||||
## pinned pieces
|
## pinned pieces
|
||||||
|
|
||||||
|
@ -305,8 +352,7 @@ proc updateChecksAndPins(self: Chessboard) =
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
friendlyKingBB = self.getBitboard(King, sideToMove)
|
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
|
||||||
friendlyKing = friendlyKingBB.toSquare()
|
|
||||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||||
|
|
||||||
|
@ -325,14 +371,15 @@ proc updateChecksAndPins(self: Chessboard) =
|
||||||
for piece in canPinDiagonally:
|
for piece in canPinDiagonally:
|
||||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||||
|
|
||||||
if (pinningRay and friendlyKingBB).countSquares() > 0:
|
# Is the pinning ray obstructed by any of our friendly pieces? If so, the
|
||||||
|
# piece is pinned
|
||||||
|
if (pinningRay and friendlyPieces).countSquares() > 0:
|
||||||
self.position.diagonalPins = self.position.diagonalPins or pinningRay
|
self.position.diagonalPins = self.position.diagonalPins or pinningRay
|
||||||
|
|
||||||
for piece in canPinOrthogonally:
|
for piece in canPinOrthogonally:
|
||||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||||
|
if (pinningRay and friendlyPieces).countSquares() > 0:
|
||||||
if (pinningRay and friendlyKingBB).countSquares() > 0:
|
self.position.orthogonalPins = self.position.orthogonalPins or pinningRay
|
||||||
self.position.diagonalPins = self.position.diagonalPins or pinningRay
|
|
||||||
|
|
||||||
|
|
||||||
func inCheck(self: Chessboard): bool {.inline.} =
|
func inCheck(self: Chessboard): bool {.inline.} =
|
||||||
|
@ -502,85 +549,49 @@ proc unmakeMove*(self: Chessboard) =
|
||||||
self.update()
|
self.update()
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnMovements(self: Chessboard, moves: var MoveList) =
|
proc generatePawnMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
||||||
## Helper of generatePawnMoves for generating all non-capture
|
|
||||||
## and non-promotion pawn moves
|
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
pawns = self.getBitboard(Pawn, sideToMove)
|
pawns = self.getBitboard(Pawn, sideToMove)
|
||||||
# We can only move to squares that are *not* occupied by another piece.
|
|
||||||
# We also cannot move to the last rank, as that will result in a promotion
|
|
||||||
# and is handled elsewhere
|
|
||||||
allowedSquares = not (self.getOccupancy() or sideToMove.getLastRank())
|
|
||||||
# Single push
|
|
||||||
for square in pawns.forwardRelativeTo(sideToMove) and allowedSquares:
|
|
||||||
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square))
|
|
||||||
# Double push
|
|
||||||
let rank = if sideToMove == White: getRankMask(6) else: getRankMask(1) # Only pawns on their starting rank can double push
|
|
||||||
for square in (pawns and rank).doubleForwardRelativeTo(sideToMove) and allowedSquares:
|
|
||||||
moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush))
|
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnCaptures(self: Chessboard, moves: var MoveList) =
|
|
||||||
## Helper of generatePawnMoves for generating all capturing
|
|
||||||
## moves
|
|
||||||
let
|
|
||||||
sideToMove = self.position.sideToMove
|
|
||||||
nonSideToMove = sideToMove.opposite()
|
|
||||||
pawns = self.getBitboard(Pawn, sideToMove)
|
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
# We can only capture enemy pieces (except the king)
|
# We can only capture enemy pieces (except the king)
|
||||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
||||||
enemyPawns = self.getBitboard(Pawn, nonSideToMove)
|
# We can only capture diagonally and forward
|
||||||
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
||||||
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
||||||
epTarget = self.position.enPassantSquare
|
epTarget = self.position.enPassantSquare
|
||||||
var epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
|
checkers = self.position.checkers
|
||||||
# TODO: Remove this. Seems like we're not keeping track of en passant targets properly and
|
diagonalPins = self.position.diagonalPins
|
||||||
# trying to do en passant on top of a piece
|
orthogonalPins = self.position.orthogonalPins
|
||||||
epBitboard = epBitboard and not occupancy
|
promotionRank = if sideToMove == White: getRankMask(0) else: getRankMask(7)
|
||||||
# Top right attacks
|
# The rank where each color's side starts
|
||||||
for square in rightMovement and enemyPieces:
|
# TODO: Give names to ranks and files so we don't have to assume a
|
||||||
moves.add(createMove(square.toBitboard().backwardLeftRelativeTo(sideToMove), square, Capture))
|
# specific board layout when calling get(Rank|File)Mask
|
||||||
# Top left attacks
|
startingRank = if sideToMove == White: getRankMask(6) else: getRankMask(1)
|
||||||
for square in leftMovement and enemyPieces:
|
|
||||||
moves.add(createMove(square.toBitboard().backwardRightRelativeTo(sideToMove), square, Capture))
|
var epBitboard = if epTarget != nullSquare(): epTarget.toBitboard() else: Bitboard(0)
|
||||||
# Special case for en passant
|
let epPawn = if epBitboard == 0: Bitboard(0) else: epBitboard.forwardRelativeTo(sideToMove)
|
||||||
|
# If we are in check, en passant is only possible if we'd capture the (only)
|
||||||
|
# checking pawn with it
|
||||||
|
if epBitboard != 0 and self.inCheck() and (epPawn and checkers).countSquares() == 0:
|
||||||
|
epBitboard = Bitboard(0)
|
||||||
|
|
||||||
|
# Single and double pushes
|
||||||
let
|
let
|
||||||
epLeft = epBitboard and leftMovement
|
# If a pawn is pinned diagonally, it cannot move
|
||||||
epRight = epBitboard and rightMovement
|
pushablePawns = pawns and not diagonalPins
|
||||||
if epLeft != 0:
|
# Neither can it move if it's pinned orthogonally
|
||||||
moves.add(createMove(epBitboard.forwardLeftRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
singlePushes = pushablePawns.forwardRelativeTo(sideToMove) and not occupancy and not orthogonalPins
|
||||||
elif epRight != 0:
|
# Only pawns on their starting rank can double push
|
||||||
moves.add(createMove(epBitboard.forwardRightRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
doublePushes = (pushablePawns and startingRank).doubleForwardRelativeTo(sideToMove) and not occupancy and orthogonalPins
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnPromotions(self: Chessboard, moves: var MoveList) =
|
proc generateRookMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
||||||
## Helper of generatePawnMoves for generating all promotion
|
|
||||||
## moves
|
|
||||||
let
|
|
||||||
sideToMove = self.position.sideToMove
|
|
||||||
pawns = self.getBitboard(Pawn, sideToMove)
|
|
||||||
occupancy = self.getOccupancy()
|
|
||||||
for square in pawns.forwardRelativeTo(sideToMove) and not occupancy and sideToMove.getLastRank():
|
|
||||||
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
|
|
||||||
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion))
|
|
||||||
|
|
||||||
|
|
||||||
proc generatePawnMoves(self: Chessboard, moves: var MoveList) =
|
|
||||||
## Generates all the legal pawn moves for the side to move
|
|
||||||
self.generatePawnMovements(moves)
|
|
||||||
self.generatePawnCaptures(moves)
|
|
||||||
self.generatePawnPromotions(moves)
|
|
||||||
|
|
||||||
|
|
||||||
proc generateRookMoves(self: Chessboard, moves: var MoveList) =
|
|
||||||
## Helper of generateSlidingMoves to generate rook moves
|
## Helper of generateSlidingMoves to generate rook moves
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
nonSideToMove = sideToMove.opposite()
|
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
||||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
|
||||||
rooks = self.getBitboard(Rook, sideToMove)
|
rooks = self.getBitboard(Rook, sideToMove)
|
||||||
queens = self.getBitboard(Queen, sideToMove)
|
queens = self.getBitboard(Queen, sideToMove)
|
||||||
movableRooks = not self.position.diagonalPins and (queens or rooks)
|
movableRooks = not self.position.diagonalPins and (queens or rooks)
|
||||||
|
@ -591,27 +602,26 @@ proc generateRookMoves(self: Chessboard, moves: var MoveList) =
|
||||||
let
|
let
|
||||||
blockers = occupancy and Rook.getRelevantBlockers(square)
|
blockers = occupancy and Rook.getRelevantBlockers(square)
|
||||||
moveset = getRookMoves(square, blockers)
|
moveset = getRookMoves(square, blockers)
|
||||||
for target in moveset and not occupancy and pinMask:
|
for target in moveset and not occupancy and pinMask and mask:
|
||||||
moves.add(createMove(square, target))
|
moves.add(createMove(square, target))
|
||||||
for target in moveset and enemyPieces and pinMask:
|
for target in moveset and enemyPieces and pinMask and mask:
|
||||||
moves.add(createMove(square, target, Capture))
|
moves.add(createMove(square, target, Capture))
|
||||||
for square in unpinnedRooks:
|
for square in unpinnedRooks:
|
||||||
let
|
let
|
||||||
blockers = occupancy and Rook.getRelevantBlockers(square)
|
blockers = occupancy and Rook.getRelevantBlockers(square)
|
||||||
moveset = getRookMoves(square, blockers)
|
moveset = getRookMoves(square, blockers)
|
||||||
for target in moveset and not occupancy:
|
for target in moveset and not occupancy and mask:
|
||||||
moves.add(createMove(square, target))
|
moves.add(createMove(square, target))
|
||||||
for target in moveset and enemyPieces:
|
for target in moveset and enemyPieces and mask:
|
||||||
moves.add(createMove(square, target, Capture))
|
moves.add(createMove(square, target, Capture))
|
||||||
|
|
||||||
|
|
||||||
proc generateBishopMoves(self: Chessboard, moves: var MoveList) =
|
proc generateBishopMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
||||||
## Helper of generateSlidingMoves to generate bishop moves
|
## Helper of generateSlidingMoves to generate bishop moves
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.getOccupancy()
|
||||||
nonSideToMove = sideToMove.opposite()
|
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
||||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
|
||||||
bishops = self.getBitboard(Bishop, sideToMove)
|
bishops = self.getBitboard(Bishop, sideToMove)
|
||||||
queens = self.getBitboard(Queen, sideToMove)
|
queens = self.getBitboard(Queen, sideToMove)
|
||||||
movableBishops = not self.position.orthogonalPins and (queens or bishops)
|
movableBishops = not self.position.orthogonalPins and (queens or bishops)
|
||||||
|
@ -622,27 +632,20 @@ proc generateBishopMoves(self: Chessboard, moves: var MoveList) =
|
||||||
let
|
let
|
||||||
blockers = occupancy and Bishop.getRelevantBlockers(square)
|
blockers = occupancy and Bishop.getRelevantBlockers(square)
|
||||||
moveset = getBishopMoves(square, blockers)
|
moveset = getBishopMoves(square, blockers)
|
||||||
for target in moveset and pinMask and not occupancy:
|
for target in moveset and pinMask and mask:
|
||||||
moves.add(createMove(square, target))
|
moves.add(createMove(square, target))
|
||||||
for target in moveset and enemyPieces and pinMask:
|
for target in moveset and enemyPieces and pinMask and mask:
|
||||||
moves.add(createMove(square, target, Capture))
|
moves.add(createMove(square, target, Capture))
|
||||||
for square in unpinnedBishops:
|
for square in unpinnedBishops:
|
||||||
let
|
let
|
||||||
blockers = occupancy and Bishop.getRelevantBlockers(square)
|
blockers = occupancy and Bishop.getRelevantBlockers(square)
|
||||||
moveset = getBishopMoves(square, blockers)
|
moveset = getBishopMoves(square, blockers)
|
||||||
for target in moveset and not occupancy:
|
for target in moveset and mask:
|
||||||
moves.add(createMove(square, target))
|
moves.add(createMove(square, target))
|
||||||
for target in moveset and enemyPieces:
|
for target in moveset and enemyPieces and mask:
|
||||||
moves.add(createMove(square, target, Capture))
|
moves.add(createMove(square, target, Capture))
|
||||||
|
|
||||||
|
|
||||||
proc generateSlidingMoves(self: Chessboard, moves: var MoveList) =
|
|
||||||
## Generates all legal sliding moves for the side to move
|
|
||||||
self.generateRookMoves(moves)
|
|
||||||
self.generateBishopMoves(moves)
|
|
||||||
# Queens are just handled rooks + bishops
|
|
||||||
|
|
||||||
|
|
||||||
proc generateKingMoves(self: Chessboard, moves: var MoveList) =
|
proc generateKingMoves(self: Chessboard, moves: var MoveList) =
|
||||||
## Generates all legal king moves for the side to move
|
## Generates all legal king moves for the side to move
|
||||||
let
|
let
|
||||||
|
@ -652,25 +655,27 @@ proc generateKingMoves(self: Chessboard, moves: var MoveList) =
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||||
bitboard = getKingAttacks(king.toSquare())
|
bitboard = getKingAttacks(king.toSquare())
|
||||||
|
noKingOccupancy = occupancy and not king
|
||||||
for square in bitboard and not occupancy:
|
for square in bitboard and not occupancy:
|
||||||
moves.add(createMove(king, square))
|
if not self.isOccupancyAttacked(square, noKingOccupancy):
|
||||||
|
moves.add(createMove(king, square))
|
||||||
for square in bitboard and enemyPieces:
|
for square in bitboard and enemyPieces:
|
||||||
moves.add(createMove(king, square, Capture))
|
if not self.isOccupancyAttacked(square, noKingOccupancy):
|
||||||
|
moves.add(createMove(king, square, Capture))
|
||||||
|
|
||||||
|
|
||||||
proc generateKnightMoves(self: Chessboard, moves: var MoveList)=
|
proc generateKnightMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
||||||
## Generates all the legal knight moves for the side to move
|
## Generates all the legal knight moves for the side to move
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
knights = self.getBitboard(Knight, sideToMove)
|
knights = self.getBitboard(Knight, sideToMove)
|
||||||
occupancy = self.getOccupancy()
|
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
pinned = self.position.diagonalPins or self.position.orthogonalPins
|
pinned = self.position.diagonalPins or self.position.orthogonalPins
|
||||||
unpinnedKnights = knights and not pinned
|
unpinnedKnights = knights and not pinned
|
||||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||||
for square in unpinnedKnights:
|
for square in unpinnedKnights:
|
||||||
let bitboard = getKnightAttacks(square)
|
let bitboard = getKnightAttacks(square)
|
||||||
for target in bitboard and not occupancy:
|
for target in bitboard and mask:
|
||||||
moves.add(createMove(square, target))
|
moves.add(createMove(square, target))
|
||||||
for target in bitboard and enemyPieces:
|
for target in bitboard and enemyPieces:
|
||||||
moves.add(createMove(square, target, Capture))
|
moves.add(createMove(square, target, Capture))
|
||||||
|
@ -682,6 +687,7 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
||||||
if self.position.halfMoveClock >= 100:
|
if self.position.halfMoveClock >= 100:
|
||||||
# Draw by 50-move rule
|
# Draw by 50-move rule
|
||||||
return
|
return
|
||||||
|
let sideToMove = self.position.sideToMove
|
||||||
# TODO: Check for draw by insufficient material
|
# TODO: Check for draw by insufficient material
|
||||||
# TODO: Check for repetitions (requires zobrist hashing + table)
|
# TODO: Check for repetitions (requires zobrist hashing + table)
|
||||||
self.generateKingMoves(moves)
|
self.generateKingMoves(moves)
|
||||||
|
@ -692,10 +698,32 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
||||||
if not self.inCheck():
|
if not self.inCheck():
|
||||||
# TODO: Castling
|
# TODO: Castling
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
# We pass a mask to our move generators to remove stuff
|
||||||
|
# like our friendly pieces from the set of possible
|
||||||
|
# target squares, as well as to ensure checks are not
|
||||||
|
# ignored
|
||||||
|
|
||||||
self.generatePawnMoves(moves)
|
var mask: Bitboard
|
||||||
self.generateKnightMoves(moves)
|
if not self.inCheck():
|
||||||
self.generateSlidingMoves(moves)
|
# Not in check: cannot move over friendly pieces
|
||||||
|
mask = not self.getOccupancyFor(sideToMove)
|
||||||
|
else:
|
||||||
|
# We *are* in check (from a single piece, because the two checks
|
||||||
|
# case was handled above already). If the piece is a slider, we'll
|
||||||
|
# extract the ray from it to our king and add the checking piece to
|
||||||
|
# it, meaning the only legal moves are those that either block the
|
||||||
|
# check or capture the checking piece. For other non-sliding pieces
|
||||||
|
# the ray will be empty so the only legal move will be to capture
|
||||||
|
# the checking piece
|
||||||
|
let checker = self.position.checkers.lowestSquare()
|
||||||
|
mask = getRayBetween(checker, self.getBitboard(King, sideToMove).toSquare()) or checker.toBitboard()
|
||||||
|
|
||||||
|
self.generatePawnMoves(moves, mask)
|
||||||
|
self.generateKnightMoves(moves, mask)
|
||||||
|
self.generateRookMoves(moves, mask)
|
||||||
|
self.generateBishopMoves(moves, mask)
|
||||||
|
# Queens are just handled rooks + bishops
|
||||||
|
|
||||||
|
|
||||||
proc isLegal(self: Chessboard, move: Move): bool {.inline.} =
|
proc isLegal(self: Chessboard, move: Move): bool {.inline.} =
|
||||||
|
@ -863,20 +891,19 @@ proc toFEN*(self: Chessboard): string =
|
||||||
result &= (if self.position.sideToMove == White: "w" else: "b")
|
result &= (if self.position.sideToMove == White: "w" else: "b")
|
||||||
result &= " "
|
result &= " "
|
||||||
# Castling availability
|
# Castling availability
|
||||||
result &= "-"
|
let castleWhite = self.position.castlingAvailability.white
|
||||||
# let castleWhite = self.position.castlingRightsAvailable.white
|
let castleBlack = self.position.castlingAvailability.black
|
||||||
# let castleBlack = self.position.castlingRightsAvailable.black
|
if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||||
# if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
result &= "-"
|
||||||
# result &= "-"
|
else:
|
||||||
# else:
|
if castleWhite.king:
|
||||||
# if castleWhite.king:
|
result &= "K"
|
||||||
# result &= "K"
|
if castleWhite.queen:
|
||||||
# if castleWhite.queen:
|
result &= "Q"
|
||||||
# result &= "Q"
|
if castleBlack.king:
|
||||||
# if castleBlack.king:
|
result &= "k"
|
||||||
# result &= "k"
|
if castleBlack.queen:
|
||||||
# if castleBlack.queen:
|
result &= "q"
|
||||||
# result &= "q"
|
|
||||||
result &= " "
|
result &= " "
|
||||||
# En passant target
|
# En passant target
|
||||||
if self.position.enPassantSquare == nullSquare():
|
if self.position.enPassantSquare == nullSquare():
|
||||||
|
|
|
@ -61,6 +61,12 @@ func countSquares*(self: Bitboard): int {.inline.} =
|
||||||
result = self.countSetBits()
|
result = self.countSetBits()
|
||||||
|
|
||||||
|
|
||||||
|
func lowestSquare*(self: Bitboard): Square {.inline.} =
|
||||||
|
## Returns the index of the lowest one bit
|
||||||
|
## in the given bitboard as a square
|
||||||
|
result = Square(self.countTrailingZeroBits().uint8)
|
||||||
|
|
||||||
|
|
||||||
func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64
|
func getFileMask*(file: int): Bitboard = Bitboard(0x101010101010101'u64) shl file.uint64
|
||||||
func getRankMask*(rank: int): Bitboard = Bitboard(0xff) shl uint64(8 * rank)
|
func getRankMask*(rank: int): Bitboard = Bitboard(0xff) shl uint64(8 * rank)
|
||||||
func toBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64
|
func toBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64
|
||||||
|
|
|
@ -11,6 +11,11 @@ type
|
||||||
|
|
||||||
# Castling metadata. Updated on every move
|
# Castling metadata. Updated on every move
|
||||||
castlingRights*: array[64, uint8]
|
castlingRights*: array[64, uint8]
|
||||||
|
# Castling availability. This just keeps track
|
||||||
|
# of whether the king or the rooks on either side
|
||||||
|
# moved and is only useful for printing the correct
|
||||||
|
# FEN for a given position
|
||||||
|
castlingAvailability*: tuple[white, black: tuple[queen, king: bool]]
|
||||||
# Number of half-moves that were performed
|
# Number of half-moves that were performed
|
||||||
# to reach this position starting from the
|
# to reach this position starting from the
|
||||||
# root of the tree
|
# root of the tree
|
||||||
|
|
|
@ -345,6 +345,8 @@ const HELP_TEXT = """Nimfish help menu:
|
||||||
- pos <args>: Shorthand for "position <args>"
|
- pos <args>: Shorthand for "position <args>"
|
||||||
- get <square>: Get the piece on the given square
|
- get <square>: Get the piece on the given square
|
||||||
- atk <square>: Print the attack bitboard of the given square for the side to move
|
- atk <square>: Print the attack bitboard of the given square for the side to move
|
||||||
|
- pins: Print the current pin mask
|
||||||
|
- checks: Print the current checks mask
|
||||||
- skip: Swap the side to move
|
- skip: Swap the side to move
|
||||||
- uci: enter UCI mode (WIP)
|
- uci: enter UCI mode (WIP)
|
||||||
- quit: exit
|
- quit: exit
|
||||||
|
@ -380,6 +382,7 @@ proc commandLoop*: int =
|
||||||
echo HELP_TEXT
|
echo HELP_TEXT
|
||||||
of "skip":
|
of "skip":
|
||||||
board.position.sideToMove = board.position.sideToMove.opposite()
|
board.position.sideToMove = board.position.sideToMove.opposite()
|
||||||
|
board.updateChecksAndPins()
|
||||||
of "go":
|
of "go":
|
||||||
handleGoCommand(board, cmd)
|
handleGoCommand(board, cmd)
|
||||||
of "position", "pos":
|
of "position", "pos":
|
||||||
|
@ -389,7 +392,10 @@ proc commandLoop*: int =
|
||||||
of "pretty", "print", "fen":
|
of "pretty", "print", "fen":
|
||||||
handlePositionCommand(board, @["position", cmd[0]])
|
handlePositionCommand(board, @["position", cmd[0]])
|
||||||
of "unmove", "u":
|
of "unmove", "u":
|
||||||
board.unmakeMove()
|
if board.positions.len() == 0:
|
||||||
|
echo "No previous move to undo"
|
||||||
|
else:
|
||||||
|
board.unmakeMove()
|
||||||
of "stm":
|
of "stm":
|
||||||
echo &"Side to move: {board.position.sideToMove}"
|
echo &"Side to move: {board.position.sideToMove}"
|
||||||
of "atk":
|
of "atk":
|
||||||
|
@ -421,6 +427,11 @@ proc commandLoop*: int =
|
||||||
echo &"Castling rights for {($board.position.sideToMove).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
echo &"Castling rights for {($board.position.sideToMove).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||||
of "check":
|
of "check":
|
||||||
echo &"{board.position.sideToMove} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
echo &"{board.position.sideToMove} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||||
|
of "pins":
|
||||||
|
echo &"Ortogonal pins:\n{board.position.orthogonalPins}"
|
||||||
|
echo &"Diagonal pins:\n{board.position.diagonalPins}"
|
||||||
|
of "checks":
|
||||||
|
echo board.position.checkers
|
||||||
of "quit":
|
of "quit":
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -10,6 +10,8 @@ from argparse import ArgumentParser, Namespace
|
||||||
def main(args: Namespace) -> int:
|
def main(args: Namespace) -> int:
|
||||||
if args.silent:
|
if args.silent:
|
||||||
print = lambda *_: ...
|
print = lambda *_: ...
|
||||||
|
else:
|
||||||
|
print = __builtins__.print
|
||||||
print("Nimfish move validator v0.0.1 by nocturn9x")
|
print("Nimfish move validator v0.0.1 by nocturn9x")
|
||||||
try:
|
try:
|
||||||
STOCKFISH = (args.stockfish or Path(which("stockfish"))).resolve(strict=True)
|
STOCKFISH = (args.stockfish or Path(which("stockfish"))).resolve(strict=True)
|
||||||
|
|
Loading…
Reference in New Issue