CPG/Chess/nimfish/nimfishpkg/movegen.nim

596 lines
27 KiB
Nim

# Copyright 2024 Mattia Giambirtone & All Contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
## Move generation logic
import std/strformat
import std/tables
import std/strutils
import bitboards
import board
import magics
import pieces
import moves
import position
import rays
export bitboards, magics, pieces, moves, position, rays, board
proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
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)
epTarget = self.position.enPassantSquare
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)
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
# Single and double pushes
# If a pawn is pinned diagonally, it cannot push forward
let
# If a pawn is pinned horizontally, it cannot move either. It can move vertically
# though. Thanks to Twipply for the tip on how to get a horizontal pin mask out of
# our orthogonal bitboard :)
horizontalPins = Bitboard((0xFF'u64 shl (rankFromSquare(friendlyKing).uint64 * 8))) and orthogonalPins
pushablePawns = pawns and not diagonalPins and not horizontalPins
singlePushes = (pushablePawns.forwardRelativeTo(sideToMove) and not occupancy) and destinationMask
# 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
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, PromoteToKnight, 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
canCaptureLeftUnpinned = (canCapture and not diagonalPins).forwardLeftRelativeTo(sideToMove) and enemyPieces and destinationMask
canCaptureRightUnpinned = (canCapture and not diagonalPins).forwardRightRelativeTo(sideToMove) and enemyPieces and destinationMask
for pawn in canCaptureRightUnpinned:
let pawnBB = pawn.toBitboard()
if promotionRank.contains(pawn):
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture, promotion))
else:
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture))
for pawn in canCaptureLeftUnpinned:
let pawnBB = pawn.toBitboard()
if promotionRank.contains(pawn):
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture, promotion))
else:
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture))
# Special cases for pawns pinned diagonally that can capture their pinners
let
canCaptureLeft = canCapture.forwardLeftRelativeTo(sideToMove) and enemyPieces and destinationMask
canCaptureRight = canCapture.forwardRightRelativeTo(sideToMove) and enemyPieces and destinationMask
leftPinnedCanCapture = (canCaptureLeft and diagonalPins) and not canCaptureLeftUnpinned
rightPinnedCanCapture = ((canCaptureRight and diagonalPins) and not canCaptureRightUnpinned) and not canCaptureRightUnpinned
for pawn in leftPinnedCanCapture:
let pawnBB = pawn.toBitboard()
if promotionRank.contains(pawn):
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture, promotion))
else:
moves.add(createMove(pawnBB.backwardRightRelativeTo(sideToMove), pawn, Capture))
for pawn in rightPinnedCanCapture:
let pawnBB = pawn.toBitboard()
if promotionRank.contains(pawn):
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture, promotion))
else:
moves.add(createMove(pawnBB.backwardLeftRelativeTo(sideToMove), pawn, Capture))
# En passant captures
var epBitboard = if epTarget != nullSquare(): epTarget.toBitboard() else: Bitboard(0)
if epBitboard != 0:
# See if en passant would create a check
let
# We don't and the destination mask with the ep target because we already check
# whether the king ends up in check. TODO: Fix this in a more idiomatic way
epPawn = epBitboard.backwardRelativeTo(sideToMove)
epLeft = pawns.forwardLeftRelativeTo(sideToMove) and epBitboard
epRight = pawns.forwardRightRelativeTo(sideToMove) and epBitboard
# Note: it's possible for two pawns to both have rights to do an en passant! See
# 4k3/8/8/2PpP3/8/8/8/4K3 w - d6 0 1
if epLeft != 0:
# We basically simulate the en passant and see if the resulting
# occupancy bitboard has the king in check
let
friendlyPawn = epBitboard.backwardRightRelativeTo(sideToMove)
newOccupancy = occupancy and not epPawn and not friendlyPawn or epBitboard
# We also need to temporarily remove the en passant pawn from
# our bitboards, or else functions like getPawnAttacks won't
# get the news that the pawn is gone and will still think the
# king is in check after en passant when it actually isn't
# (see pos fen rnbqkbnr/pppp1ppp/8/2P5/K7/8/PPPP1PPP/RNBQ1BNR b kq - 0 1 moves b7b5 c5b6)
let epPawnSquare = epPawn.toSquare()
let epPiece = self.getPiece(epPawnSquare)
self.removePiece(epPawnSquare)
if not self.isOccupancyAttacked(friendlyKing, newOccupancy):
# En passant does not create a check on the king: all good
moves.add(createMove(friendlyPawn, epBitboard, EnPassant))
self.spawnPiece(epPawnSquare, epPiece)
if epRight != 0:
# Note that this isn't going to be the same pawn from the previous if block!
let
friendlyPawn = epBitboard.backwardLeftRelativeTo(sideToMove)
newOccupancy = occupancy and not epPawn and not friendlyPawn or epBitboard
let epPawnSquare = epPawn.toSquare()
let epPiece = self.getPiece(epPawnSquare)
self.removePiece(epPawnSquare)
if not self.isOccupancyAttacked(friendlyKing, newOccupancy):
# En passant does not create a check on the king: all good
moves.add(createMove(friendlyPawn, epBitboard, EnPassant))
self.spawnPiece(epPawnSquare, epPiece)
proc generateRookMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
let
sideToMove = self.position.sideToMove
occupancy = self.getOccupancy()
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, sideToMove.opposite())
rooks = self.getBitboard(Rook, sideToMove)
queens = self.getBitboard(Queen, sideToMove)
movableRooks = not self.position.diagonalPins and (queens or rooks)
pinMask = self.position.orthogonalPins
pinnedRooks = movableRooks and pinMask
unpinnedRooks = movableRooks and not pinnedRooks
for square in pinnedRooks:
let
blockers = occupancy and Rook.getRelevantBlockers(square)
moveset = getRookMoves(square, blockers)
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 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 destinationMask and not enemyPieces:
moves.add(createMove(square, target))
for target in moveset and enemyPieces and destinationMask:
moves.add(createMove(square, target, Capture))
proc generateBishopMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
let
sideToMove = self.position.sideToMove
occupancy = self.getOccupancy()
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, sideToMove.opposite())
bishops = self.getBitboard(Bishop, sideToMove)
queens = self.getBitboard(Queen, sideToMove)
movableBishops = not self.position.orthogonalPins and (queens or bishops)
pinMask = self.position.diagonalPins
pinnedBishops = movableBishops and pinMask
unpinnedBishops = movableBishops and not pinnedBishops
for square in pinnedBishops:
let
blockers = occupancy and Bishop.getRelevantBlockers(square)
moveset = getBishopMoves(square, blockers)
for target in moveset and pinMask and destinationMask and not enemyPieces:
moves.add(createMove(square, target))
for target in moveset and pinMask 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 destinationMask and not enemyPieces:
moves.add(createMove(square, target))
for target in moveset and enemyPieces and destinationMask:
moves.add(createMove(square, target, Capture))
proc generateKingMoves(self: Chessboard, moves: var MoveList, capturesOnly=false) =
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)
bitboard = getKingAttacks(king.toSquare())
noKingOccupancy = occupancy and not king
if not capturesOnly:
for square in bitboard and not occupancy:
if not self.isOccupancyAttacked(square, noKingOccupancy):
moves.add(createMove(king, square))
for square in bitboard and enemyPieces:
if not self.isOccupancyAttacked(square, noKingOccupancy):
moves.add(createMove(king, square, Capture))
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)
for square in unpinnedKnights:
let bitboard = getKnightAttacks(square)
for target in bitboard and destinationMask and not enemyPieces:
moves.add(createMove(square, target))
for target in bitboard and destinationMask and enemyPieces:
moves.add(createMove(square, target, Capture))
proc generateCastling(self: Chessboard, moves: var MoveList) =
let
sideToMove = self.position.sideToMove
castlingRights = self.canCastle()
kingSquare = self.getBitboard(King, sideToMove).toSquare()
kingPiece = self.getPiece(kingSquare)
if castlingRights.king:
moves.add(createMove(kingSquare, kingPiece.kingSideCastling(), Castle))
if castlingRights.queen:
moves.add(createMove(kingSquare, kingPiece.queenSideCastling(), Castle))
proc generateMoves*(self: Chessboard, moves: var MoveList, capturesOnly: bool = false) =
## Generates the list of all possible legal moves
## in the current position. If capturesOnly is
## true, only capture moves are generated
let
sideToMove = self.position.sideToMove
nonSideToMove = sideToMove.opposite()
self.generateKingMoves(moves, capturesOnly)
if self.position.checkers.countSquares() > 1:
# King is in double check: no need to generate any more
# moves
return
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 destinationMask: Bitboard
if not self.inCheck():
# Not in check: cannot move over friendly pieces
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
# 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 (or moving the king)
let
checker = self.position.checkers.lowestSquare()
checkerBB = checker.toBitboard()
# epTarget = self.position.enPassantSquare
# checkerPiece = self.getPiece(checker)
destinationMask = getRayBetween(checker, self.getBitboard(King, sideToMove).toSquare()) or checkerBB
# TODO: This doesn't really work. I've addressed the issue for now, but it's kinda ugly. Find a better
# solution
# if checkerPiece.kind == Pawn and checkerBB.backwardRelativeTo(checkerPiece.color).toSquare() == epTarget:
# # We are in check by a pawn that pushed two squares: add the ep target square to the set of
# # squares that our friendly pieces can move to in order to resolve it. This will do nothing
# # for most pieces, because the move generators won't allow them to move there, but it does matter
# # for pawns
# destinationMask = destinationMask or epTarget.toBitboard()
if capturesOnly:
# Note: This does not cover en passant (which is good because it's a capture,
# but the "fix" stands on flimsy ground)
destinationMask = destinationMask and self.getOccupancyFor(nonSideToMove)
self.generatePawnMoves(moves, destinationMask)
self.generateKnightMoves(moves, destinationMask)
self.generateRookMoves(moves, destinationMask)
self.generateBishopMoves(moves, destinationMask)
# Queens are just handled rooks + bishops
proc doMove*(self: Chessboard, move: Move) =
## Internal function called by makeMove after
## performing legality checks. Can be used in
## performance-critical paths where a move is
## already known to be legal (i.e. during search)
# Record final position for future reference
self.positions.add(self.position)
# Final checks
let piece = self.getPiece(move.startSquare)
when not defined(danger):
doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}"
var
halfMoveClock = self.position.halfMoveClock
fullMoveCount = self.position.fullMoveCount
enPassantTarget = nullSquare()
# Needed to detect draw by the 50 move rule
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
# Number of half-moves since the last reversible half-move
halfMoveClock = 0
else:
inc(halfMoveClock)
if piece.color == Black:
inc(fullMoveCount)
if move.isDoublePush():
enPassantTarget = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
# Create new position
self.position = Position(plyFromRoot: self.position.plyFromRoot + 1,
halfMoveClock: halfMoveClock,
fullMoveCount: fullMoveCount,
sideToMove: self.position.sideToMove.opposite(),
enPassantSquare: enPassantTarget,
pieces: self.position.pieces,
castlingAvailability: self.position.castlingAvailability
)
# Update position metadata
if move.isEnPassant():
# Make the en passant pawn disappear
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare())
if move.isCastling() or piece.kind == King:
# If the king has moved, all castling rights for the side to
# move are revoked
self.position.castlingAvailability[piece.color.int] = (false, false)
if move.isCastling():
# Move the rook where it belongs
if move.targetSquare == piece.kingSideCastling():
let rook = self.getPiece(piece.color.kingSideRook())
self.movePiece(piece.color.kingSideRook(), rook.kingSideCastling())
if move.targetSquare == piece.queenSideCastling():
let rook = self.getPiece(piece.color.queenSideRook())
self.movePiece(piece.color.queenSideRook(), rook.queenSideCastling())
if piece.kind == Rook:
# If a rook on either side moves, castling rights are permanently revoked
# on that side
if move.startSquare == piece.color.kingSideRook():
self.position.castlingAvailability[piece.color.int].king = false
elif move.startSquare == piece.color.queenSideRook():
self.position.castlingAvailability[piece.color.int].queen = false
if move.isCapture():
# Get rid of captured pieces
let captured = self.getPiece(move.targetSquare)
self.removePiece(move.targetSquare)
# If a rook has been captured, castling on that side is prohibited
if captured.kind == Rook:
if move.targetSquare == captured.color.kingSideRook():
self.position.castlingAvailability[captured.color.int].king = false
elif move.targetSquare == captured.color.queenSideRook():
self.position.castlingAvailability[captured.color.int].queen = false
# Move the piece to its target square
self.movePiece(move)
if move.isPromotion():
# Move is a pawn promotion: get rid of the pawn
# and spawn a new piece
self.removePiece(move.targetSquare)
case move.getPromotionType():
of PromoteToBishop:
self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color))
of PromoteToKnight:
self.spawnPiece(move.targetSquare, Piece(kind: Knight, color: piece.color))
of PromoteToRook:
self.spawnPiece(move.targetSquare, Piece(kind: Rook, color: piece.color))
of PromoteToQueen:
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
else:
# Unreachable
discard
# Updates checks and pins for the (new) side to move
self.updateChecksAndPins()
# Update zobrist key
self.hash()
discard self.drawByRepetition()
proc isLegal*(self: Chessboard, move: Move): bool {.inline.} =
## Returns whether the given move is legal
var moves = newMoveList()
self.generateMoves(moves)
return move in moves
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
if not self.isLegal(move):
return nullMove()
self.doMove(move)
proc unmakeMove*(self: Chessboard) =
## Reverts to the previous board position,
## if one exists
self.position = self.positions.pop()
self.update()
self.hash()
## Testing stuff
proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
doAssert piece.kind == kind and piece.color == color, &"expected piece of kind {kind} and color {color}, got {piece.kind} / {piece.color} instead"
proc testPieceCount(board: Chessboard, kind: PieceKind, color: PieceColor, count: int) =
let pieces = board.countPieces(kind, color)
doAssert pieces == count, &"expected {count} pieces of kind {kind} and color {color}, got {pieces} instead"
proc testPieceBitboard(bitboard: Bitboard, squares: seq[Square]) =
var i = 0
for square in bitboard:
doAssert squares[i] == square, &"squares[{i}] != bitboard[i]: {squares[i]} != {square}"
inc(i)
if i != squares.len():
doAssert false, &"bitboard.len() ({i}) != squares.len() ({squares.len()})"
const testFens = staticRead("../../tests/all.txt").splitLines()
const benchFens = staticRead("../../tests/all.txt").splitLines()
proc basicTests* =
for fen in testFens:
doAssert fen == newChessboardFromFEN(fen).toFEN()
for fen in benchFens:
var
board = newChessboardFromFEN(fen)
hashes = newTable[ZobristKey, Move]()
moves = newMoveList()
board.generateMoves(moves)
for move in moves:
board.makeMove(move)
let
currentFEN = board.toFEN()
pos = board.position
key = pos.zobristKey
board.unmakeMove()
doAssert not hashes.contains(key), &"{fen} has zobrist collisions {move} -> {hashes[key]}"
hashes[key] = move
var board = newDefaultChessboard()
# Ensure correct number of pieces
testPieceCount(board, Pawn, White, 8)
testPieceCount(board, Pawn, Black, 8)
testPieceCount(board, Knight, White, 2)
testPieceCount(board, Knight, Black, 2)
testPieceCount(board, Bishop, White, 2)
testPieceCount(board, Bishop, Black, 2)
testPieceCount(board, Rook, White, 2)
testPieceCount(board, Rook, Black, 2)
testPieceCount(board, Queen, White, 1)
testPieceCount(board, Queen, Black, 1)
testPieceCount(board, King, White, 1)
testPieceCount(board, King, Black, 1)
# Ensure pieces are in the correct squares. This is testing the FEN
# parser
# Pawns
for loc in ["a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2"]:
testPiece(board.getPiece(loc), Pawn, White)
for loc in ["a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7"]:
testPiece(board.getPiece(loc), Pawn, Black)
# Rooks
testPiece(board.getPiece("a1"), Rook, White)
testPiece(board.getPiece("h1"), Rook, White)
testPiece(board.getPiece("a8"), Rook, Black)
testPiece(board.getPiece("h8"), Rook, Black)
# Knights
testPiece(board.getPiece("b1"), Knight, White)
testPiece(board.getPiece("g1"), Knight, White)
testPiece(board.getPiece("b8"), Knight, Black)
testPiece(board.getPiece("g8"), Knight, Black)
# Bishops
testPiece(board.getPiece("c1"), Bishop, White)
testPiece(board.getPiece("f1"), Bishop, White)
testPiece(board.getPiece("c8"), Bishop, Black)
testPiece(board.getPiece("f8"), Bishop, Black)
# Kings
testPiece(board.getPiece("e1"), King, White)
testPiece(board.getPiece("e8"), King, Black)
# Queens
testPiece(board.getPiece("d1"), Queen, White)
testPiece(board.getPiece("d8"), Queen, Black)
# Ensure our bitboards match with the board
let
whitePawns = board.getBitboard(Pawn, White)
whiteKnights = board.getBitboard(Knight, White)
whiteBishops = board.getBitboard(Bishop, White)
whiteRooks = board.getBitboard(Rook, White)
whiteQueens = board.getBitboard(Queen, White)
whiteKing = board.getBitboard(King, White)
blackPawns = board.getBitboard(Pawn, Black)
blackKnights = board.getBitboard(Knight, Black)
blackBishops = board.getBitboard(Bishop, Black)
blackRooks = board.getBitboard(Rook, Black)
blackQueens = board.getBitboard(Queen, Black)
blackKing = board.getBitboard(King, Black)
whitePawnSquares = @[makeSquare(6'i8, 0'i8), makeSquare(6, 1), makeSquare(6, 2), makeSquare(6, 3), makeSquare(6, 4), makeSquare(6, 5), makeSquare(6, 6), makeSquare(6, 7)]
whiteKnightSquares = @[makeSquare(7'i8, 1'i8), makeSquare(7, 6)]
whiteBishopSquares = @[makeSquare(7'i8, 2'i8), makeSquare(7, 5)]
whiteRookSquares = @[makeSquare(7'i8, 0'i8), makeSquare(7, 7)]
whiteQueenSquares = @[makeSquare(7'i8, 3'i8)]
whiteKingSquares = @[makeSquare(7'i8, 4'i8)]
blackPawnSquares = @[makeSquare(1'i8, 0'i8), makeSquare(1, 1), makeSquare(1, 2), makeSquare(1, 3), makeSquare(1, 4), makeSquare(1, 5), makeSquare(1, 6), makeSquare(1, 7)]
blackKnightSquares = @[makeSquare(0'i8, 1'i8), makeSquare(0, 6)]
blackBishopSquares = @[makeSquare(0'i8, 2'i8), makeSquare(0, 5)]
blackRookSquares = @[makeSquare(0'i8, 0'i8), makeSquare(0, 7)]
blackQueenSquares = @[makeSquare(0'i8, 3'i8)]
blackKingSquares = @[makeSquare(0'i8, 4'i8)]
testPieceBitboard(whitePawns, whitePawnSquares)
testPieceBitboard(whiteKnights, whiteKnightSquares)
testPieceBitboard(whiteBishops, whiteBishopSquares)
testPieceBitboard(whiteRooks, whiteRookSquares)
testPieceBitboard(whiteQueens, whiteQueenSquares)
testPieceBitboard(whiteKing, whiteKingSquares)
testPieceBitboard(blackPawns, blackPawnSquares)
testPieceBitboard(blackKnights, blackKnightSquares)
testPieceBitboard(blackBishops, blackBishopSquares)
testPieceBitboard(blackRooks, blackRookSquares)
testPieceBitboard(blackQueens, blackQueenSquares)
testPieceBitboard(blackKing, blackKingSquares)
when isMainModule:
basicTests()