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:
return bitboard shl 9
of BackwardLeft:
return bitboard shr 7
else:
discard
return bitboard shl 7
of Left:
return bitboard shr 1
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
@ -172,14 +174,18 @@ func getDirectionMask*(bitboard: Bitboard, color: PieceColor, direction: Directi
of BackwardRight:
return bitboard shr 9
of BackwardLeft:
return bitboard shl 7
else:
discard
return bitboard shr 7
of Left:
return bitboard shl 1
of Right:
return bitboard shr 1
else:
discard
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 =
## 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 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
# 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)
func bottomRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, BackwardRight) and not getFileMask(7)
func backwardRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, BackwardRight)
func bottomLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
getDirectionMask(self, side, BackwardLeft) and not getFileMask(0)
func backwardLeftRelativeTo*(self: Bitboard, side: PieceColor): Bitboard =
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:
result.position.pieces.black.queens.uint64.setBit(bitIndex)
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")
result.position.pieces.black.king.uint64.setBit(bitIndex)
else:
@ -360,7 +360,7 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
#[if result.inCheck(result.getSideToMove().opposite):
# Opponent king cannot be captured on the next move
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
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)
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
## color (if it is None, the active color is used)
## side (if it is None, the side to move is used)
var color = color
if color == None:
color = self.getSideToMove()
case color:
of White:
return Square(self.position.pieces.white.king.uint64.countTrailingZeroBits())
return self.position.pieces.white.king.toSquare()
of Black:
return Square(self.position.pieces.black.king.uint64.countTrailingZeroBits())
return self.position.pieces.black.king.toSquare()
else:
discard
@ -527,6 +527,28 @@ proc getOccupancy(self: ChessBoard): Bitboard =
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) =
## Helper of generatePawnMoves for generating all non-capture
## 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 also cannot move to the last rank, as that will result in a promotion
# and is handled elsewhere
occupancy = not (self.getOccupancy() or sideToMove.getLastRank())
allowedSquares = not (self.getOccupancy() or sideToMove.getLastRank())
# 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))
# 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))
@ -553,17 +575,18 @@ proc generatePawnCaptures(self: ChessBoard, moves: var MoveList) =
sideToMove = self.getSideToMove()
nonSideToMove = sideToMove.opposite()
pawns = self.getBitboard(Pawn, sideToMove)
# We can only capture enemy pieces
enemyPieces = self.getOccupancyFor(nonSideToMove)
# We can only capture enemy pieces (except the king)
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
rightMovement = pawns.forwardRightRelativeTo(sideToMove)
leftMovement = pawns.forwardLeftRelativeTo(sideToMove)
epTarget = self.getEnPassantTarget()
let epBitboard = if (epTarget != nullSquare()): epTarget.toBitboard() else: Bitboard(0)
# Top right attacks
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:
moves.add(createMove(square.toBitboard().bottomRightRelativeTo(sideToMove), square, Capture))
moves.add(createMove(square.toBitboard().backwardRightRelativeTo(sideToMove), square, Capture))
# Special case for en passant
let
epLeft = epBitboard and leftMovement
@ -580,31 +603,47 @@ proc generatePawnPromotions(self: ChessBoard, moves: var MoveList) =
let
sideToMove = self.getSideToMove()
pawns = self.getBitboard(Pawn, sideToMove)
occupancy = not self.getOccupancy()
for square in pawns.forwardRelativeTo(sideToMove) and occupancy and sideToMove.getLastRank():
occupancy = self.getOccupancy()
for square in pawns.forwardRelativeTo(sideToMove) and not occupancy and sideToMove.getLastRank():
for promotion in [PromoteToBishop, PromoteToKnight, PromoteToQueen, PromoteToRook]:
moves.add(createMove(square.toBitboard().backwardRelativeTo(sideToMove), square, promotion))
proc generatePawnMoves(self: ChessBoard, moves: var MoveList) =
## Generates the possible moves for the pawn in the given
## square
## Generates all the legal pawn moves for the side to move
self.generatePawnMovements(moves)
self.generatePawnCaptures(moves)
self.generatePawnPromotions(moves)
proc generateSlidingMoves(self: ChessBoard, square: Square): seq[Move] =
## Generates moves for the sliding piece in the given square
proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) =
## 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] =
## Generates moves for the king in the given square
proc generateKnightMoves(self: ChessBoard, square: Square): seq[Move] =
## Generates moves for the knight in the given square
proc generateKnightMoves(self: ChessBoard, moves: var MoveList)=
## Generates all the legal knight moves for the side to move
@ -678,6 +717,7 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) =
]#
# TODO: Check for repetitions (requires zobrist hashing + table)
self.generatePawnMoves(moves)
self.generateKingMoves(moves)
# TODO: all pieces
@ -1716,17 +1756,12 @@ when isMainModule:
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.generatePawnMoves(m)
echo &"Pawn moves for {b.getSideToMove()} at {b.toFEN()}: "
b.generateMoves(m)
echo &"Legal moves for {b.getSideToMove()} at {b.toFEN()}: "
for move in m:
echo " - ", move.startSquare, move.targetSquare, " ", move.getFlags()
#[b.doMove(createMove("d1", "c1"))
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()]#
echo b.pretty()
# setControlCHook(proc () {.noconv.} = quit(0))
# quit(main())