Added knight movegen. Updated attack tracking. Fix bugs
This commit is contained in:
parent
3bb2cc7c66
commit
a07e9cc475
|
@ -112,30 +112,6 @@ func pretty*(self: Bitboard): string =
|
|||
func `$`*(self: Bitboard): string = self.pretty()
|
||||
|
||||
|
||||
func computeDiagonalBitboards: array[14, Bitboard] {.compileTime.} =
|
||||
## Precomputes all the bitboards for diagonals
|
||||
result[0] = Bitboard(0x8040201008040201'u64)
|
||||
var
|
||||
col = 1
|
||||
i = 0
|
||||
# Left to right
|
||||
while col < 8:
|
||||
result[col] = Bitboard(0x8040201008040201'u64) shl (8 * col)
|
||||
inc(col)
|
||||
inc(i)
|
||||
result[i] = Bitboard(0x102040810204080'u64)
|
||||
inc(i)
|
||||
col = 1
|
||||
# Right to left
|
||||
while col < 7:
|
||||
result[i] = Bitboard(0x102040810204080'u64) shr (8 * col)
|
||||
inc(i)
|
||||
inc(col)
|
||||
|
||||
|
||||
const diagonalBitboards = computeDiagonalBitboards()
|
||||
|
||||
|
||||
func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Direction): Bitboard =
|
||||
## Get a bitmask relative to the given bitboard
|
||||
## for the given direction for a piece of the
|
||||
|
@ -185,6 +161,8 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi
|
|||
|
||||
func getLastRank*(color: PieceColor): Bitboard = (if color == White: getRankMask(0) else: getRankMask(7))
|
||||
func getFirstRank*(color: PieceColor): Bitboard = (if color == White: getRankMask(7) else: getRankMask(0))
|
||||
func getLeftmostFile*(color: PieceColor): Bitboard = (if color == White: getFileMask(0) else: getFileMask(7))
|
||||
func getRightmostFile*(color: PieceColor): Bitboard = (if color == White: getFileMask(7) else: getFileMask(0))
|
||||
|
||||
|
||||
func getDirectionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard =
|
||||
|
@ -199,27 +177,104 @@ func doubleForwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self
|
|||
func backwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Backward)
|
||||
func doubleBackwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.backwardRelativeTo(side).backwardRelativeTo(side)
|
||||
|
||||
func leftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Left) and not getFileMask(0)
|
||||
func rightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Right) and not getFileMask(7)
|
||||
func leftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Left) and not getRightmostFile(side)
|
||||
func rightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Right) and not getLeftmostFile(side)
|
||||
|
||||
|
||||
# We mask off the first and last ranks for
|
||||
# left and right movements respectively to
|
||||
# avoid weird wraparounds
|
||||
# We mask off the opposide files to make sure there are
|
||||
# no weird wraparounds when moving diagonally
|
||||
func forwardRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
getDirectionMask(self, side, ForwardRight) and not getFileMask(7)
|
||||
getDirectionMask(self, side, ForwardRight) and not getLeftmostFile(side)
|
||||
|
||||
|
||||
func forwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
getDirectionMask(self, side, ForwardLeft) and not getFileMask(0)
|
||||
getDirectionMask(self, side, ForwardLeft) and not getRightmostFile(side)
|
||||
|
||||
|
||||
func backwardRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
getDirectionMask(self, side, BackwardRight)
|
||||
getDirectionMask(self, side, BackwardRight) and not getLeftmostFile(side)
|
||||
|
||||
|
||||
func backwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
|
||||
getDirectionMask(self, side, BackwardLeft)
|
||||
getDirectionMask(self, side, BackwardLeft) and not getRightmostFile(side)
|
||||
|
||||
|
||||
# func forwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Forward)
|
||||
func longKnightUpLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.doubleForwardRelativeTo(side).leftRelativeTo(side)
|
||||
func longKnightUpRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.doubleForwardRelativeTo(side).rightRelativeTo(side)
|
||||
func longKnightDownLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.doubleBackwardRelativeTo(side).leftRelativeTo(side)
|
||||
func longKnightDownRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.doubleBackwardRelativeTo(side).rightRelativeTo(side)
|
||||
|
||||
func shortKnightUpLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.forwardRelativeTo(side).leftRelativeTo(side).leftRelativeTo(side)
|
||||
func shortKnightUpRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.forwardRelativeTo(side).rightRelativeTo(side).rightRelativeTo(side)
|
||||
func shortKnightDownLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.backwardRelativeTo(side).leftRelativeTo(side).leftRelativeTo(side)
|
||||
func shortKnightDownRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.backwardRelativeTo(side).rightRelativeTo(side).rightRelativeTo(side)
|
||||
|
||||
# We precompute as much stuff as possible: lookup tables are fast!
|
||||
|
||||
func computeDiagonalBitboards: array[14, Bitboard] {.compileTime.} =
|
||||
## Precomputes all the bitboards for diagonals
|
||||
result[0] = Bitboard(0x8040201008040201'u64)
|
||||
var
|
||||
col = 1
|
||||
i = 0
|
||||
# Left to right
|
||||
while col < 8:
|
||||
result[col] = Bitboard(0x8040201008040201'u64) shl (8 * col)
|
||||
inc(col)
|
||||
inc(i)
|
||||
result[i] = Bitboard(0x102040810204080'u64)
|
||||
inc(i)
|
||||
col = 1
|
||||
# Right to left
|
||||
while col < 7:
|
||||
result[i] = Bitboard(0x102040810204080'u64) shr (8 * col)
|
||||
inc(i)
|
||||
inc(col)
|
||||
|
||||
|
||||
func computeKingBitboards: array[64, Bitboard] =
|
||||
## Precomputes all the movement bitboards for the king
|
||||
for i in 0'u64..63:
|
||||
let king = i.toBitboard()
|
||||
# It doesn't really matter which side we generate
|
||||
# the move for, they're identical for both
|
||||
var movements = king.forwardRelativeTo(White)
|
||||
movements = movements or king.forwardLeftRelativeTo(White)
|
||||
movements = movements or king.leftRelativeTo(White)
|
||||
movements = movements or king.rightRelativeTo(White)
|
||||
movements = movements or king.backwardRelativeTo(White)
|
||||
movements = movements or king.forwardLeftRelativeTo(White)
|
||||
movements = movements or king.backwardRightRelativeTo(White)
|
||||
movements = movements or king.backwardLeftRelativeTo(White)
|
||||
# We don't *need* to mask the king off: the engine already masks off
|
||||
# the board's occupancy when generating moves, but it may be useful for
|
||||
# other parts of the movegen for this stuff not to say "the king can just
|
||||
# stay still", so we do it anyway
|
||||
movements = movements and not king
|
||||
result[i] = movements
|
||||
|
||||
|
||||
func computeKnightBitboards: array[64, Bitboard] =
|
||||
## Precomputes all the movement bitboards for knights
|
||||
for i in 0'u64..63:
|
||||
let knight = i.toBitboard()
|
||||
# It doesn't really matter which side we generate
|
||||
# the move for, they're identical for both
|
||||
var movements = knight.longKnightDownLeftRelativeTo(White)
|
||||
movements = movements or knight.longKnightDownRightRelativeTo(White)
|
||||
movements = movements or knight.longKnightUpLeftRelativeTo(White)
|
||||
movements = movements or knight.longKnightUpRightRelativeTo(White)
|
||||
movements = movements or knight.shortKnightDownLeftRelativeTo(White)
|
||||
movements = movements or knight.shortKnightDownRightRelativeTo(White)
|
||||
movements = movements or knight.shortKnightUpLeftRelativeTo(White)
|
||||
movements = movements or knight.shortKnightUpRightRelativeTo(White)
|
||||
result[i] = movements
|
||||
|
||||
|
||||
# Precomputing stuff is *very* helpful for chess, it turns out
|
||||
const DIAGONAL_BITBOARDS* = computeDiagonalBitboards()
|
||||
# For some reason nim freaks out if I try to call computeKingBitboards()
|
||||
# at compile-time. ¯\_(ツ)_/¯
|
||||
let
|
||||
KING_BITBOARDS* = computeKingBitboards()
|
||||
KNIGHT_BITBOARDS* = computeKnightBitboards()
|
||||
|
|
|
@ -539,6 +539,29 @@ proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bit
|
|||
return pawns and (bottomLeft or bottomRight)
|
||||
|
||||
|
||||
proc getKingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the attack bitboard for the given square from
|
||||
## the king of the given side
|
||||
result = Bitboard(0)
|
||||
let
|
||||
sq = square.toBitboard()
|
||||
king = self.getBitboard(King, attacker)
|
||||
if (sq and KING_BITBOARDS[square.uint64]) != 0:
|
||||
result = result or sq
|
||||
|
||||
|
||||
proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the attack bitboard for the given square from
|
||||
## the knights of the given side
|
||||
let
|
||||
sq = square.toBitboard()
|
||||
knights = self.getBitboard(Knight, attacker)
|
||||
result = Bitboard(0)
|
||||
for knight in knights:
|
||||
if (sq and KNIGHT_BITBOARDS[knight.uint64]) != 0:
|
||||
result = result or knight.toBitboard()
|
||||
|
||||
|
||||
proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Computes the attack bitboard for the given square from
|
||||
## the given side
|
||||
|
@ -546,8 +569,17 @@ proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitbo
|
|||
let
|
||||
squareBitboard = square.toBitboard()
|
||||
result = result or self.getPawnAttacks(square, attacker)
|
||||
result = result or self.getKingAttacks(square, attacker)
|
||||
result = result or self.getKnightAttacks(square, attacker)
|
||||
|
||||
|
||||
proc getCapturablePieces(self: ChessBoard, side: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the set of pieces of the given color that can
|
||||
## be captured
|
||||
|
||||
# Just a handy helper to filter out the king and avoid code duplication
|
||||
return self.getOccupancyFor(side) and not self.getBitboard(King, side)
|
||||
|
||||
|
||||
proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generatePawnMoves for generating all non-capture
|
||||
|
@ -576,7 +608,7 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
|
|||
nonSideToMove = sideToMove.opposite()
|
||||
pawns = self.getBitboard(Pawn, sideToMove)
|
||||
# We can only capture enemy pieces (except the king)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||
enemyPieces = self.getCapturablePieces(nonSideToMove)
|
||||
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
|
||||
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
|
||||
epTarget = self.getEnPassantTarget()
|
||||
|
@ -625,26 +657,33 @@ proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
|
|||
let
|
||||
sideToMove = self.getSideToMove()
|
||||
king = self.getBitboard(King, sideToMove)
|
||||
moveIdx = king.toSquare().uint64
|
||||
allowedSquares = not self.getOccupancy()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||
var movements = king.forwardRelativeTo(sideToMove)
|
||||
movements = movements or king.rightRelativeTo(sideToMove)
|
||||
movements = movements or king.backwardRelativeTo(sideToMove)
|
||||
movements = movements or king.leftRelativeTo(sideToMove)
|
||||
movements = movements or king.forwardRightRelativeTo(sideToMove)
|
||||
movements = movements or king.forwardLeftRelativeTo(sideToMove)
|
||||
enemyPieces = self.getCapturablePieces(nonSideToMove)
|
||||
# Regular moves
|
||||
for square in movements and allowedSquares:
|
||||
for square in KING_BITBOARDS[moveIdx] and allowedSquares:
|
||||
moves.add(createMove(king, square))
|
||||
# Captures
|
||||
for square in movements and enemyPieces:
|
||||
for square in KING_BITBOARDS[moveIdx] and enemyPieces:
|
||||
moves.add(createMove(king, square, Capture))
|
||||
|
||||
|
||||
proc generateKnightMoves(self: ChessBoard, moves: var MoveList)=
|
||||
## Generates all the legal knight moves for the side to move
|
||||
|
||||
let
|
||||
sideToMove = self.getSideToMove()
|
||||
knights = self.getBitboard(Knight, sideToMove)
|
||||
allowedSquares = not self.getOccupancy()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
enemyPieces = self.getCapturablePieces(nonSideToMove)
|
||||
for square in knights:
|
||||
# Regular moves
|
||||
for target in KNIGHT_BITBOARDS[square.uint64] and allowedSquares:
|
||||
moves.add(createMove(square, target))
|
||||
# Captures
|
||||
for target in KNIGHT_BITBOARDS[square.uint64] and enemyPieces:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
|
||||
|
||||
proc checkInsufficientMaterialPieceCount(self: ChessBoard, color: PieceColor): bool =
|
||||
|
@ -718,6 +757,7 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) =
|
|||
# TODO: Check for repetitions (requires zobrist hashing + table)
|
||||
self.generatePawnMoves(moves)
|
||||
self.generateKingMoves(moves)
|
||||
self.generateKnightMoves(moves)
|
||||
# TODO: all pieces
|
||||
|
||||
|
||||
|
@ -1755,13 +1795,12 @@ when isMainModule:
|
|||
testPieceBitboard(blackQueens, blackQueenSquares)
|
||||
testPieceBitboard(blackKing, blackKingSquares)
|
||||
|
||||
b = newChessboardFromFEN("B7/P1P1P1P1/1P1P1P1P/7k/8/8/8/3K4 w - - 0 1")
|
||||
b.doMove(createMove("d1", "e1"))
|
||||
var m = MoveList()
|
||||
b.generateMoves(m)
|
||||
echo &"Legal moves for {b.getSideToMove()} at {b.toFEN()}: "
|
||||
echo &"There are {len(m)} legal moves for {b.getSideToMove()} at {b.toFEN()}: "
|
||||
for move in m:
|
||||
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
|
||||
echo b.pretty()
|
||||
echo b.getAttacksTo("f3".toSquare(), White)
|
||||
# setControlCHook(proc () {.noconv.} = quit(0))
|
||||
# quit(main())
|
||||
|
|
Loading…
Reference in New Issue