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
|
||||
position*: Position
|
||||
# List of all previously reached positions
|
||||
positions: seq[Position]
|
||||
positions*: seq[Position]
|
||||
|
||||
|
||||
# A bunch of simple utility functions and forward declarations
|
||||
|
@ -53,7 +53,7 @@ proc removePiece(self: Chessboard, square: Square)
|
|||
proc update*(self: Chessboard)
|
||||
func inCheck*(self: Chessboard): bool {.inline.}
|
||||
proc fromChar*(c: char): Piece
|
||||
|
||||
proc updateChecksAndPins*(self: Chessboard)
|
||||
|
||||
|
||||
func kingSideRook*(color: PieceColor): Square {.inline.} = (if color == White: "h1".toSquare() else: "h8".toSquare())
|
||||
|
@ -148,13 +148,13 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
|||
of '-':
|
||||
discard
|
||||
of 'K':
|
||||
discard
|
||||
result.position.castlingAvailability.white.king = true
|
||||
of 'Q':
|
||||
discard
|
||||
result.position.castlingAvailability.white.queen = true
|
||||
of 'k':
|
||||
discard
|
||||
result.position.castlingAvailability.black.king = true
|
||||
of 'q':
|
||||
discard
|
||||
result.position.castlingAvailability.black.queen = true
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||
of 3:
|
||||
|
@ -187,6 +187,7 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
|||
else:
|
||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||
inc(index)
|
||||
result.updateChecksAndPins()
|
||||
|
||||
|
||||
proc newDefaultChessboard*: Chessboard {.inline.} =
|
||||
|
@ -233,8 +234,7 @@ func getOccupancy(self: Chessboard): Bitboard {.inline.} =
|
|||
|
||||
|
||||
func getPawnAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the attack bitboard for the given square from
|
||||
## the pawns of the given side
|
||||
## Returns the locations of the pawns attacking the given square
|
||||
let
|
||||
sq = square.toBitboard()
|
||||
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.} =
|
||||
## Returns the attack bitboard for the given square from
|
||||
## the king of the given side
|
||||
## Returns the location of the king if it is attacking the given square
|
||||
result = Bitboard(0)
|
||||
let
|
||||
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 =
|
||||
## Returns the attack bitboard for the given square from
|
||||
## the knights of the given side
|
||||
## Returns the locations of the knights attacking the given square
|
||||
let
|
||||
knights = self.getBitboard(Knight, attacker)
|
||||
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 =
|
||||
## Returns the attack bitboard for the given square from
|
||||
## the sliding pieces of the given side
|
||||
## Returns the locations of the sliding pieces attacking the given square
|
||||
let
|
||||
queens = self.getBitboard(Queen, attacker)
|
||||
rooks = self.getBitboard(Rook, attacker) or queens
|
||||
bishops = self.getBitboard(Bishop, attacker) or queens
|
||||
occupancy = self.getOccupancy()
|
||||
squareBB = square.toBitboard()
|
||||
result = Bitboard(0)
|
||||
for rook in rooks:
|
||||
let blockers = Rook.getRelevantBlockers(square)
|
||||
let rookBB = rook.toBitboard()
|
||||
if (getRookMoves(square, blockers) and rookBB) != 0:
|
||||
result = result or rookBB
|
||||
let
|
||||
blockers = occupancy and Rook.getRelevantBlockers(rook)
|
||||
moves = getRookMoves(rook, blockers)
|
||||
# Attack set intersects our chosen square
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or rook.toBitboard()
|
||||
for bishop in bishops:
|
||||
let
|
||||
blockers = Bishop.getRelevantBlockers(square)
|
||||
bishopBB = bishop.toBitboard()
|
||||
if (getBishopMoves(square, blockers) and bishopBB) != 0:
|
||||
result = result or bishopBB
|
||||
blockers = occupancy and Bishop.getRelevantBlockers(bishop)
|
||||
moves = getBishopMoves(bishop, blockers)
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or bishop.toBitboard()
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
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
|
||||
## pinned pieces
|
||||
|
||||
|
@ -305,8 +352,7 @@ proc updateChecksAndPins(self: Chessboard) =
|
|||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
friendlyKingBB = self.getBitboard(King, sideToMove)
|
||||
friendlyKing = friendlyKingBB.toSquare()
|
||||
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
|
||||
|
@ -325,14 +371,15 @@ proc updateChecksAndPins(self: Chessboard) =
|
|||
for piece in canPinDiagonally:
|
||||
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
|
||||
|
||||
for piece in canPinOrthogonally:
|
||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||
|
||||
if (pinningRay and friendlyKingBB).countSquares() > 0:
|
||||
self.position.diagonalPins = self.position.diagonalPins or pinningRay
|
||||
if (pinningRay and friendlyPieces).countSquares() > 0:
|
||||
self.position.orthogonalPins = self.position.orthogonalPins or pinningRay
|
||||
|
||||
|
||||
func inCheck(self: Chessboard): bool {.inline.} =
|
||||
|
@ -502,85 +549,49 @@ proc unmakeMove*(self: Chessboard) =
|
|||
self.update()
|
||||
|
||||
|
||||
proc generatePawnMovements(self: Chessboard, moves: var MoveList) =
|
||||
## Helper of generatePawnMoves for generating all non-capture
|
||||
## and non-promotion pawn moves
|
||||
proc generatePawnMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
||||
let
|
||||
sideToMove = self.position.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()
|
||||
# We can only capture enemy pieces (except the king)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||
enemyPawns = self.getBitboard(Pawn, nonSideToMove)
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
||||
# We can only capture diagonally and forward
|
||||
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
||||
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
||||
epTarget = self.position.enPassantSquare
|
||||
var epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
|
||||
# TODO: Remove this. Seems like we're not keeping track of en passant targets properly and
|
||||
# trying to do en passant on top of a piece
|
||||
epBitboard = epBitboard and not occupancy
|
||||
# Top right attacks
|
||||
for square in rightMovement and enemyPieces:
|
||||
moves.add(createMove(square.toBitboard().backwardLeftRelativeTo(sideToMove), square, Capture))
|
||||
# Top left attacks
|
||||
for square in leftMovement and enemyPieces:
|
||||
moves.add(createMove(square.toBitboard().backwardRightRelativeTo(sideToMove), square, Capture))
|
||||
# Special case for en passant
|
||||
checkers = self.position.checkers
|
||||
diagonalPins = self.position.diagonalPins
|
||||
orthogonalPins = self.position.orthogonalPins
|
||||
promotionRank = if sideToMove == White: getRankMask(0) else: getRankMask(7)
|
||||
# The rank where each color's side starts
|
||||
# TODO: Give names to ranks and files so we don't have to assume a
|
||||
# specific board layout when calling get(Rank|File)Mask
|
||||
startingRank = if sideToMove == White: getRankMask(6) else: getRankMask(1)
|
||||
|
||||
var epBitboard = if epTarget != nullSquare(): epTarget.toBitboard() else: Bitboard(0)
|
||||
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
|
||||
epLeft = epBitboard and leftMovement
|
||||
epRight = epBitboard and rightMovement
|
||||
if epLeft != 0:
|
||||
moves.add(createMove(epBitboard.forwardLeftRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
||||
elif epRight != 0:
|
||||
moves.add(createMove(epBitboard.forwardRightRelativeTo(nonSideToMove), epBitboard, EnPassant))
|
||||
# If a pawn is pinned diagonally, it cannot move
|
||||
pushablePawns = pawns and not diagonalPins
|
||||
# Neither can it move if it's pinned orthogonally
|
||||
singlePushes = pushablePawns.forwardRelativeTo(sideToMove) and not occupancy and not orthogonalPins
|
||||
# Only pawns on their starting rank can double push
|
||||
doublePushes = (pushablePawns and startingRank).doubleForwardRelativeTo(sideToMove) and not occupancy and orthogonalPins
|
||||
|
||||
|
||||
proc generatePawnPromotions(self: Chessboard, moves: var MoveList) =
|
||||
## 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) =
|
||||
proc generateRookMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
||||
## Helper of generateSlidingMoves to generate rook moves
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
occupancy = self.getOccupancy()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
||||
rooks = self.getBitboard(Rook, sideToMove)
|
||||
queens = self.getBitboard(Queen, sideToMove)
|
||||
movableRooks = not self.position.diagonalPins and (queens or rooks)
|
||||
|
@ -591,27 +602,26 @@ proc generateRookMoves(self: Chessboard, moves: var MoveList) =
|
|||
let
|
||||
blockers = occupancy and Rook.getRelevantBlockers(square)
|
||||
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))
|
||||
for target in moveset and enemyPieces and pinMask:
|
||||
for target in moveset and enemyPieces and pinMask and mask:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
for square in unpinnedRooks:
|
||||
let
|
||||
blockers = occupancy and Rook.getRelevantBlockers(square)
|
||||
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))
|
||||
for target in moveset and enemyPieces:
|
||||
for target in moveset and enemyPieces and mask:
|
||||
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
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
occupancy = self.getOccupancy()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
||||
bishops = self.getBitboard(Bishop, sideToMove)
|
||||
queens = self.getBitboard(Queen, sideToMove)
|
||||
movableBishops = not self.position.orthogonalPins and (queens or bishops)
|
||||
|
@ -622,27 +632,20 @@ proc generateBishopMoves(self: Chessboard, moves: var MoveList) =
|
|||
let
|
||||
blockers = occupancy and Bishop.getRelevantBlockers(square)
|
||||
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))
|
||||
for target in moveset and enemyPieces and pinMask:
|
||||
for target in moveset and enemyPieces and pinMask and mask:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
for square in unpinnedBishops:
|
||||
let
|
||||
blockers = occupancy and Bishop.getRelevantBlockers(square)
|
||||
moveset = getBishopMoves(square, blockers)
|
||||
for target in moveset and not occupancy:
|
||||
for target in moveset and mask:
|
||||
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))
|
||||
|
||||
|
||||
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) =
|
||||
## Generates all legal king moves for the side to move
|
||||
let
|
||||
|
@ -652,25 +655,27 @@ proc generateKingMoves(self: Chessboard, moves: var MoveList) =
|
|||
nonSideToMove = sideToMove.opposite()
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||
bitboard = getKingAttacks(king.toSquare())
|
||||
noKingOccupancy = occupancy and not king
|
||||
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:
|
||||
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
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
knights = self.getBitboard(Knight, sideToMove)
|
||||
occupancy = self.getOccupancy()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
pinned = self.position.diagonalPins or self.position.orthogonalPins
|
||||
unpinnedKnights = knights and not pinned
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||
for square in unpinnedKnights:
|
||||
let bitboard = getKnightAttacks(square)
|
||||
for target in bitboard and not occupancy:
|
||||
for target in bitboard and mask:
|
||||
moves.add(createMove(square, target))
|
||||
for target in bitboard and enemyPieces:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
|
@ -682,6 +687,7 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
|||
if self.position.halfMoveClock >= 100:
|
||||
# Draw by 50-move rule
|
||||
return
|
||||
let sideToMove = self.position.sideToMove
|
||||
# TODO: Check for draw by insufficient material
|
||||
# TODO: Check for repetitions (requires zobrist hashing + table)
|
||||
self.generateKingMoves(moves)
|
||||
|
@ -692,10 +698,32 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
|||
if not self.inCheck():
|
||||
# TODO: Castling
|
||||
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)
|
||||
self.generateKnightMoves(moves)
|
||||
self.generateSlidingMoves(moves)
|
||||
var mask: Bitboard
|
||||
if not self.inCheck():
|
||||
# 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.} =
|
||||
|
@ -863,20 +891,19 @@ proc toFEN*(self: Chessboard): string =
|
|||
result &= (if self.position.sideToMove == White: "w" else: "b")
|
||||
result &= " "
|
||||
# Castling availability
|
||||
result &= "-"
|
||||
# let castleWhite = self.position.castlingRightsAvailable.white
|
||||
# let castleBlack = self.position.castlingRightsAvailable.black
|
||||
# if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||
# result &= "-"
|
||||
# else:
|
||||
# if castleWhite.king:
|
||||
# result &= "K"
|
||||
# if castleWhite.queen:
|
||||
# result &= "Q"
|
||||
# if castleBlack.king:
|
||||
# result &= "k"
|
||||
# if castleBlack.queen:
|
||||
# result &= "q"
|
||||
let castleWhite = self.position.castlingAvailability.white
|
||||
let castleBlack = self.position.castlingAvailability.black
|
||||
if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||
result &= "-"
|
||||
else:
|
||||
if castleWhite.king:
|
||||
result &= "K"
|
||||
if castleWhite.queen:
|
||||
result &= "Q"
|
||||
if castleBlack.king:
|
||||
result &= "k"
|
||||
if castleBlack.queen:
|
||||
result &= "q"
|
||||
result &= " "
|
||||
# En passant target
|
||||
if self.position.enPassantSquare == nullSquare():
|
||||
|
|
|
@ -61,6 +61,12 @@ func countSquares*(self: Bitboard): int {.inline.} =
|
|||
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 getRankMask*(rank: int): Bitboard = Bitboard(0xff) shl uint64(8 * rank)
|
||||
func toBitboard*(square: SomeInteger): Bitboard = Bitboard(1'u64) shl square.uint64
|
||||
|
|
|
@ -11,6 +11,11 @@ type
|
|||
|
||||
# Castling metadata. Updated on every move
|
||||
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
|
||||
# to reach this position starting from the
|
||||
# root of the tree
|
||||
|
|
|
@ -345,6 +345,8 @@ const HELP_TEXT = """Nimfish help menu:
|
|||
- pos <args>: Shorthand for "position <args>"
|
||||
- get <square>: Get the piece on the given square
|
||||
- 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
|
||||
- uci: enter UCI mode (WIP)
|
||||
- quit: exit
|
||||
|
@ -380,6 +382,7 @@ proc commandLoop*: int =
|
|||
echo HELP_TEXT
|
||||
of "skip":
|
||||
board.position.sideToMove = board.position.sideToMove.opposite()
|
||||
board.updateChecksAndPins()
|
||||
of "go":
|
||||
handleGoCommand(board, cmd)
|
||||
of "position", "pos":
|
||||
|
@ -389,7 +392,10 @@ proc commandLoop*: int =
|
|||
of "pretty", "print", "fen":
|
||||
handlePositionCommand(board, @["position", cmd[0]])
|
||||
of "unmove", "u":
|
||||
board.unmakeMove()
|
||||
if board.positions.len() == 0:
|
||||
echo "No previous move to undo"
|
||||
else:
|
||||
board.unmakeMove()
|
||||
of "stm":
|
||||
echo &"Side to move: {board.position.sideToMove}"
|
||||
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\")}"
|
||||
of "check":
|
||||
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":
|
||||
return 0
|
||||
else:
|
||||
|
|
|
@ -10,6 +10,8 @@ from argparse import ArgumentParser, Namespace
|
|||
def main(args: Namespace) -> int:
|
||||
if args.silent:
|
||||
print = lambda *_: ...
|
||||
else:
|
||||
print = __builtins__.print
|
||||
print("Nimfish move validator v0.0.1 by nocturn9x")
|
||||
try:
|
||||
STOCKFISH = (args.stockfish or Path(which("stockfish"))).resolve(strict=True)
|
||||
|
|
Loading…
Reference in New Issue