|
|
|
@ -12,6 +12,8 @@
|
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
|
|
## Move generation logic
|
|
|
|
|
|
|
|
|
|
import std/strformat
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -29,16 +31,13 @@ export bitboards, magics, pieces, moves, position, rays, misc, board
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc generatePawnMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
|
|
|
|
proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
|
|
|
|
let
|
|
|
|
|
sideToMove = self.position.sideToMove
|
|
|
|
|
pawns = self.getBitboard(Pawn, sideToMove)
|
|
|
|
|
occupancy = self.getOccupancy()
|
|
|
|
|
# We can only capture enemy pieces (except the king)
|
|
|
|
|
enemyPieces = self.getOccupancyFor(sideToMove.opposite())
|
|
|
|
|
# We can only capture diagonally and forward
|
|
|
|
|
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
|
|
|
|
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
|
|
|
|
epTarget = self.position.enPassantSquare
|
|
|
|
|
checkers = self.position.checkers
|
|
|
|
|
diagonalPins = self.position.diagonalPins
|
|
|
|
@ -61,13 +60,51 @@ proc generatePawnMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
|
|
|
|
# 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
|
|
|
|
|
singlePushes = (pushablePawns.forwardRelativeTo(sideToMove) and not enemyPieces) and destinationMask and not orthogonalPins
|
|
|
|
|
# We do this weird dance instead of using doubleForwardRelativeTo() because that doesn't have any
|
|
|
|
|
# way to check if there's pieces on the two squares ahead of the pawn
|
|
|
|
|
var canDoublePush = pushablePawns and startingRank
|
|
|
|
|
canDoublePush = canDoublePush.forwardRelativeTo(sideToMove) and not occupancy and not orthogonalPins
|
|
|
|
|
canDoublePush = canDoublePush.forwardRelativeTo(sideToMove) and not occupancy and destinationMask
|
|
|
|
|
|
|
|
|
|
for pawn in singlePushes:
|
|
|
|
|
let pawnBB = pawn.toBitboard()
|
|
|
|
|
if promotionRank.contains(pawn):
|
|
|
|
|
for promotion in [PromoteToBishop, PromoteToBishop, PromoteToQueen, PromoteToRook]:
|
|
|
|
|
moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn, promotion))
|
|
|
|
|
else:
|
|
|
|
|
moves.add(createMove(pawnBB.backwardRelativeTo(sideToMove), pawn))
|
|
|
|
|
|
|
|
|
|
for pawn in canDoublePush:
|
|
|
|
|
moves.add(createMove(pawn.toBitboard().doubleBackwardRelativeTo(sideToMove), pawn, DoublePush))
|
|
|
|
|
|
|
|
|
|
let canCapture = pawns and not orthogonalPins
|
|
|
|
|
var
|
|
|
|
|
captureLeft = canCapture.forwardLeftRelativeTo(sideToMove) and enemyPieces and destinationMask
|
|
|
|
|
captureRight = canCapture.forwardRightRelativeTo(sideToMove) and enemyPieces and destinationMask
|
|
|
|
|
# If a capturing pawn is pinned diagonally, it is allowed to capture only
|
|
|
|
|
# in the direction of the pin
|
|
|
|
|
if (diagonalPins and captureLeft) != 0:
|
|
|
|
|
captureLeft = captureLeft and diagonalPins
|
|
|
|
|
if (diagonalPins and captureRight) != 0:
|
|
|
|
|
captureRight = captureRight and diagonalPins
|
|
|
|
|
for pawn in captureLeft:
|
|
|
|
|
let pawnBB = pawn.toBitboard()
|
|
|
|
|
if promotionRank.contains(pawn):
|
|
|
|
|
for promotion in [PromoteToBishop, PromoteToBishop, PromoteToQueen, PromoteToRook]:
|
|
|
|
|
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture, promotion))
|
|
|
|
|
else:
|
|
|
|
|
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture))
|
|
|
|
|
for pawn in captureRight:
|
|
|
|
|
let pawnBB = pawn.toBitboard()
|
|
|
|
|
if promotionRank.contains(pawn):
|
|
|
|
|
for promotion in [PromoteToBishop, PromoteToBishop, PromoteToQueen, PromoteToRook]:
|
|
|
|
|
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture, promotion))
|
|
|
|
|
else:
|
|
|
|
|
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc generateRookMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
|
|
|
|
## Helper of generateSlidingMoves to generate rook moves
|
|
|
|
|
proc generateRookMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
|
|
|
|
let
|
|
|
|
|
sideToMove = self.position.sideToMove
|
|
|
|
|
occupancy = self.getOccupancy()
|
|
|
|
@ -82,22 +119,21 @@ proc generateRookMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
|
|
|
|
let
|
|
|
|
|
blockers = occupancy and Rook.getRelevantBlockers(square)
|
|
|
|
|
moveset = getRookMoves(square, blockers)
|
|
|
|
|
for target in moveset and not occupancy and pinMask and mask:
|
|
|
|
|
for target in moveset and pinMask and destinationMask and not enemyPieces:
|
|
|
|
|
moves.add(createMove(square, target))
|
|
|
|
|
for target in moveset and enemyPieces and pinMask and mask:
|
|
|
|
|
for target in moveset and enemyPieces and pinMask and destinationMask:
|
|
|
|
|
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 and mask:
|
|
|
|
|
for target in moveset and destinationMask and not enemyPieces:
|
|
|
|
|
moves.add(createMove(square, target))
|
|
|
|
|
for target in moveset and enemyPieces and mask:
|
|
|
|
|
for target in moveset and enemyPieces and destinationMask:
|
|
|
|
|
moves.add(createMove(square, target, Capture))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc generateBishopMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
|
|
|
|
## Helper of generateSlidingMoves to generate bishop moves
|
|
|
|
|
proc generateBishopMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
|
|
|
|
let
|
|
|
|
|
sideToMove = self.position.sideToMove
|
|
|
|
|
occupancy = self.getOccupancy()
|
|
|
|
@ -112,28 +148,27 @@ proc generateBishopMoves(self: Chessboard, moves: var MoveList, mask: Bitboard)
|
|
|
|
|
let
|
|
|
|
|
blockers = occupancy and Bishop.getRelevantBlockers(square)
|
|
|
|
|
moveset = getBishopMoves(square, blockers)
|
|
|
|
|
for target in moveset and pinMask and mask:
|
|
|
|
|
for target in moveset and pinMask and destinationMask and not enemyPieces:
|
|
|
|
|
moves.add(createMove(square, target))
|
|
|
|
|
for target in moveset and enemyPieces and pinMask and mask:
|
|
|
|
|
for target in moveset and enemyPieces and destinationMask:
|
|
|
|
|
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 mask:
|
|
|
|
|
for target in moveset and destinationMask and not enemyPieces:
|
|
|
|
|
moves.add(createMove(square, target))
|
|
|
|
|
for target in moveset and enemyPieces and mask:
|
|
|
|
|
for target in moveset and enemyPieces and destinationMask:
|
|
|
|
|
moves.add(createMove(square, target, Capture))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc generateKingMoves(self: Chessboard, moves: var MoveList) =
|
|
|
|
|
## Generates all legal king moves for the side to move
|
|
|
|
|
let
|
|
|
|
|
sideToMove = self.position.sideToMove
|
|
|
|
|
king = self.getBitboard(King, sideToMove)
|
|
|
|
|
occupancy = self.getOccupancy()
|
|
|
|
|
nonSideToMove = sideToMove.opposite()
|
|
|
|
|
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
|
|
|
|
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
|
|
|
|
bitboard = getKingAttacks(king.toSquare())
|
|
|
|
|
noKingOccupancy = occupancy and not king
|
|
|
|
|
for square in bitboard and not occupancy:
|
|
|
|
@ -144,23 +179,29 @@ proc generateKingMoves(self: Chessboard, moves: var MoveList) =
|
|
|
|
|
moves.add(createMove(king, square, Capture))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc generateKnightMoves(self: Chessboard, moves: var MoveList, mask: Bitboard) =
|
|
|
|
|
## Generates all the legal knight moves for the side to move
|
|
|
|
|
proc generateKnightMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
|
|
|
|
let
|
|
|
|
|
sideToMove = self.position.sideToMove
|
|
|
|
|
knights = self.getBitboard(Knight, sideToMove)
|
|
|
|
|
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)
|
|
|
|
|
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
|
|
|
|
for square in unpinnedKnights:
|
|
|
|
|
let bitboard = getKnightAttacks(square)
|
|
|
|
|
for target in bitboard and mask:
|
|
|
|
|
for target in bitboard and destinationMask and not enemyPieces:
|
|
|
|
|
moves.add(createMove(square, target))
|
|
|
|
|
for target in bitboard and enemyPieces:
|
|
|
|
|
moves.add(createMove(square, target, Capture))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc generateCastling(self: Chessboard, moves: var MoveList) =
|
|
|
|
|
let
|
|
|
|
|
sideToMove = self.position.sideToMove
|
|
|
|
|
rooks = self.getBitboard(Rook, sideToMove)
|
|
|
|
|
# TODO
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
|
|
|
|
## Generates the list of all possible legal moves
|
|
|
|
|
## in the current position
|
|
|
|
@ -176,18 +217,17 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
|
|
|
|
# moves
|
|
|
|
|
return
|
|
|
|
|
if not self.inCheck():
|
|
|
|
|
# TODO: Castling
|
|
|
|
|
discard
|
|
|
|
|
self.generateCastling(moves)
|
|
|
|
|
|
|
|
|
|
# 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
|
|
|
|
|
|
|
|
|
|
var mask: Bitboard
|
|
|
|
|
var destinationMask: Bitboard
|
|
|
|
|
if not self.inCheck():
|
|
|
|
|
# Not in check: cannot move over friendly pieces
|
|
|
|
|
mask = not self.getOccupancyFor(sideToMove)
|
|
|
|
|
destinationMask = 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
|
|
|
|
@ -197,12 +237,11 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
|
|
|
|
# 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)
|
|
|
|
|
destinationMask = getRayBetween(checker, self.getBitboard(King, sideToMove).toSquare()) or checker.toBitboard()
|
|
|
|
|
self.generatePawnMoves(moves, destinationMask)
|
|
|
|
|
self.generateKnightMoves(moves, destinationMask)
|
|
|
|
|
self.generateRookMoves(moves, destinationMask)
|
|
|
|
|
self.generateBishopMoves(moves, destinationMask)
|
|
|
|
|
# Queens are just handled rooks + bishops
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -290,7 +329,8 @@ proc doMove*(self: Chessboard, move: Move) =
|
|
|
|
|
sideToMove: self.position.sideToMove.opposite(),
|
|
|
|
|
castlingRights: castlingRights,
|
|
|
|
|
enPassantSquare: enPassantTarget,
|
|
|
|
|
pieces: self.position.pieces
|
|
|
|
|
pieces: self.position.pieces,
|
|
|
|
|
castlingAvailability: self.position.castlingAvailability
|
|
|
|
|
)
|
|
|
|
|
# Update position metadata
|
|
|
|
|
|
|
|
|
@ -336,7 +376,6 @@ proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} =
|
|
|
|
|
## Makes a move on the board
|
|
|
|
|
result = move
|
|
|
|
|
# Updates checks and pins for the side to move
|
|
|
|
|
self.updateChecksAndPins()
|
|
|
|
|
if not self.isLegal(move):
|
|
|
|
|
return nullMove()
|
|
|
|
|
self.doMove(move)
|
|
|
|
|