Bug fixes and huge performance improvement. Initial work on pins
This commit is contained in:
parent
4a9deb517a
commit
d5bcd15c48
|
@ -21,6 +21,7 @@ import nimfishpkg/magics
|
|||
import nimfishpkg/pieces
|
||||
import nimfishpkg/moves
|
||||
import nimfishpkg/position
|
||||
import nimfishpkg/rays
|
||||
|
||||
|
||||
export bitboards, magics, pieces, moves, position
|
||||
|
@ -248,7 +249,7 @@ func getKingAttacks(self: Chessboard, square: Square, attacker: PieceColor): Bit
|
|||
result = Bitboard(0)
|
||||
let
|
||||
king = self.getBitboard(King, attacker)
|
||||
if (getKingBitboard(square) and king) != 0:
|
||||
if (getKingAttacks(square) and king) != 0:
|
||||
result = result or king
|
||||
|
||||
|
||||
|
@ -260,7 +261,7 @@ func getKnightAttacks(self: Chessboard, square: Square, attacker: PieceColor): B
|
|||
result = Bitboard(0)
|
||||
for knight in knights:
|
||||
let knightBB = knight.toBitboard()
|
||||
if (getKnightBitboard(knight) and knightBB) != 0:
|
||||
if (getKnightAttacks(knight) and knightBB) != 0:
|
||||
result = result or knightBB
|
||||
|
||||
|
||||
|
@ -298,10 +299,14 @@ proc getAttacksTo*(self: Chessboard, square: Square, attacker: PieceColor): Bitb
|
|||
proc updateChecksAndPins(self: Chessboard) =
|
||||
## Updates internal metadata about checks and
|
||||
## pinned pieces
|
||||
|
||||
# *Ahem*, stolen from https://github.com/Ciekce/voidstar/blob/424ac4624011271c4d1dbd743602c23f6dbda1de/src/position.rs
|
||||
# Can you tell I'm a *great* coder?
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
|
||||
friendlyKingBB = self.getBitboard(King, sideToMove)
|
||||
friendlyKing = friendlyKingBB.toSquare()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
|
||||
|
@ -311,6 +316,24 @@ proc updateChecksAndPins(self: Chessboard) =
|
|||
self.position.diagonalPins = Bitboard(0)
|
||||
self.position.orthogonalPins = Bitboard(0)
|
||||
|
||||
let
|
||||
diagonalAttackers = self.getBitboard(Queen, nonSideToMove) or self.getBitboard(Bishop, nonSideToMove)
|
||||
orthogonalAttackers = self.getBitboard(Queen, nonSideToMove) or self.getBitboard(Rook, nonSideToMove)
|
||||
canPinDiagonally = diagonalAttackers and getBishopMoves(friendlyKing, enemyPieces)
|
||||
canPinOrthogonally = orthogonalAttackers and getRookMoves(friendlyKing, enemyPieces)
|
||||
|
||||
for piece in canPinDiagonally:
|
||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||
|
||||
if (pinningRay and friendlyKingBB).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
|
||||
|
||||
|
||||
func inCheck(self: Chessboard): bool {.inline.} =
|
||||
## Returns if the current side to move is in check
|
||||
|
@ -499,12 +522,13 @@ proc generatePawnMovements(self: Chessboard, moves: var MoveList) =
|
|||
|
||||
|
||||
proc generatePawnCaptures(self: Chessboard, moves: var MoveList) =
|
||||
## Helper of generatePawnMoves for generating all capture
|
||||
## pawn moves
|
||||
## 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)
|
||||
|
@ -512,7 +536,9 @@ proc generatePawnCaptures(self: Chessboard, moves: var MoveList) =
|
|||
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
||||
epTarget = self.position.enPassantSquare
|
||||
var epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
|
||||
epBitboard = epBitboard and enemyPawns
|
||||
# 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))
|
||||
|
@ -530,7 +556,7 @@ proc generatePawnCaptures(self: Chessboard, moves: var MoveList) =
|
|||
|
||||
|
||||
proc generatePawnPromotions(self: Chessboard, moves: var MoveList) =
|
||||
## Helper of generatePawnMoves for generating all pawn promotion
|
||||
## Helper of generatePawnMoves for generating all promotion
|
||||
## moves
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
|
@ -555,14 +581,26 @@ proc generateRookMoves(self: Chessboard, moves: var MoveList) =
|
|||
occupancy = self.getOccupancy()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, nonSideToMove)
|
||||
rooks = self.getBitboard(Rook, sideToMove) or self.getBitboard(Queen, sideToMove)
|
||||
for square in rooks:
|
||||
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 not occupancy and pinMask:
|
||||
moves.add(createMove(square, target))
|
||||
for target in moveset and enemyPieces and pinMask:
|
||||
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:
|
||||
moves.add(createMove(square, target))
|
||||
# Captures
|
||||
for target in moveset and enemyPieces:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
|
||||
|
@ -571,11 +609,24 @@ proc generateBishopMoves(self: Chessboard, moves: var MoveList) =
|
|||
## 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)
|
||||
occupancy = self.getOccupancy()
|
||||
bishops = self.getBitboard(Bishop, sideToMove) or self.getBitboard(Queen, sideToMove)
|
||||
for square in bishops:
|
||||
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 not occupancy:
|
||||
moves.add(createMove(square, target))
|
||||
for target in moveset and enemyPieces and pinMask:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
for square in unpinnedBishops:
|
||||
let
|
||||
blockers = occupancy and Bishop.getRelevantBlockers(square)
|
||||
moveset = getBishopMoves(square, blockers)
|
||||
|
@ -597,15 +648,13 @@ proc generateKingMoves(self: Chessboard, moves: var MoveList) =
|
|||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
king = self.getBitboard(King, sideToMove)
|
||||
moveIdx = king.toSquare().uint64
|
||||
occupancy = self.getOccupancy()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||
# Regular moves
|
||||
for square in KING_BITBOARDS[moveIdx] and not occupancy:
|
||||
bitboard = getKingAttacks(king.toSquare())
|
||||
for square in bitboard and not occupancy:
|
||||
moves.add(createMove(king, square))
|
||||
# Captures
|
||||
for square in KING_BITBOARDS[moveIdx] and enemyPieces:
|
||||
for square in bitboard and enemyPieces:
|
||||
moves.add(createMove(king, square, Capture))
|
||||
|
||||
|
||||
|
@ -616,13 +665,14 @@ proc generateKnightMoves(self: Chessboard, moves: var MoveList)=
|
|||
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 knights:
|
||||
# Regular moves
|
||||
for target in KNIGHT_BITBOARDS[square.uint64] and not occupancy:
|
||||
for square in unpinnedKnights:
|
||||
let bitboard = getKnightAttacks(square)
|
||||
for target in bitboard and not occupancy:
|
||||
moves.add(createMove(square, target))
|
||||
# Captures
|
||||
for target in KNIGHT_BITBOARDS[square.uint64] and enemyPieces:
|
||||
for target in bitboard and enemyPieces:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
|
||||
|
||||
|
@ -635,6 +685,14 @@ proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
|||
# TODO: Check for draw by insufficient material
|
||||
# TODO: Check for repetitions (requires zobrist hashing + table)
|
||||
self.generateKingMoves(moves)
|
||||
if self.position.checkers.countSquares() > 1:
|
||||
# King is in double check: no need to generate any more
|
||||
# moves
|
||||
return
|
||||
if not self.inCheck():
|
||||
# TODO: Castling
|
||||
discard
|
||||
|
||||
self.generatePawnMoves(moves)
|
||||
self.generateKnightMoves(moves)
|
||||
self.generateSlidingMoves(moves)
|
||||
|
|
|
@ -52,6 +52,7 @@ func clearBit*(a: var Bitboard, bit: SomeInteger) = a.uint64.clearBit(bit)
|
|||
func setBit*(a: var Bitboard, bit: SomeInteger) = a.uint64.setBit(bit)
|
||||
func clearBit*(a: var Bitboard, bit: Square) = a.uint64.clearBit(bit.int)
|
||||
func setBit*(a: var Bitboard, bit: Square) = a.uint64.setBit(bit.int)
|
||||
func removed*(a, b: Bitboard): Bitboard = a and not b
|
||||
|
||||
|
||||
func countSquares*(self: Bitboard): int {.inline.} =
|
||||
|
@ -103,10 +104,9 @@ iterator subsets*(self: Bitboard): Bitboard =
|
|||
var subset = Bitboard(0)
|
||||
while true:
|
||||
subset = (subset - self) and self
|
||||
yield subset
|
||||
if subset == 0:
|
||||
break
|
||||
yield subset
|
||||
|
||||
|
||||
iterator pairs*(self: Bitboard): tuple[i: int, sq: Square] =
|
||||
var i = 0
|
||||
|
@ -167,8 +167,6 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi
|
|||
of Right:
|
||||
return bitboard shl 1
|
||||
of Black:
|
||||
# The directions for black are just the opposite of those for white,
|
||||
# so we avoid duplicating any code
|
||||
case direction:
|
||||
of Forward:
|
||||
return bitboard shl 8
|
||||
|
@ -284,9 +282,9 @@ func computeKnightBitboards: array[64, Bitboard] {.compileTime.} =
|
|||
|
||||
|
||||
const
|
||||
KING_BITBOARDS* = computeKingBitboards()
|
||||
KNIGHT_BITBOARDS* = computeKnightBitboards()
|
||||
KING_BITBOARDS = computeKingBitboards()
|
||||
KNIGHT_BITBOARDS = computeKnightBitboards()
|
||||
|
||||
|
||||
func getKingBitboard*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square.int]
|
||||
func getKnightBitboard*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square.int]
|
||||
func getKingAttacks*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square.int]
|
||||
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square.int]
|
||||
|
|
|
@ -137,8 +137,8 @@ proc getRookMoves*(square: Square, blockers: Bitboard): Bitboard =
|
|||
## square with the given blockers bitboard
|
||||
let
|
||||
magic = ROOK_MAGICS[square.uint]
|
||||
moves = ROOK_MOVES[square.uint]
|
||||
return moves[getIndex(magic, blockers)]
|
||||
movesAddr = addr ROOK_MOVES[square.uint]
|
||||
return movesAddr[][getIndex(magic, blockers)]
|
||||
|
||||
|
||||
proc getBishopMoves*(square: Square, blockers: Bitboard): Bitboard =
|
||||
|
@ -146,8 +146,20 @@ proc getBishopMoves*(square: Square, blockers: Bitboard): Bitboard =
|
|||
## square with the given blockers bitboard
|
||||
let
|
||||
magic = BISHOP_MAGICS[square.uint]
|
||||
moves = BISHOP_MOVES[square.uint]
|
||||
return moves[getIndex(magic, blockers)]
|
||||
movesAddr = addr BISHOP_MOVES[square.uint]
|
||||
return movesAddr[][getIndex(magic, blockers)]
|
||||
|
||||
|
||||
proc getRookMagic*(square: Square): MagicEntry =
|
||||
## Returns the magic entry for a rook
|
||||
## on the given square
|
||||
return ROOK_MAGICS[square.uint]
|
||||
|
||||
|
||||
proc getBishopMagic*(square: Square): MagicEntry =
|
||||
## Returns the magic entry for a bishop
|
||||
## on the given square
|
||||
return BISHOP_MAGICS[square.uint]
|
||||
|
||||
|
||||
# Precomputed blocker masks. Only pieces on these bitboards
|
||||
|
@ -188,7 +200,7 @@ func tryOffset(square: Square, df, dr: SomeInteger): Square =
|
|||
return makeSquare(rank + dr, file + df)
|
||||
|
||||
|
||||
proc getMoveSet*(kind: PieceKind, square: Square, blocker: Bitboard): Bitboard =
|
||||
proc getMoveset*(kind: PieceKind, square: Square, blocker: Bitboard): Bitboard =
|
||||
## A naive implementation of sliding attacks. Returns the moves that can
|
||||
## be performed from the given piece at the given square with the given
|
||||
## blocker mask
|
||||
|
@ -223,8 +235,8 @@ proc attemptMagicTableCreation(kind: PieceKind, square: Square, entry: MagicEntr
|
|||
# for several different blocker configurations, as
|
||||
# many of them (while different) produce the same
|
||||
# results
|
||||
var moves = kind.getMoveSet(square, blocker)
|
||||
if result.table[index] == 0:
|
||||
var moves = kind.getMoveset(square, blocker)
|
||||
if result.table[index] == Bitboard(0):
|
||||
# No entry here, yet, so no problem!
|
||||
result.table[index] = moves
|
||||
elif result.table[index] != moves:
|
||||
|
@ -265,7 +277,7 @@ proc findMagic(kind: PieceKind, square: Square, indexBits: uint8): tuple[entry:
|
|||
# Again, this is stolen from the article. A magic number
|
||||
# is only useful if it has high bit sparsity, so we AND
|
||||
# together a bunch of random values to get a number that's
|
||||
# hopefully better
|
||||
# hopefully better than a single one
|
||||
let
|
||||
magic = rand.next() and rand.next() and rand.next()
|
||||
entry = MagicEntry(mask: mask, value: magic, indexBits: indexBits)
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
import std/strutils
|
||||
import std/strformat
|
||||
|
||||
|
||||
import bitboards
|
||||
import magics
|
||||
import pieces
|
||||
import moves
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import bitboards
|
||||
import magics
|
||||
|
||||
# Stolen from https://github.com/Ciekce/voidstar/blob/main/src/rays.rs :D
|
||||
|
||||
|
||||
|
||||
proc computeRaysBetweenSquares: array[64, array[64, Bitboard]] =
|
||||
## Computes all sliding rays between each pair of squares
|
||||
## in the chessboard
|
||||
for i in 0..63:
|
||||
let
|
||||
source = Square(i)
|
||||
sourceBitboard = source.toBitboard()
|
||||
rooks = getRookMoves(source, Bitboard(0))
|
||||
bishops = getBishopMoves(source, Bitboard(0))
|
||||
for j in 0..63:
|
||||
let target = Square(j)
|
||||
if target == source:
|
||||
result[i][j] = Bitboard(0)
|
||||
else:
|
||||
let targetBitboard = target.toBitboard()
|
||||
if rooks.contains(target):
|
||||
result[i][j] = getRookMoves(source, targetBitboard) and getRookMoves(target, sourceBitboard)
|
||||
elif bishops.contains(target):
|
||||
result[i][j] = getBishopMoves(source, targetBitboard) and getBishopMoves(target, sourceBitboard)
|
||||
else:
|
||||
result[i][j] = Bitboard(0)
|
||||
|
||||
|
||||
let BETWEEN_RAYS = computeRaysBetweenSquares()
|
||||
|
||||
|
||||
proc getRayBetween*(source, target: Square): Bitboard {.inline.} = BETWEEN_RAYS[source.int][target.int]
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue