Add king move generation

This commit is contained in:
Mattia Giambirtone 2024-04-17 16:50:55 +02:00
parent 86265c68f0
commit 3bb2cc7c66
2 changed files with 92 additions and 45 deletions

View File

@ -154,9 +154,11 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi
of BackwardRight: of BackwardRight:
return bitboard shl 9 return bitboard shl 9
of BackwardLeft: of BackwardLeft:
return bitboard shr 7 return bitboard shl 7
else: of Left:
discard return bitboard shr 1
of Right:
return bitboard shl 1
of Black: of Black:
# The directions for black are just the opposite of those for white, # The directions for black are just the opposite of those for white,
# so we avoid duplicating any code # so we avoid duplicating any code
@ -172,14 +174,18 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi
of BackwardRight: of BackwardRight:
return bitboard shr 9 return bitboard shr 9
of BackwardLeft: of BackwardLeft:
return bitboard shl 7 return bitboard shr 7
else: of Left:
discard return bitboard shl 1
of Right:
return bitboard shr 1
else: else:
discard discard
func getLastRank*(color: PieceColor): Bitboard = (if color == White: getRankMask(0) else: getRankMask(7)) 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 getDirectionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard = func getDirectionMask*(square: Square, color: PieceColor, direction: Direction): Bitboard =
## Get a bitmask for the given direction for a piece ## Get a bitmask for the given direction for a piece
@ -193,6 +199,9 @@ func doubleForwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self
func backwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Backward) func backwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Backward)
func doubleBackwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = self.backwardRelativeTo(side).backwardRelativeTo(side) 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)
# We mask off the first and last ranks for # We mask off the first and last ranks for
# left and right movements respectively to # left and right movements respectively to
@ -205,9 +214,12 @@ func forwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, ForwardLeft) and not getFileMask(0) getDirectionMask(self, side, ForwardLeft) and not getFileMask(0)
func bottomRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = func backwardRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, BackwardRight) and not getFileMask(7) getDirectionMask(self, side, BackwardRight)
func bottomLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = func backwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, BackwardLeft) and not getFileMask(0) getDirectionMask(self, side, BackwardLeft)
# func forwardRelativeTo*(self: Bitboard, side: PieceColor): Bitboard = getDirectionMask(self, side, Forward)

View File

