Add move generation for bishops and queens as well as attack tracking
This commit is contained in:
parent
82cef11cc4
commit
19ad46bbda
|
@ -149,6 +149,8 @@ func shortCastleKing(color: PieceColor): Square {.inline.} = (if color == White:
|
|||
func longCastleRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(7, 3) else: makeSquare(7, 5))
|
||||
func shortCastleRook(color: PieceColor): Square {.inline.} = (if color == White: makeSquare(0, 0) else: makeSquare(0, 2))
|
||||
|
||||
proc inCheck(self: ChessBoard, side: PieceColor): bool
|
||||
|
||||
|
||||
proc newChessboard: ChessBoard =
|
||||
## Returns a new, empty chessboard
|
||||
|
@ -358,10 +360,9 @@ proc newChessboardFromFEN*(fen: string): ChessBoard =
|
|||
else:
|
||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||
inc(index)
|
||||
# result.updateAttackedSquares()
|
||||
#[if result.inCheck(result.getSideToMove().opposite):
|
||||
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")]#
|
||||
raise newException(ValueError, "invalid position: opponent king can be captured")
|
||||
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")
|
||||
|
@ -548,7 +549,7 @@ proc getKingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bit
|
|||
let
|
||||
sq = square.toBitboard()
|
||||
king = self.getBitboard(King, attacker)
|
||||
if (sq and KING_BITBOARDS[square.uint64]) != 0:
|
||||
if (KING_BITBOARDS[square.uint] and king) != 0:
|
||||
result = result or sq
|
||||
|
||||
|
||||
|
@ -560,10 +561,37 @@ proc getKnightAttacks(self: ChessBoard, square: Square, attacker: PieceColor): B
|
|||
knights = self.getBitboard(Knight, attacker)
|
||||
result = Bitboard(0)
|
||||
for knight in knights:
|
||||
if (sq and KNIGHT_BITBOARDS[knight.uint64]) != 0:
|
||||
if (KNIGHT_BITBOARDS[square.uint] and knight.toBitboard()) != 0:
|
||||
result = result or knight.toBitboard()
|
||||
|
||||
|
||||
proc getSlidingAttacks(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the attack bitboard for the given square from
|
||||
## the sliding pieces of the given side
|
||||
let
|
||||
sq = square.toBitboard()
|
||||
queens = self.getBitboard(Queen, attacker)
|
||||
rooks = self.getBitboard(Rook, attacker)
|
||||
bishops = self.getBitboard(Bishop, attacker)
|
||||
occupancy = self.getOccupancy()
|
||||
result = Bitboard(0)
|
||||
for rook in rooks:
|
||||
let blockers = Rook.getRelevantBlockers(square)
|
||||
if (getRookMoves(square, blockers) and rook.toBitboard()) != 0:
|
||||
result = result or rook.toBitboard()
|
||||
for bishop in bishops:
|
||||
let blockers = Bishop.getRelevantBlockers(square)
|
||||
if (getBishopMoves(square, blockers) and bishop.toBitboard()) != 0:
|
||||
result = result or bishop.toBitboard()
|
||||
for queen in queens:
|
||||
let rookBlockers = Rook.getRelevantBlockers(square)
|
||||
if (getRookMoves(square, rookBlockers) and queen.toBitboard()) != 0:
|
||||
result = result or queen.toBitboard()
|
||||
let bishopBlockers = Bishop.getRelevantBlockers(square)
|
||||
if (getBishopMoves(square, bishopBlockers) and queen.toBitboard()) != 0:
|
||||
result = result or queen.toBitboard()
|
||||
|
||||
|
||||
proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Computes the attack bitboard for the given square from
|
||||
## the given side
|
||||
|
@ -573,6 +601,17 @@ proc getAttacksTo(self: ChessBoard, square: Square, attacker: PieceColor): Bitbo
|
|||
result = result or self.getPawnAttacks(square, attacker)
|
||||
result = result or self.getKingAttacks(square, attacker)
|
||||
result = result or self.getKnightAttacks(square, attacker)
|
||||
result = result or self.getSlidingAttacks(square, attacker)
|
||||
|
||||
|
||||
proc inCheck(self: ChessBoard, side: PieceColor): bool =
|
||||
## Returns if the current side to move is in check
|
||||
return self.getAttacksTo(self.getKingSquare(side), side.opposite()) != 0
|
||||
|
||||
|
||||
proc canCastle(self: ChessBoard, side: PieceColor): tuple[king, queen: bool] =
|
||||
## Returns if the current side to move can castle
|
||||
return (false, false) # TODO
|
||||
|
||||
|
||||
proc getCapturablePieces(self: ChessBoard, side: PieceColor): Bitboard {.inline.} =
|
||||
|
@ -597,7 +636,7 @@ proc generatePawnMovements(self: ChessBoard, moves: var MoveList) =
|
|||
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
|
||||
let rank = sideToMove.getFirstRank() # Only pawns on their starting rank can double push
|
||||
for square in (pawns and rank).doubleForwardRelativeTo(sideToMove) and allowedSquares:
|
||||
moves.add(createMove(square.toBitboard().doubleBackwardRelativeTo(sideToMove), square, DoublePush))
|
||||
|
||||
|
@ -661,7 +700,7 @@ proc generateRookMovements(self: ChessBoard, moves: var MoveList) =
|
|||
for square in rooks:
|
||||
let blockers = occupancy and Rook.getRelevantBlockers(square)
|
||||
var moveset = getRookMoves(square, blockers)
|
||||
# Can't capture our own pieces
|
||||
# Can't move over our own pieces
|
||||
moveset = moveset and not friendlyPieces
|
||||
for target in moveset:
|
||||
moves.add(createMove(square, target))
|
||||
|
@ -679,9 +718,10 @@ proc generateRookCaptures(self: ChessBoard, moves: var MoveList) =
|
|||
let blockers = occupancy and Rook.getRelevantBlockers(square)
|
||||
var moveset = getRookMoves(square, blockers)
|
||||
# Can only cature enemy pieces
|
||||
moveset = moveset and not enemyPieces
|
||||
moveset = moveset and enemyPieces
|
||||
for target in moveset:
|
||||
moves.add(createMove(square, target))
|
||||
moves.add(createMove(square, target, Capture))
|
||||
|
||||
|
||||
proc generateRookMoves(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generateSlidingMoves to generate rook moves
|
||||
|
@ -689,9 +729,103 @@ proc generateRookMoves(self: ChessBoard, moves: var MoveList) =
|
|||
self.generateRookCaptures(moves)
|
||||
|
||||
|
||||
proc generateBishopMovements(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generateBishopMoves to generate all non-capture
|
||||
## bishop moves
|
||||
let
|
||||
sideToMove = self.getSideToMove()
|
||||
occupancy = self.getOccupancy()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
bishops = self.getBitboard(Bishop, sideToMove)
|
||||
for square in bishops:
|
||||
let blockers = occupancy and Bishop.getRelevantBlockers(square)
|
||||
var moveset = getBishopMoves(square, blockers)
|
||||
# Can't move over our own pieces
|
||||
moveset = moveset and not friendlyPieces
|
||||
for target in moveset:
|
||||
moves.add(createMove(square, target))
|
||||
|
||||
|
||||
proc generateBishopCaptures(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generateBishopMoves to generate all capture
|
||||
## bishop moves
|
||||
let
|
||||
sideToMove = self.getSideToMove()
|
||||
occupancy = self.getOccupancy()
|
||||
enemyPieces = self.getCapturablePieces(sideToMove.opposite())
|
||||
bishops = self.getBitboard(Bishop, sideToMove)
|
||||
for square in bishops:
|
||||
let blockers = occupancy and Bishop.getRelevantBlockers(square)
|
||||
var moveset = getRookMoves(square, blockers)
|
||||
# Can only cature enemy pieces
|
||||
moveset = moveset and enemyPieces
|
||||
for target in moveset:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
|
||||
|
||||
proc generateBishopMoves(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generateSlidingMoves to generate bishop moves
|
||||
self.generateBishopMovements(moves)
|
||||
self.generateBishopCaptures(moves)
|
||||
|
||||
|
||||
proc generateQueenMovements(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generateQueenMoves to generate all non-capture
|
||||
## bishop moves
|
||||
let
|
||||
sideToMove = self.getSideToMove()
|
||||
occupancy = self.getOccupancy()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
queens = self.getBitboard(Queen, sideToMove)
|
||||
for square in queens:
|
||||
# A queen is just a rook plus a bishop in terms of move
|
||||
# generation
|
||||
let
|
||||
rookBlockers = Rook.getRelevantBlockers(square) and occupancy
|
||||
bishopBlockers = Bishop.getRelevantBlockers(square) and occupancy
|
||||
rookMoves = getRookMoves(square, rookBlockers)
|
||||
bishopMoves = getBishopMoves(square, bishopBlockers)
|
||||
var moveset = rookMoves or bishopMoves
|
||||
# Can't move over our own pieces
|
||||
moveset = moveset and not friendlyPieces
|
||||
for target in moveset:
|
||||
moves.add(createMove(square, target))
|
||||
|
||||
|
||||
proc generateQueenCaptures(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generateQueenMoves to generate all capture
|
||||
## queen moves
|
||||
let
|
||||
sideToMove = self.getSideToMove()
|
||||
occupancy = self.getOccupancy()
|
||||
enemyPieces = self.getCapturablePieces(sideToMove.opposite())
|
||||
queens = self.getBitboard(Queen, sideToMove)
|
||||
for square in queens:
|
||||
# A queen is just a rook plus a bishop in terms of move
|
||||
# generation
|
||||
let
|
||||
rookBlockers = Rook.getRelevantBlockers(square) and occupancy
|
||||
bishopBlockers = Bishop.getRelevantBlockers(square) and occupancy
|
||||
rookMoves = getRookMoves(square, rookBlockers)
|
||||
bishopMoves = getBishopMoves(square, bishopBlockers)
|
||||
var moveset = rookMoves or bishopMoves
|
||||
# Can only capture the enemy pieces
|
||||
moveset = moveset and enemyPieces
|
||||
for target in moveset:
|
||||
moves.add(createMove(square, target, Capture))
|
||||
|
||||
|
||||
proc generateQueenMoves(self: ChessBoard, moves: var MoveList) =
|
||||
## Helper of generateSlidingMoves to generate queen moves
|
||||
self.generateQueenMovements(moves)
|
||||
self.generateQueenCaptures(moves)
|
||||
|
||||
|
||||
proc generateSlidingMoves(self: ChessBoard, moves: var MoveList) =
|
||||
## Generates all legal sliding moves for the side to move
|
||||
self.generateRookMoves(moves)
|
||||
self.generateBishopMoves(moves)
|
||||
self.generateQueenMoves(moves)
|
||||
|
||||
|
||||
proc generateKingMoves(self: ChessBoard, moves: var MoveList) =
|
||||
|
@ -800,8 +934,7 @@ proc generateMoves*(self: ChessBoard, moves: var MoveList) =
|
|||
self.generatePawnMoves(moves)
|
||||
self.generateKingMoves(moves)
|
||||
self.generateKnightMoves(moves)
|
||||
self.generateRookMoves(moves)
|
||||
# TODO: all pieces
|
||||
self.generateSlidingMoves(moves)
|
||||
|
||||
|
||||
proc removePieceFromBitboard(self: ChessBoard, square: Square) =
|
||||
|
@ -1329,7 +1462,7 @@ proc toFEN*(self: ChessBoard): string =
|
|||
# Fullmove number
|
||||
result &= $self.getMoveCount()
|
||||
|
||||
#[
|
||||
|
||||
proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = false, bulk: bool = false): CountData =
|
||||
## Counts (and debugs) the number of legal positions reached after
|
||||
## the given number of ply
|
||||
|
@ -1337,7 +1470,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
var moves = MoveList()
|
||||
self.generateMoves(moves)
|
||||
if not bulk:
|
||||
if len(moves) == 0 and self.inCheck():
|
||||
if len(moves) == 0 and self.inCheck(self.getSideToMove()):
|
||||
result.checkmates = 1
|
||||
# TODO: Should we count stalemates/draws?
|
||||
if ply == 0:
|
||||
|
@ -1367,11 +1500,11 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
if verbose:
|
||||
let canCastle = self.canCastle(self.getSideToMove())
|
||||
echo &"Ply (from root): {self.position.plyFromRoot}"
|
||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}, from ({move.startSquare.rank}, {move.startSquare.file}) to ({move.targetSquare.rank}, {move.targetSquare.file})"
|
||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
||||
echo &"Turn: {self.getSideToMove()}"
|
||||
echo &"Piece: {self.grid[move.startSquare].kind}"
|
||||
echo &"Flags: {move.getFlags()}"
|
||||
echo &"In check: {(if self.inCheck(): \"yes\" else: \"no\")}"
|
||||
echo &"In check: {(if self.inCheck(self.getSideToMove()): \"yes\" else: \"no\")}"
|
||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
echo &"Position before move: {self.toFEN()}"
|
||||
stdout.write("En Passant target: ")
|
||||
|
@ -1390,13 +1523,13 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
inc(result.promotions)
|
||||
if move.isEnPassant():
|
||||
inc(result.enPassant)
|
||||
if self.inCheck():
|
||||
if self.inCheck(self.getSideToMove()):
|
||||
# Opponent king is in check
|
||||
inc(result.checks)
|
||||
if verbose:
|
||||
let canCastle = self.canCastle(self.getSideToMove())
|
||||
echo "\n"
|
||||
echo &"Opponent in check: {(if self.inCheck(): \"yes\" else: \"no\")}"
|
||||
echo &"Opponent in check: {(if self.inCheck(self.getSideToMove()): \"yes\" else: \"no\")}"
|
||||
echo &"Opponent can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
echo &"Position after move: {self.toFEN()}"
|
||||
echo "\n", self.pretty()
|
||||
|
@ -1433,7 +1566,7 @@ proc perft*(self: ChessBoard, ply: int, verbose: bool = false, divide: bool = fa
|
|||
result.castles += next.castles
|
||||
result.enPassant += next.enPassant
|
||||
result.checkmates += next.checkmates
|
||||
]#
|
||||
|
||||
|
||||
proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
||||
if len(command) < 2:
|
||||
|
@ -1462,7 +1595,7 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
|||
break
|
||||
if not ok:
|
||||
return
|
||||
#[try:
|
||||
try:
|
||||
let ply = parseInt(args[0])
|
||||
if bulk:
|
||||
let t = cpuTime()
|
||||
|
@ -1485,7 +1618,6 @@ proc handleGoCommand(board: ChessBoard, command: seq[string]) =
|
|||
echo "Error: go: perft: invalid depth"
|
||||
else:
|
||||
echo &"Error: go: unknown subcommand '{command[1]}'"
|
||||
]#
|
||||
|
||||
|
||||
proc handleMoveCommand(board: ChessBoard, command: seq[string]): Move {.discardable.} =
|
||||
|
@ -1660,6 +1792,7 @@ const HELP_TEXT = """Nimfish help menu:
|
|||
- fen: Shorthand for "position fen"
|
||||
- pos <args>: Shorthand for "position <args>"
|
||||
- get <square>: Get the piece on the given square
|
||||
- atk: Print the attack bitboard of the given square for the side to move
|
||||
- uci: enter UCI mode (WIP)
|
||||
"""
|
||||
|
||||
|
@ -1703,6 +1836,15 @@ proc main: int =
|
|||
board.unmakeMove()
|
||||
of "turn":
|
||||
echo &"Active color: {board.getSideToMove()}"
|
||||
of "atk":
|
||||
if len(cmd) != 2:
|
||||
echo "error: atk: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.getAttacksTo(cmd[1].toSquare(), board.getSideToMove())
|
||||
except ValueError:
|
||||
echo "error: atk: invalid square"
|
||||
continue
|
||||
of "ep":
|
||||
let target = board.getEnPassantTarget()
|
||||
if target != nullSquare():
|
||||
|
@ -1718,12 +1860,11 @@ proc main: int =
|
|||
except ValueError:
|
||||
echo "error: get: invalid square"
|
||||
continue
|
||||
#[of "castle":
|
||||
let canCastle = board.canCastle()
|
||||
of "castle":
|
||||
let canCastle = board.canCastle(board.getSideToMove())
|
||||
echo &"Castling rights for {($board.getSideToMove()).toLowerAscii()}:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
of "check":
|
||||
echo &"{board.getSideToMove()} king in check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||
]#
|
||||
echo &"{board.getSideToMove()} king in check: {(if board.inCheck(board.getSideToMove()): \"yes\" else: \"no\")}"
|
||||
else:
|
||||
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
||||
except IOError:
|
||||
|
@ -1839,14 +1980,5 @@ when isMainModule:
|
|||
testPieceBitboard(blackQueens, blackQueenSquares)
|
||||
testPieceBitboard(blackKing, blackKingSquares)
|
||||
|
||||
|
||||
b = newChessboardFromFEN("8/5R2/8/8/7k/8/5K2/8 w - - 0 1")
|
||||
var m = MoveList()
|
||||
b.generateRookMovements(m)
|
||||
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()
|
||||
|
||||
# setControlCHook(proc () {.noconv.} = quit(0))
|
||||
# quit(main())
|
||||
setControlCHook(proc () {.noconv.} = quit(0))
|
||||
quit(main())
|
||||
|
|
|
@ -342,9 +342,10 @@ else:
|
|||
var path = joinPath(getCurrentDir(), "src/resources")
|
||||
if path.lastPathPart() == "nimfish":
|
||||
path = joinPath("src", path)
|
||||
echo "Loading magic bitboards"
|
||||
var magics = readFile(joinPath(path, "magics.json")).fromJson(TableRef[string, array[64, MagicEntry]])
|
||||
var moves = readFile(joinPath(path, "movesets.json")).fromJson(TableRef[string, array[64, seq[Bitboard]]])
|
||||
ROOK_MAGICS = magics["rooks"]
|
||||
BISHOP_MAGICS = magics["bishops"]
|
||||
ROOK_MOVES = moves["rooks"]
|
||||
BISHOP_MOVES = moves["bishops"]
|
||||
BISHOP_MOVES = moves["bishops"]
|
||||
|
|
Loading…
Reference in New Issue