@ -260,7 +260,7 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
of Queen: of Queen:
result.position.pieces.black.queens.uint64.setBit(bitIndex) result.position.pieces.black.queens.uint64.setBit(bitIndex)
of King: of King:
if result.position.pieces.black.king != Bitboard(0'u64): if result.position.pieces.black.king != 0:
raise newException(ValueError, "invalid position: exactly one king of each color must be present") raise newException(ValueError, "invalid position: exactly one king of each color must be present")
result.position.pieces.black.king.uint64.setBit(bitIndex) result.position.pieces.black.king.uint64.setBit(bitIndex)
else: else:
@ -360,7 +360,7 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
#[if result.inCheck(result.getSideToMove().opposite): #[if result.inCheck(result.getSideToMove().opposite):
# Opponent king cannot be captured on the next move # Opponent king cannot be captured on the next move
raise newException(ValueError, "invalid position: opponent king can be captured")]# raise newException(ValueError, "invalid position: opponent king can be captured")]#
if result.position.pieces.white.king == Bitboard(0) or result.position.pieces.black.king == Bitboard(0): if result.position.pieces.white.king == 0 or result.position.pieces.black.king == 0:
# Both kings must be on the board # Both kings must be on the board
raise newException(ValueError, "invalid position: exactly one king of each color must be present") raise newException(ValueError, "invalid position: exactly one king of each color must be present")
@ -492,17 +492,17 @@ func getFlags*(move: Move): seq[MoveFlag] =
result.add(Default) result.add(Default)
func getKing(self: ChessBoard, color: PieceColor = None): Square {.inline.} = func getKingSquare(self: ChessBoard, color: PieceColor = None): Square {.inline.} =
## Returns the square of the king for the given ## Returns the square of the king for the given
## color (if it is None, the active color is used) ## side (if it is None, the side to move is used)
var color = color var color = color
if color == None: if color == None:
color = self.getSideToMove() color = self.getSideToMove()
case color: case color:
of White: of White:
return Square(self.position.pieces.white.king.uint64.countTrailingZeroBits()) return self.position.pieces.white.king.toSquare()
of Black: of Black:
return Square(self.position.pieces.black.king.uint64.countTrailingZeroBits()) return self.position.pieces.black.king.toSquare()
else: else:
discard discard
@ -527,6 +527,28 @@ proc getOccupancy(self: ChessBoard): Bitboard =
result = self.getOccupancyFor(Black) or self.getOccupancyFor(White) result = self.getOccupancyFor(Black) or self.getOccupancyFor(White)
proc getPawnAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
## Returns the attack bitboard for the given square from
## the pawns of the given side
let
sq = square.toBitboard()
pawns = self.getBitboard(Pawn, attacker)
bottomLeft = sq.backwardLeftRelativeTo(attacker)
bottomRight = sq.backwardRightRelativeTo(attacker)
return pawns and (bottomLeft or bottomRight)
proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
## Computes the attack bitboard for the given square from
## the given side
result = Bitboard(0)
let
squareBitboard = square.toBitboard()
result = result or self.getPawnAttacks(square, attacker)
proc generatePawnMovements(self: ChessBoard, moves: var MoveList) = proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
## Helper of generatePawnMoves for generating all non-capture ## Helper of generatePawnMoves for generating all non-capture
## and non-promotion pawn moves ## and non-promotion pawn moves
@ -536,13 +558,13 @@ proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
# We can only move to squares that are *not* occupied by another piece. # 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 # We also cannot move to the last rank, as that will result in a promotion
# and is handled elsewhere # and is handled elsewhere
occupancy = not (self.getOccupancy() or sideToMove.getLastRank()) allowedSquares = not (self.getOccupancy() or sideToMove.getLastRank())
# Single push # Single push
for square in pawns.forwardRelativeTo(sideToMove) and occupancy: for square in pawns.forwardRelativeTo(sideToMove) and allowedSquares:
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square)) moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square))
# Double push # Double push
let rank = if sideToMove == White: getRankMask(6) else: getRankMask(1) # Only pawns on their starting rank can 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 occupancy: for square in (pawns and rank).doubleForwardRelativeTo(sideToMove) and allowedSquares:
moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush)) moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush))
@ -553,17 +575,18 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
sideToMove = self.getSideToMove() sideToMove = self.getSideToMove()
nonSideToMove = sideToMove.opposite() nonSideToMove = sideToMove.opposite()
pawns = self.getBitboard(Pawn, sideToMove) pawns = self.getBitboard(Pawn, sideToMove)
# We can only capture enemy pieces # We can only capture enemy pieces (except the king)
enemyPieces = self.getOccupancyFor(nonSideToMove) enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
rightMovement = pawns.forwardRightRelativeTo(sideToMove) rightMovement = pawns.forwardRightRelativeTo(sideToMove)
leftMovement = pawns.forwardLeftRelativeTo(sideToMove) leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
epTarget = self.getEnPassantTarget() epTarget = self.getEnPassantTarget()
let epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0) let epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
# Top right attacks # Top right attacks
for square in rightMovement and enemyPieces: for square in rightMovement and enemyPieces:
moves.add(createMove(square.toBitboard().bottomLeftRelativeTo(sideToMove), square, Capture)) moves.add(createMove(square.toBitboard().backwardLeftRelativeTo(sideToMove), square, Capture))
# Top left attacks
for square in leftMovement and enemyPieces: for square in leftMovement and enemyPieces:
moves.add(createMove(square.toBitboard().bottomRightRelativeTo(sideToMove), square, Capture)) moves.add(createMove(square.toBitboard().backwardRightRelativeTo(sideToMove), square, Capture))
# Special case for en passant # Special case for en passant
let let
epLeft = epBitboard and leftMovement epLeft = epBitboard and leftMovement
@ -580,31 +603,47 @@ proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) =
let let
sideToMove = self.getSideToMove() sideToMove = self.getSideToMove()
pawns = self.getBitboard(Pawn, sideToMove) pawns = self.getBitboard(Pawn, sideToMove)
occupancy = not self.getOccupancy() occupancy = self.getOccupancy()
for square in pawns.forwardRelativeTo(sideToMove) and occupancy and sideToMove.getLastRank(): for square in pawns.forwardRelativeTo(sideToMove) and not occupancy and sideToMove.getLastRank():
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]: for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion)) moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion))
proc generatePawnMoves(self: ChessBoard, moves: var MoveList) = proc generatePawnMoves(self: ChessBoard, moves: var MoveList) =
## Generates the possible moves for the pawn in the given ## Generates all the legal pawn moves for the side to move
## square
self.generatePawnMovements(moves) self.generatePawnMovements(moves)
self.generatePawnCaptures(moves) self.generatePawnCaptures(moves)
self.generatePawnPromotions(moves) self.generatePawnPromotions(moves)
proc generateSlidingMoves(self: ChessBoard, square: Square): seq[Move] = proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) =
## Generates moves for the sliding piece in the given square ## Generates all legal sliding moves for the side to move
proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
## Generates all legal king moves for the side to move
let
sideToMove = self.getSideToMove()
king = self.getBitboard(King, sideToMove)
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)
# Regular moves
for square in movements and allowedSquares:
moves.add(createMove(king, square))
# Captures
for square in movements and enemyPieces:
moves.add(createMove(king, square, Capture))
proc generateKingMoves(self: ChessBoard, square: Square): seq[Move] = proc generateKnightMoves(self: ChessBoard, moves: var MoveList)=
## Generates moves for the king in the given square ## Generates all the legal knight moves for the side to move
proc generateKnightMoves(self: ChessBoard, square: Square): seq[Move] =
## Generates moves for the knight in the given square
@ -678,6 +717,7 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) =
]# ]#
# TODO: Check for repetitions (requires zobrist hashing + table) # TODO: Check for repetitions (requires zobrist hashing + table)
self.generatePawnMoves(moves) self.generatePawnMoves(moves)
self.generateKingMoves(moves)
# TODO: all pieces # TODO: all pieces
@ -1716,17 +1756,12 @@ when isMainModule:
testPieceBitboard(blackKing, blackKingSquares) testPieceBitboard(blackKing, blackKingSquares)
b = newChessboardFromFEN("B7/P1P1P1P1/1P1P1P1P/7k/8/8/8/3K4 w - - 0 1") b = newChessboardFromFEN("B7/P1P1P1P1/1P1P1P1P/7k/8/8/8/3K4 w - - 0 1")
b.doMove(createMove("d1", "e1"))
var m = MoveList() var m = MoveList()
b.generatePawnMoves(m) b.generateMoves(m)
echo &"Pawn moves for {b.getSideToMove()} at {b.toFEN()}: " echo &"Legal moves for {b.getSideToMove()} at {b.toFEN()}: "
for move in m: for move in m:
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags() echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
#[b.doMove(createMove("d1", "c1")) echo b.pretty()
m.clear()
b.generatePawnMoves(m)
echo &"Pawn moves for {b.getSideToMove()} at {b.toFEN()}: "
for move in m:
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
echo b.pretty()]#
# setControlCHook(proc () {.noconv.} = quit(0)) # setControlCHook(proc () {.noconv.} = quit(0))
# quit(main()) # quit(main())