Refactor chessboard code and move most logic to Position
This commit is contained in:
parent
877b6f4b06
commit
da5eac94d6
|
@ -262,9 +262,9 @@ func shortKnightDownRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard
|
|||
# We precompute as much stuff as possible: lookup tables are fast!
|
||||
|
||||
|
||||
func computeKingBitboards: array[64, Bitboard] {.compileTime.} =
|
||||
func computeKingBitboards: array[Square(0)..Square(63), Bitboard] {.compileTime.} =
|
||||
## Precomputes all the movement bitboards for the king
|
||||
for i in 0'u64..63:
|
||||
for i in Square(0)..Square(63):
|
||||
let king = i.toBitboard()
|
||||
# It doesn't really matter which side we generate
|
||||
# the move for, they're identical for both
|
||||
|
@ -284,9 +284,9 @@ func computeKingBitboards: array[64, Bitboard] {.compileTime.} =
|
|||
result[i] = movements
|
||||
|
||||
|
||||
func computeKnightBitboards: array[64, Bitboard] {.compileTime.} =
|
||||
func computeKnightBitboards: array[Square(0)..Square(63), Bitboard] {.compileTime.} =
|
||||
## Precomputes all the movement bitboards for knights
|
||||
for i in 0'u64..63:
|
||||
for i in Square(0)..Square(63):
|
||||
let knight = i.toBitboard()
|
||||
# It doesn't really matter which side we generate
|
||||
# the move for, they're identical for both
|
||||
|
@ -302,28 +302,19 @@ func computeKnightBitboards: array[64, Bitboard] {.compileTime.} =
|
|||
result[i] = movements
|
||||
|
||||
|
||||
func computePawnAttacks(color: PieceColor): array[64, Bitboard] {.compileTime.} =
|
||||
func computePawnAttacks(color: PieceColor): array[Square(0)..Square(63), Bitboard] {.compileTime.} =
|
||||
## Precomputes all the attack bitboards for pawns
|
||||
## of the given color
|
||||
for i in 0'u64..63:
|
||||
let
|
||||
pawn = i.toBitboard()
|
||||
square = Square(i)
|
||||
file = fileFromSquare(square)
|
||||
var movements = Bitboard(0)
|
||||
if file in 1..7:
|
||||
movements = movements or pawn.forwardLeftRelativeTo(color)
|
||||
if file in 0..6:
|
||||
movements = movements or pawn.forwardRightRelativeTo(color)
|
||||
movements = movements and not pawn
|
||||
result[i] = movements
|
||||
for i in Square(0)..Square(63):
|
||||
let pawn = i.toBitboard()
|
||||
result[i] = pawn.backwardLeftRelativeTo(color) or pawn.backwardRightRelativeTo(color)
|
||||
|
||||
const
|
||||
KING_BITBOARDS = computeKingBitboards()
|
||||
KNIGHT_BITBOARDS = computeKnightBitboards()
|
||||
PAWN_ATTACKS = [computePawnAttacks(White), computePawnAttacks(Black)]
|
||||
PAWN_ATTACKS: array[PieceColor.White..PieceColor.Black, array[Square(0)..Square(63), Bitboard]] = [computePawnAttacks(White), computePawnAttacks(Black)]
|
||||
|
||||
|
||||
func getKingAttacks*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square.int]
|
||||
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square.int]
|
||||
func getPawnAttacks*(color: PieceColor, square: Square): Bitboard {.inline.} = PAWN_ATTACKS[color.int][square.int]
|
||||
func getKingAttacks*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square]
|
||||
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square]
|
||||
func getPawnAttacks*(color: PieceColor, square: Square): Bitboard {.inline.} = PAWN_ATTACKS[color][square]
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
# limitations under the License.
|
||||
|
||||
## Implementation of a simple chessboard
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
|
||||
|
||||
import pieces
|
||||
|
@ -41,10 +39,7 @@ type
|
|||
positions*: seq[Position]
|
||||
|
||||
|
||||
# A bunch of simple utility functions and forward declarations
|
||||
proc toFEN*(self: Chessboard): string
|
||||
proc updateChecksAndPins*(self: var Chessboard)
|
||||
proc hash*(self: var Chessboard)
|
||||
|
||||
|
||||
proc newChessboard*: Chessboard =
|
||||
|
@ -54,118 +49,11 @@ proc newChessboard*: Chessboard =
|
|||
result.position.mailbox[i] = nullPiece()
|
||||
|
||||
|
||||
func getBitboard*(self: Chessboard, kind: PieceKind, color: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the positional bitboard for the given piece kind and color
|
||||
return self.position.getBitboard(kind, color)
|
||||
|
||||
|
||||
func getBitboard*(self: Chessboard, piece: Piece): Bitboard {.inline.} =
|
||||
## Returns the positional bitboard for the given piece type
|
||||
return self.getBitboard(piece.kind, piece.color)
|
||||
|
||||
|
||||
proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||
## Initializes a chessboard with the
|
||||
## position encoded by the given FEN string
|
||||
result = newChessboard()
|
||||
var
|
||||
# Current square in the grid
|
||||
row: int8 = 0
|
||||
column: int8 = 0
|
||||
# Current section in the FEN string
|
||||
section = 0
|
||||
# Current index into the FEN string
|
||||
index = 0
|
||||
# Temporary variable to store a piece
|
||||
piece: Piece
|
||||
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
|
||||
while index <= fen.high():
|
||||
var c = fen[index]
|
||||
if c == ' ':
|
||||
# Next section
|
||||
inc(section)
|
||||
inc(index)
|
||||
continue
|
||||
case section:
|
||||
of 0:
|
||||
# Piece placement data
|
||||
case c.toLowerAscii():
|
||||
# Piece
|
||||
of 'r', 'n', 'b', 'q', 'k', 'p':
|
||||
let square = makeSquare(row, column)
|
||||
piece = c.fromChar()
|
||||
result.position.pieces[piece.color][piece.kind].setBit(square)
|
||||
result.position.mailbox[square] = piece
|
||||
inc(column)
|
||||
of '/':
|
||||
# Next row
|
||||
inc(row)
|
||||
column = 0
|
||||
of '0'..'9':
|
||||
# Skip x columns
|
||||
let x = int(uint8(c) - uint8('0'))
|
||||
if x > 8:
|
||||
raise newException(ValueError, &"invalid FEN: invalid column skip size ({x} > 8)")
|
||||
column += int8(x)
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown piece identifier '{c}'")
|
||||
of 1:
|
||||
# Active color
|
||||
case c:
|
||||
of 'w':
|
||||
result.position.sideToMove = White
|
||||
of 'b':
|
||||
result.position.sideToMove = Black
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: invalid active color identifier '{c}'")
|
||||
of 2:
|
||||
# Castling availability
|
||||
case c:
|
||||
# TODO
|
||||
of '-':
|
||||
discard
|
||||
of 'K':
|
||||
result.position.castlingAvailability[White].king = true
|
||||
of 'Q':
|
||||
result.position.castlingAvailability[White].queen = true
|
||||
of 'k':
|
||||
result.position.castlingAvailability[Black].king = true
|
||||
of 'q':
|
||||
result.position.castlingAvailability[Black].queen = true
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||
of 3:
|
||||
# En passant target square
|
||||
case c:
|
||||
of '-':
|
||||
# Field is already uninitialized to the correct state
|
||||
discard
|
||||
else:
|
||||
result.position.enPassantSquare = fen[index..index+1].toSquare()
|
||||
# Square metadata is 2 bytes long
|
||||
inc(index)
|
||||
of 4:
|
||||
# Halfmove clock
|
||||
var s = ""
|
||||
while not fen[index].isSpaceAscii():
|
||||
s.add(fen[index])
|
||||
inc(index)
|
||||
# Backtrack so the space is seen by the
|
||||
# next iteration of the loop
|
||||
dec(index)
|
||||
result.position.halfMoveClock = parseInt(s).uint16
|
||||
of 5:
|
||||
# Fullmove number
|
||||
var s = ""
|
||||
while index <= fen.high():
|
||||
s.add(fen[index])
|
||||
inc(index)
|
||||
result.position.fullMoveCount = parseInt(s).uint16
|
||||
else:
|
||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||
inc(index)
|
||||
result.updateChecksAndPins()
|
||||
result.hash()
|
||||
result.position = loadFEN(fen)
|
||||
|
||||
|
||||
proc newDefaultChessboard*: Chessboard {.inline.} =
|
||||
|
@ -174,375 +62,29 @@ proc newDefaultChessboard*: Chessboard {.inline.} =
|
|||
return newChessboardFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
|
||||
|
||||
|
||||
func countPieces*(self: Chessboard, kind: PieceKind, color: PieceColor): int {.inline.} =
|
||||
## Returns the number of pieces with
|
||||
## the given color and type in the
|
||||
## current position
|
||||
return self.position.pieces[color][kind].countSquares()
|
||||
|
||||
|
||||
func getPiece*(self: Chessboard, square: Square): Piece {.inline.} =
|
||||
## Gets the piece at the given square
|
||||
return self.position.mailbox[square]
|
||||
|
||||
|
||||
func getPiece*(self: Chessboard, square: string): Piece {.inline.} =
|
||||
## Gets the piece on the given square
|
||||
## in algebraic notation
|
||||
return self.getPiece(square.toSquare())
|
||||
|
||||
|
||||
proc removePieceFromBitboard*(self: var Chessboard, square: Square) =
|
||||
## Removes a piece at the given square in the chessboard from
|
||||
## its respective bitboard
|
||||
let piece = self.getPiece(square)
|
||||
self.position.pieces[piece.color][piece.kind].clearBit(square)
|
||||
|
||||
|
||||
proc addPieceToBitboard*(self: var Chessboard, square: Square, piece: Piece) =
|
||||
## Adds the given piece at the given square in the chessboard to
|
||||
## its respective bitboard
|
||||
self.position.pieces[piece.color][piece.kind].setBit(square)
|
||||
|
||||
|
||||
proc spawnPiece*(self: var Chessboard, square: Square, piece: Piece) =
|
||||
## Internal helper to "spawn" a given piece at the given
|
||||
## square
|
||||
when not defined(danger):
|
||||
doAssert self.getPiece(square).kind == Empty
|
||||
self.addPieceToBitboard(square, piece)
|
||||
self.position.mailbox[square] = piece
|
||||
|
||||
|
||||
proc removePiece*(self: var Chessboard, square: Square) =
|
||||
## Removes a piece from the board, updating necessary
|
||||
## metadata
|
||||
when not defined(danger):
|
||||
let piece = self.getPiece(square)
|
||||
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
||||
self.removePieceFromBitboard(square)
|
||||
self.position.mailbox[square] = nullPiece()
|
||||
|
||||
|
||||
proc movePiece*(self: var Chessboard, move: Move) =
|
||||
## Internal helper to move a piece from
|
||||
## its current square to a target square
|
||||
let piece = self.getPiece(move.startSquare)
|
||||
when not defined(danger):
|
||||
let targetSquare = self.getPiece(move.targetSquare)
|
||||
if targetSquare.color != None:
|
||||
raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}: {move}")
|
||||
# Update positional metadata
|
||||
self.removePiece(move.startSquare)
|
||||
self.spawnPiece(move.targetSquare, piece)
|
||||
|
||||
|
||||
proc movePiece*(self: var Chessboard, startSquare, targetSquare: Square) =
|
||||
self.movePiece(createMove(startSquare, targetSquare))
|
||||
|
||||
|
||||
func countPieces*(self: Chessboard, piece: Piece): int {.inline.} =
|
||||
## Returns the number of pieces on the board that
|
||||
## are of the same type and color as the given piece
|
||||
return self.countPieces(piece.kind, piece.color)
|
||||
|
||||
|
||||
func getOccupancyFor*(self: Chessboard, color: PieceColor): Bitboard {.inline.} =
|
||||
## Get the occupancy bitboard for every piece of the given color
|
||||
result = self.position.getOccupancyFor(color)
|
||||
|
||||
|
||||
func getOccupancy*(self: Chessboard): Bitboard {.inline.} =
|
||||
## Get the occupancy bitboard for every piece on
|
||||
## the chessboard
|
||||
result = self.position.getOccupancy()
|
||||
|
||||
|
||||
func getPawnAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the locations of the pawns attacking the given square
|
||||
let
|
||||
sq = square.toBitboard()
|
||||
pawns = self.getBitboard(Pawn, attacker)
|
||||
bottomLeft = sq.backwardLeftRelativeTo(attacker)
|
||||
bottomRight = sq.backwardRightRelativeTo(attacker)
|
||||
return pawns and (bottomLeft or bottomRight)
|
||||
|
||||
|
||||
func getKingAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the location of the king if it is attacking the given square
|
||||
result = Bitboard(0)
|
||||
let
|
||||
king = self.getBitboard(King, attacker)
|
||||
squareBB = square.toBitboard()
|
||||
if (getKingAttacks(square) and squareBB) != 0:
|
||||
result = result or king
|
||||
|
||||
|
||||
func getKnightAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the locations of the knights attacking the given square
|
||||
let
|
||||
knights = self.getBitboard(Knight, attacker)
|
||||
squareBB = square.toBitboard()
|
||||
result = Bitboard(0)
|
||||
for knight in knights:
|
||||
if (getKnightAttacks(knight) and squareBB) != 0:
|
||||
result = result or knight.toBitboard()
|
||||
|
||||
|
||||
proc getSlidingAttacks*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the locations of the sliding pieces attacking the given square
|
||||
let
|
||||
queens = self.getBitboard(Queen, attacker)
|
||||
rooks = self.getBitboard(Rook, attacker) or queens
|
||||
bishops = self.getBitboard(Bishop, attacker) or queens
|
||||
occupancy = self.getOccupancy()
|
||||
squareBB = square.toBitboard()
|
||||
result = Bitboard(0)
|
||||
for rook in rooks:
|
||||
let
|
||||
blockers = occupancy and Rook.getRelevantBlockers(rook)
|
||||
moves = getRookMoves(rook, blockers)
|
||||
# Attack set intersects our chosen square
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or rook.toBitboard()
|
||||
for bishop in bishops:
|
||||
let
|
||||
blockers = occupancy and Bishop.getRelevantBlockers(bishop)
|
||||
moves = getBishopMoves(bishop, blockers)
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or bishop.toBitboard()
|
||||
|
||||
|
||||
proc getAttacksTo*(self: Chessboard, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Computes the attack bitboard for the given square from
|
||||
## the given side
|
||||
result = Bitboard(0)
|
||||
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 isOccupancyAttacked*(self: Chessboard, square: Square, occupancy: Bitboard): bool =
|
||||
## Returns whether the given square would be attacked by the
|
||||
## enemy side if the board had the given occupancy. This function
|
||||
## is necessary mostly to make sure sliding attacks can check the
|
||||
## king properly: due to how we generate our attack bitboards, if
|
||||
## the king moved backwards along a ray from a slider we would not
|
||||
## consider it to be in check (because the ray stops at the first
|
||||
## blocker). In order to fix that, in generateKingMoves() we use this
|
||||
## function and pass in the board's occupancy without the moving king so
|
||||
## that we can pick the correct magic bitboard and ray. Also, since this
|
||||
## function doesn't need to generate all the attacks to know whether a
|
||||
## given square is unsafe, it can short circuit at the first attack and
|
||||
## exit early, unlike getAttacksTo
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
knights = self.getBitboard(Knight, nonSideToMove)
|
||||
|
||||
# Let's do the cheap ones first (the ones which are precomputed)
|
||||
if (getKnightAttacks(square) and knights) != 0:
|
||||
return true
|
||||
|
||||
let king = self.getBitboard(King, nonSideToMove)
|
||||
|
||||
if (getKingAttacks(square) and king) != 0:
|
||||
return true
|
||||
|
||||
let
|
||||
queens = self.getBitboard(Queen, nonSideToMove)
|
||||
bishops = self.getBitboard(Bishop, nonSideToMove) or queens
|
||||
|
||||
if (getBishopMoves(square, occupancy) and bishops) != 0:
|
||||
return true
|
||||
|
||||
let rooks = self.getBitboard(Rook, nonSideToMove) or queens
|
||||
|
||||
if (getRookMoves(square, occupancy) and rooks) != 0:
|
||||
return true
|
||||
|
||||
# TODO: Precompute pawn moves as well?
|
||||
let pawns = self.getBitboard(Pawn, nonSideToMove)
|
||||
|
||||
if (self.getPawnAttacks(square, nonSideToMove) and pawns) != 0:
|
||||
return true
|
||||
|
||||
|
||||
proc updateChecksAndPins*(self: var 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()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
|
||||
# Update checks
|
||||
self.position.checkers = self.getAttacksTo(friendlyKing, nonSideToMove)
|
||||
# Update pins
|
||||
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()
|
||||
|
||||
# Is the pinning ray obstructed by any of our friendly pieces? If so, the
|
||||
# piece is pinned
|
||||
if (pinningRay and friendlyPieces).countSquares() == 1:
|
||||
self.position.diagonalPins = self.position.diagonalPins or pinningRay
|
||||
|
||||
for piece in canPinOrthogonally:
|
||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||
if (pinningRay and friendlyPieces).countSquares() == 1:
|
||||
self.position.orthogonalPins = self.position.orthogonalPins or pinningRay
|
||||
|
||||
|
||||
func inCheck*(self: Chessboard): bool {.inline.} =
|
||||
## Returns if the current side to move is in check
|
||||
return self.position.checkers != 0
|
||||
return self.position.inCheck()
|
||||
|
||||
|
||||
proc canCastle*(self: Chessboard): tuple[queen, king: bool] =
|
||||
proc canCastle*(self: Chessboard): tuple[queen, king: bool] {.inline.} =
|
||||
## Returns if the current side to move can castle
|
||||
if self.inCheck():
|
||||
return (false, false)
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
occupancy = self.getOccupancy()
|
||||
result = self.position.castlingAvailability[sideToMove]
|
||||
if result.king:
|
||||
result.king = (kingSideCastleRay(sideToMove) and occupancy) == 0
|
||||
if result.queen:
|
||||
result.queen = (queenSideCastleRay(sideToMove) and occupancy) == 0
|
||||
if result.king:
|
||||
# There are no pieces in between our friendly king and
|
||||
# rook: check for attacks
|
||||
let
|
||||
king = self.getBitboard(King, sideToMove).toSquare()
|
||||
for square in getRayBetween(king, sideToMove.kingSideRook()):
|
||||
if self.isOccupancyAttacked(square, occupancy):
|
||||
result.king = false
|
||||
break
|
||||
|
||||
if result.queen:
|
||||
let
|
||||
king: Square = self.getBitboard(King, sideToMove).toSquare()
|
||||
# The king always moves two squares, but the queen side rook moves
|
||||
# 3 squares. We only need to check for attacks on the squares where
|
||||
# the king moves to and not any further. We subtract 3 instead of 2
|
||||
# because getRayBetween ignores the start and target squares in the
|
||||
# ray it returns so we have to extend it by one
|
||||
destination = makeSquare(rankFromSquare(king), fileFromSquare(king) - 3)
|
||||
for square in getRayBetween(king, destination):
|
||||
if self.isOccupancyAttacked(square, occupancy):
|
||||
result.queen = false
|
||||
break
|
||||
return self.position.canCastle()
|
||||
|
||||
|
||||
proc `$`*(self: Chessboard): string =
|
||||
result &= "- - - - - - - -"
|
||||
var file = 8
|
||||
for i in 0..7:
|
||||
result &= "\n"
|
||||
for j in 0..7:
|
||||
let piece = self.position.mailbox[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
result &= "x "
|
||||
continue
|
||||
result &= &"{piece.toChar()} "
|
||||
result &= &"{file}"
|
||||
dec(file)
|
||||
result &= "\n- - - - - - - -"
|
||||
result &= "\na b c d e f g h"
|
||||
proc `$`*(self: Chessboard): string = $self.position
|
||||
|
||||
|
||||
proc pretty*(self: Chessboard): string =
|
||||
## Returns a colored version of the
|
||||
## board for easier visualization
|
||||
var file = 8
|
||||
for i in 0..7:
|
||||
if i > 0:
|
||||
result &= "\n"
|
||||
for j in 0..7:
|
||||
# Equivalent to (i + j) mod 2
|
||||
# (I'm just evil)
|
||||
if ((i + j) and 1) == 0:
|
||||
result &= "\x1b[39;44;1m"
|
||||
else:
|
||||
result &= "\x1b[39;40;1m"
|
||||
let piece = self.position.mailbox[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
result &= " \x1b[0m"
|
||||
else:
|
||||
result &= &"{piece.toPretty()} \x1b[0m"
|
||||
result &= &" \x1b[33;1m{file}\x1b[0m"
|
||||
dec(file)
|
||||
|
||||
result &= "\n\x1b[31;1ma b c d e f g h"
|
||||
result &= "\x1b[0m"
|
||||
## current position for easier visualization
|
||||
return self.position.pretty()
|
||||
|
||||
|
||||
proc toFEN*(self: Chessboard): string =
|
||||
## Returns a FEN string of the current
|
||||
## position in the chessboard
|
||||
var skip: int
|
||||
# Piece placement data
|
||||
for i in 0..7:
|
||||
skip = 0
|
||||
for j in 0..7:
|
||||
let piece = self.position.mailbox[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
inc(skip)
|
||||
elif skip > 0:
|
||||
result &= &"{skip}{piece.toChar()}"
|
||||
skip = 0
|
||||
else:
|
||||
result &= piece.toChar()
|
||||
if skip > 0:
|
||||
result &= $skip
|
||||
if i < 7:
|
||||
result &= "/"
|
||||
result &= " "
|
||||
# Active color
|
||||
result &= (if self.position.sideToMove == White: "w" else: "b")
|
||||
result &= " "
|
||||
# Castling availability
|
||||
let castleWhite = self.position.castlingAvailability[White]
|
||||
let castleBlack = self.position.castlingAvailability[Black]
|
||||
if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||
result &= "-"
|
||||
else:
|
||||
if castleWhite.king:
|
||||
result &= "K"
|
||||
if castleWhite.queen:
|
||||
result &= "Q"
|
||||
if castleBlack.king:
|
||||
result &= "k"
|
||||
if castleBlack.queen:
|
||||
result &= "q"
|
||||
result &= " "
|
||||
# En passant target
|
||||
if self.position.enPassantSquare == nullSquare():
|
||||
result &= "-"
|
||||
else:
|
||||
result &= self.position.enPassantSquare.toAlgebraic()
|
||||
result &= " "
|
||||
# Halfmove clock
|
||||
result &= $self.position.halfMoveClock
|
||||
result &= " "
|
||||
# Fullmove number
|
||||
result &= $self.position.fullMoveCount
|
||||
return self.position.toFEN()
|
||||
|
||||
|
||||
proc drawByRepetition*(self: var Chessboard): bool =
|
||||
|
@ -559,29 +101,3 @@ proc drawByRepetition*(self: var Chessboard): bool =
|
|||
return true
|
||||
dec(i)
|
||||
|
||||
|
||||
proc hash*(self: var Chessboard) =
|
||||
## Computes the zobrist hash of the current
|
||||
## position. This only needs to be called when
|
||||
## a position is loaded the first time, as all
|
||||
## subsequent hashes are updated incrementally
|
||||
## at every call to doMove()
|
||||
self.position.zobristKey = ZobristKey(0)
|
||||
|
||||
if self.position.sideToMove == Black:
|
||||
self.position.zobristKey = self.position.zobristKey xor getBlackToMoveKey()
|
||||
|
||||
for sq in self.getOccupancy():
|
||||
self.position.zobristKey = self.position.zobristKey xor self.getPiece(sq).getKey(sq)
|
||||
|
||||
if self.position.castlingAvailability[White].king:
|
||||
self.position.zobristKey = self.position.zobristKey xor getKingSideCastlingKey(White)
|
||||
if self.position.castlingAvailability[White].queen:
|
||||
self.position.zobristKey = self.position.zobristKey xor getQueenSideCastlingKey(White)
|
||||
if self.position.castlingAvailability[Black].king:
|
||||
self.position.zobristKey = self.position.zobristKey xor getKingSideCastlingKey(Black)
|
||||
if self.position.castlingAvailability[Black].queen:
|
||||
self.position.zobristKey = self.position.zobristKey xor getQueenSideCastlingKey(Black)
|
||||
|
||||
if self.position.enPassantSquare != nullSquare():
|
||||
self.position.zobristKey = self.position.zobristKey xor getEnPassantKey(fileFromSquare(self.position.enPassantSquare))
|
||||
|
|
|
@ -21,7 +21,7 @@ type
|
|||
|
||||
# Stolen from https://www.chessprogramming.org/PeSTO's_Evaluation_Function
|
||||
const
|
||||
PAWN_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||
PAWN_MIDDLEGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
98, 134, 61, 95, 68, 126, 34, -11,
|
||||
-6, 7, 26, 31, 65, 56, 25, -20,
|
||||
|
@ -32,7 +32,7 @@ const
|
|||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
]
|
||||
|
||||
PAWN_ENDGAME_SCORES: array[64, Score] = [
|
||||
PAWN_ENDGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
178, 173, 158, 134, 147, 132, 165, 187,
|
||||
94, 100, 85, 67, 56, 53, 82, 84,
|
||||
|
@ -43,7 +43,7 @@ const
|
|||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
]
|
||||
|
||||
KNIGHT_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||
KNIGHT_MIDDLEGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
-167, -89, -34, -49, 61, -97, -15, -107,
|
||||
-73, -41, 72, 36, 23, 62, 7, -17,
|
||||
-47, 60, 37, 65, 84, 129, 73, 44,
|
||||
|
@ -54,7 +54,7 @@ const
|
|||
-105, -21, -58, -33, -17, -28, -19, -23,
|
||||
]
|
||||
|
||||
KNIGHT_ENDGAME_SCORES: array[64, Score] = [
|
||||
KNIGHT_ENDGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
-58, -38, -13, -28, -31, -27, -63, -99,
|
||||
-25, -8, -25, -2, -9, -25, -24, -52,
|
||||
-24, -20, 10, 9, -1, -9, -19, -41,
|
||||
|
@ -65,7 +65,7 @@ const
|
|||
-29, -51, -23, -15, -22, -18, -50, -64,
|
||||
]
|
||||
|
||||
BISHOP_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||
BISHOP_MIDDLEGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
-29, 4, -82, -37, -25, -42, 7, -8,
|
||||
-26, 16, -18, -13, 30, 59, 18, -47,
|
||||
-16, 37, 43, 40, 35, 50, 37, -2,
|
||||
|
@ -76,7 +76,7 @@ const
|
|||
-33, -3, -14, -21, -13, -12, -39, -21,
|
||||
]
|
||||
|
||||
BISHOP_ENDGAME_SCORES: array[64, Score] = [
|
||||
BISHOP_ENDGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
-14, -21, -11, -8, -7, -9, -17, -24,
|
||||
-8, -4, 7, -12, -3, -13, -4, -14,
|
||||
2, -8, 0, -1, -2, 6, 0, 4,
|
||||
|
@ -87,7 +87,7 @@ const
|
|||
-23, -9, -23, -5, -9, -16, -5, -17,
|
||||
]
|
||||
|
||||
ROOK_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||
ROOK_MIDDLEGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
32, 42, 32, 51, 63, 9, 31, 43,
|
||||
27, 32, 58, 62, 80, 67, 26, 44,
|
||||
-5, 19, 26, 36, 17, 45, 61, 16,
|
||||
|
@ -98,7 +98,7 @@ const
|
|||
-19, -13, 1, 17, 16, 7, -37, -26,
|
||||
]
|
||||
|
||||
ROOK_ENDGAME_SCORES: array[64, Score] = [
|
||||
ROOK_ENDGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
13, 10, 18, 15, 12, 12, 8, 5,
|
||||
11, 13, 13, 11, -3, 3, 8, 3,
|
||||
7, 7, 7, 5, 4, -3, -5, -3,
|
||||
|
@ -109,7 +109,7 @@ const
|
|||
-9, 2, 3, -1, -5, -13, 4, -20,
|
||||
]
|
||||
|
||||
QUEEN_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||
QUEEN_MIDDLEGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
-28, 0, 29, 12, 59, 44, 43, 45,
|
||||
-24, -39, -5, 1, -16, 57, 28, 54,
|
||||
-13, -17, 7, 8, 29, 56, 47, 57,
|
||||
|
@ -120,7 +120,7 @@ const
|
|||
-1, -18, -9, 10, -15, -25, -31, -50,
|
||||
]
|
||||
|
||||
QUEEN_ENDGAME_SCORES: array[64, Score] = [
|
||||
QUEEN_ENDGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
-9, 22, 22, 27, 27, 19, 10, 20,
|
||||
-17, 20, 32, 41, 58, 25, 30, 0,
|
||||
-20, 6, 9, 49, 47, 35, 19, 9,
|
||||
|
@ -131,7 +131,7 @@ const
|
|||
-33, -28, -22, -43, -5, -32, -20, -41,
|
||||
]
|
||||
|
||||
KING_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||
KING_MIDDLEGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
-65, 23, 16, -15, -56, -34, 2, 13,
|
||||
29, -1, -20, -7, -8, -4, -38, -29,
|
||||
-9, 24, 2, -16, -20, 6, 22, -22,
|
||||
|
@ -142,7 +142,7 @@ const
|
|||
-15, 36, 12, -54, 8, -28, 24, 14,
|
||||
]
|
||||
|
||||
KING_ENDGAME_SCORES: array[64, Score] = [
|
||||
KING_ENDGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
-74, -35, -18, -18, -11, 15, 4, -17,
|
||||
-12, 17, 14, 17, 17, 38, 23, 11,
|
||||
10, 17, 23, 15, 20, 45, 44, 13,
|
||||
|
@ -154,10 +154,10 @@ const
|
|||
]
|
||||
|
||||
# Bishop, King, Knight, Pawn, Queen, Rook
|
||||
MIDDLEGAME_WEIGHTS: array[6, Score] = [365, 0, 337, 82, 1025, 477]
|
||||
ENDGAME_WEIGHTS: array[6, Score] = [297, 0, 281, 94, 936, 512]
|
||||
MIDDLEGAME_WEIGHTS: array[PieceKind.Bishop..PieceKind.Rook, Score] = [365, 0, 337, 82, 1025, 477]
|
||||
ENDGAME_WEIGHTS: array[PieceKind.Bishop..PieceKind.Rook, Score] = [297, 0, 281, 94, 936, 512]
|
||||
|
||||
MIDDLEGAME_PSQ_TABLES: array[6, array[64, Score]] = [
|
||||
MIDDLEGAME_PSQ_TABLES: array[PieceKind.Bishop..PieceKind.Rook, array[Square(0)..Square(63), Score]] = [
|
||||
BISHOP_MIDDLEGAME_SCORES,
|
||||
KING_MIDDLEGAME_SCORES,
|
||||
KNIGHT_MIDDLEGAME_SCORES,
|
||||
|
@ -166,7 +166,7 @@ const
|
|||
ROOK_MIDDLEGAME_SCORES
|
||||
]
|
||||
|
||||
ENDGAME_PSQ_TABLES: array[6, array[64, Score]] = [
|
||||
ENDGAME_PSQ_TABLES: array[PieceKind.Bishop..PieceKind.Rook, array[Square(0)..Square(63), Score]] = [
|
||||
BISHOP_ENDGAME_SCORES,
|
||||
KING_ENDGAME_SCORES,
|
||||
KNIGHT_ENDGAME_SCORES,
|
||||
|
@ -180,18 +180,18 @@ const
|
|||
|
||||
|
||||
var
|
||||
MIDDLEGAME_VALUE_TABLES: array[2, array[6, array[64, Score]]]
|
||||
ENDGAME_VALUE_TABLES: array[2, array[6, array[64, Score]]]
|
||||
MIDDLEGAME_VALUE_TABLES: array[PieceColor.White..PieceColor.Black, array[PieceKind.Bishop..PieceKind.Rook, array[Square(0)..Square(63), Score]]]
|
||||
ENDGAME_VALUE_TABLES: array[PieceColor.White..PieceColor.Black, array[PieceKind.Bishop..PieceKind.Rook, array[Square(0)..Square(63), Score]]]
|
||||
|
||||
|
||||
|
||||
proc initializeTables =
|
||||
for kind in [Bishop, King, Knight, Pawn, Queen, Rook]:
|
||||
for sq in 0..63:
|
||||
MIDDLEGAME_VALUE_TABLES[White.int][kind.int][sq] = MIDDLEGAME_WEIGHTS[kind.int] + MIDDLEGAME_PSQ_TABLES[kind.int][sq]
|
||||
ENDGAME_VALUE_TABLES[White.int][kind.int][sq] = ENDGAME_WEIGHTS[kind.int] + ENDGAME_PSQ_TABLES[kind.int][sq]
|
||||
MIDDLEGAME_VALUE_TABLES[Black.int][kind.int][sq] = MIDDLEGAME_WEIGHTS[kind.int] + MIDDLEGAME_PSQ_TABLES[kind.int][sq xor 56]
|
||||
ENDGAME_VALUE_TABLES[Black.int][kind.int][sq] = ENDGAME_WEIGHTS[kind.int] + ENDGAME_PSQ_TABLES[kind.int][sq xor 56]
|
||||
for sq in Square(0)..Square(63):
|
||||
MIDDLEGAME_VALUE_TABLES[White][kind][sq] = MIDDLEGAME_WEIGHTS[kind] + MIDDLEGAME_PSQ_TABLES[kind][sq]
|
||||
ENDGAME_VALUE_TABLES[White][kind][sq] = ENDGAME_WEIGHTS[kind] + ENDGAME_PSQ_TABLES[kind][sq]
|
||||
MIDDLEGAME_VALUE_TABLES[Black][kind][sq] = MIDDLEGAME_WEIGHTS[kind] + MIDDLEGAME_PSQ_TABLES[kind][sq xor 56]
|
||||
ENDGAME_VALUE_TABLES[Black][kind][sq] = ENDGAME_WEIGHTS[kind] + ENDGAME_PSQ_TABLES[kind][sq xor 56]
|
||||
|
||||
|
||||
initializeTables()
|
||||
|
@ -202,12 +202,12 @@ func highestEval*: Score {.inline.} = Score(25_000)
|
|||
func mateScore*: Score {.inline.} = lowestEval() + 1
|
||||
|
||||
|
||||
func getGamePhase(board: Chessboard): int {.inline.} =
|
||||
func getGamePhase(position: Position): int {.inline.} =
|
||||
## Computes the game phase according to
|
||||
## how many pieces are left on the board
|
||||
result = 0
|
||||
for sq in board.getOccupancy():
|
||||
case board.getPiece(sq).kind:
|
||||
for sq in position.getOccupancy():
|
||||
case position.getPiece(sq).kind:
|
||||
of Bishop, Knight:
|
||||
inc(result)
|
||||
of Queen:
|
||||
|
@ -221,26 +221,26 @@ func getGamePhase(board: Chessboard): int {.inline.} =
|
|||
result = min(24, result)
|
||||
|
||||
|
||||
proc getPieceScore*(board: Chessboard, square: Square): Score =
|
||||
proc getPieceScore*(position: Position, square: Square): Score =
|
||||
## Returns the value of the piece located at
|
||||
## the given square
|
||||
let
|
||||
piece = board.getPiece(square)
|
||||
middleGameScore = MIDDLEGAME_VALUE_TABLES[piece.color.int][piece.kind.int][square.int]
|
||||
endGameScore = ENDGAME_VALUE_TABLES[piece.color.int][piece.kind.int][square.int]
|
||||
middleGamePhase = board.getGamePhase()
|
||||
piece = position.getPiece(square)
|
||||
middleGameScore = MIDDLEGAME_VALUE_TABLES[piece.color][piece.kind][square]
|
||||
endGameScore = ENDGAME_VALUE_TABLES[piece.color][piece.kind][square]
|
||||
middleGamePhase = position.getGamePhase()
|
||||
endGamePhase = 24 - middleGamePhase
|
||||
|
||||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
||||
|
||||
|
||||
proc getPieceScore*(board: Chessboard, piece: Piece, square: Square): Score =
|
||||
proc getPieceScore*(position: Position, piece: Piece, square: Square): Score =
|
||||
## Returns the value the given piece would have if it
|
||||
## were at the given square
|
||||
let
|
||||
middleGameScore = MIDDLEGAME_VALUE_TABLES[piece.color.int][piece.kind.int][square.int]
|
||||
endGameScore = ENDGAME_VALUE_TABLES[piece.color.int][piece.kind.int][square.int]
|
||||
middleGamePhase = board.getGamePhase()
|
||||
middleGameScore = MIDDLEGAME_VALUE_TABLES[piece.color][piece.kind][square]
|
||||
endGameScore = ENDGAME_VALUE_TABLES[piece.color][piece.kind][square]
|
||||
middleGamePhase = position.getGamePhase()
|
||||
endGamePhase = 24 - middleGamePhase
|
||||
|
||||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
||||
|
@ -250,33 +250,33 @@ proc evaluateMaterial(board: ChessBoard): Score =
|
|||
## Returns a material and position evaluation
|
||||
## for the current side to move
|
||||
let
|
||||
middleGamePhase = board.getGamePhase()
|
||||
middleGamePhase = board.position.getGamePhase()
|
||||
endGamePhase = 24 - middleGamePhase
|
||||
var
|
||||
# White, Black
|
||||
middleGameScores: array[2, Score] = [0, 0]
|
||||
endGameScores: array[2, Score] = [0, 0]
|
||||
middleGameScores: array[PieceColor.White..PieceColor.Black, Score] = [0, 0]
|
||||
endGameScores: array[PieceColor.White..PieceColor.Black, Score] = [0, 0]
|
||||
|
||||
for sq in board.getOccupancy():
|
||||
let piece = board.getPiece(sq)
|
||||
middleGameScores[piece.color.int] += MIDDLEGAME_VALUE_TABLES[piece.color.int][piece.kind.int][sq.int]
|
||||
endGameScores[piece.color.int] += ENDGAME_VALUE_TABLES[piece.color.int][piece.kind.int][sq.int]
|
||||
for sq in board.position.getOccupancy():
|
||||
let piece = board.position.getPiece(sq)
|
||||
middleGameScores[piece.color] += MIDDLEGAME_VALUE_TABLES[piece.color][piece.kind][sq]
|
||||
endGameScores[piece.color] += ENDGAME_VALUE_TABLES[piece.color][piece.kind][sq]
|
||||
|
||||
let
|
||||
sideToMove = board.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
middleGameScore = middleGameScores[sideToMove.int] - middleGameScores[nonSideToMove.int]
|
||||
endGameScore = endGameScores[sideToMove.int] - endGameScores[nonSideToMove.int]
|
||||
middleGameScore = middleGameScores[sideToMove] - middleGameScores[nonSideToMove]
|
||||
endGameScore = endGameScores[sideToMove] - endGameScores[nonSideToMove]
|
||||
|
||||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
||||
|
||||
|
||||
proc evaluatePawnStructure(board: Chessboard): Score {.used.} =
|
||||
proc evaluatePawnStructure(position: Position): Score {.used.} =
|
||||
## Evaluates the pawn structure of the current
|
||||
## position for the side to move
|
||||
let
|
||||
sideToMove = board.position.sideToMove
|
||||
friendlyPawns = board.getOccupancyFor(sideToMove)
|
||||
sideToMove = position.sideToMove
|
||||
friendlyPawns = position.getOccupancyFor(sideToMove)
|
||||
|
||||
# Doubled pawns are a bad idea
|
||||
var doubledPawns = 0
|
||||
|
@ -299,9 +299,9 @@ proc evaluatePawnStructure(board: Chessboard): Score {.used.} =
|
|||
# stronger
|
||||
var
|
||||
strongPawnIncrement = Score(0)
|
||||
for pawn in board.getBitboard(Pawn, White):
|
||||
if board.getPawnAttacks(pawn, White) != 0:
|
||||
strongPawnIncrement += board.getPieceScore(pawn) div Score(4)
|
||||
for pawn in position.getBitboard(Pawn, White):
|
||||
if position.getPawnAttacks(pawn, White) != 0:
|
||||
strongPawnIncrement += position.getPieceScore(pawn) div Score(4)
|
||||
|
||||
return DOUBLED_PAWNS_MALUS[doubledPawns] + ISOLATED_PAWN_MALUS[isolatedPawns] + strongPawnIncrement
|
||||
|
||||
|
|
|
@ -36,10 +36,10 @@ proc generatePawnMoves(self: var Chessboard, moves: var MoveList, destinationMas
|
|||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
pawns = self.getBitboard(Pawn, sideToMove)
|
||||
occupancy = self.getOccupancy()
|
||||
pawns = self.position.getBitboard(Pawn, sideToMove)
|
||||
occupancy = self.position.getOccupancy()
|
||||
# We can only capture enemy pieces (except the king)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
enemyPieces = self.position.getOccupancyFor(nonSideToMove)
|
||||
epTarget = self.position.enPassantSquare
|
||||
diagonalPins = self.position.diagonalPins
|
||||
orthogonalPins = self.position.orthogonalPins
|
||||
|
@ -48,7 +48,7 @@ proc generatePawnMoves(self: var Chessboard, moves: var MoveList, destinationMas
|
|||
# TODO: Give names to ranks and files so we don't have to assume a
|
||||
# specific board layout when calling get(Rank|File)Mask
|
||||
startingRank = if sideToMove == White: getRankMask(6) else: getRankMask(1)
|
||||
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
|
||||
friendlyKing = self.position.getBitboard(King, sideToMove).toSquare()
|
||||
|
||||
# Single and double pushes
|
||||
|
||||
|
@ -146,33 +146,33 @@ proc generatePawnMoves(self: var Chessboard, moves: var MoveList, destinationMas
|
|||
# king is in check after en passant when it actually isn't
|
||||
# (see pos fen rnbqkbnr/pppp1ppp/8/2P5/K7/8/PPPP1PPP/RNBQ1BNR b kq - 0 1 moves b7b5 c5b6)
|
||||
let epPawnSquare = epPawn.toSquare()
|
||||
let epPiece = self.getPiece(epPawnSquare)
|
||||
self.removePiece(epPawnSquare)
|
||||
if not self.isOccupancyAttacked(friendlyKing, newOccupancy):
|
||||
let epPiece = self.position.getPiece(epPawnSquare)
|
||||
self.position.removePiece(epPawnSquare)
|
||||
if not self.position.isOccupancyAttacked(friendlyKing, newOccupancy):
|
||||
# En passant does not create a check on the king: all good
|
||||
moves.add(createMove(friendlyPawn, epBitboard, EnPassant))
|
||||
self.spawnPiece(epPawnSquare, epPiece)
|
||||
self.position.spawnPiece(epPawnSquare, epPiece)
|
||||
if epRight != 0:
|
||||
# Note that this isn't going to be the same pawn from the previous if block!
|
||||
let
|
||||
friendlyPawn = epBitboard.backwardLeftRelativeTo(sideToMove)
|
||||
newOccupancy = occupancy and not epPawn and not friendlyPawn or epBitboard
|
||||
let epPawnSquare = epPawn.toSquare()
|
||||
let epPiece = self.getPiece(epPawnSquare)
|
||||
self.removePiece(epPawnSquare)
|
||||
if not self.isOccupancyAttacked(friendlyKing, newOccupancy):
|
||||
let epPiece = self.position.getPiece(epPawnSquare)
|
||||
self.position.removePiece(epPawnSquare)
|
||||
if not self.position.isOccupancyAttacked(friendlyKing, newOccupancy):
|
||||
# En passant does not create a check on the king: all good
|
||||
moves.add(createMove(friendlyPawn, epBitboard, EnPassant))
|
||||
self.spawnPiece(epPawnSquare, epPiece)
|
||||
self.position.spawnPiece(epPawnSquare, epPiece)
|
||||
|
||||
|
||||
proc generateRookMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
occupancy = self.getOccupancy()
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, sideToMove.opposite())
|
||||
rooks = self.getBitboard(Rook, sideToMove)
|
||||
queens = self.getBitboard(Queen, sideToMove)
|
||||
occupancy = self.position.getOccupancy()
|
||||
enemyPieces = self.position.getOccupancyFor(sideToMove.opposite()) and not self.position.getBitboard(King, sideToMove.opposite())
|
||||
rooks = self.position.getBitboard(Rook, sideToMove)
|
||||
queens = self.position.getBitboard(Queen, sideToMove)
|
||||
movableRooks = not self.position.diagonalPins and (queens or rooks)
|
||||
pinMask = self.position.orthogonalPins
|
||||
pinnedRooks = movableRooks and pinMask
|
||||
|
@ -200,10 +200,10 @@ proc generateRookMoves(self: Chessboard, moves: var MoveList, destinationMask: B
|
|||
proc generateBishopMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
occupancy = self.getOccupancy()
|
||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, sideToMove.opposite())
|
||||
bishops = self.getBitboard(Bishop, sideToMove)
|
||||
queens = self.getBitboard(Queen, sideToMove)
|
||||
occupancy = self.position.getOccupancy()
|
||||
enemyPieces = self.position.getOccupancyFor(sideToMove.opposite()) and not self.position.getBitboard(King, sideToMove.opposite())
|
||||
bishops = self.position.getBitboard(Bishop, sideToMove)
|
||||
queens = self.position.getBitboard(Queen, sideToMove)
|
||||
movableBishops = not self.position.orthogonalPins and (queens or bishops)
|
||||
pinMask = self.position.diagonalPins
|
||||
pinnedBishops = movableBishops and pinMask
|
||||
|
@ -229,29 +229,29 @@ proc generateBishopMoves(self: Chessboard, moves: var MoveList, destinationMask:
|
|||
proc generateKingMoves(self: Chessboard, moves: var MoveList, capturesOnly=false) =
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
king = self.getBitboard(King, sideToMove)
|
||||
occupancy = self.getOccupancy()
|
||||
king = self.position.getBitboard(King, sideToMove)
|
||||
occupancy = self.position.getOccupancy()
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove) and not self.getBitboard(King, nonSideToMove)
|
||||
enemyPieces = self.position.getOccupancyFor(nonSideToMove) and not self.position.getBitboard(King, nonSideToMove)
|
||||
bitboard = getKingAttacks(king.toSquare())
|
||||
noKingOccupancy = occupancy and not king
|
||||
if not capturesOnly:
|
||||
for square in bitboard and not occupancy:
|
||||
if not self.isOccupancyAttacked(square, noKingOccupancy):
|
||||
if not self.position.isOccupancyAttacked(square, noKingOccupancy):
|
||||
moves.add(createMove(king, square))
|
||||
for square in bitboard and enemyPieces:
|
||||
if not self.isOccupancyAttacked(square, noKingOccupancy):
|
||||
if not self.position.isOccupancyAttacked(square, noKingOccupancy):
|
||||
moves.add(createMove(king, square, Capture))
|
||||
|
||||
|
||||
proc generateKnightMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
||||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
knights = self.getBitboard(Knight, sideToMove)
|
||||
knights = self.position.getBitboard(Knight, sideToMove)
|
||||
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)
|
||||
enemyPieces = self.position.getOccupancyFor(nonSideToMove) and not self.position.getBitboard(King, nonSideToMove)
|
||||
for square in unpinnedKnights:
|
||||
let bitboard = getKnightAttacks(square)
|
||||
for target in bitboard and destinationMask and not enemyPieces:
|
||||
|
@ -264,8 +264,8 @@ proc generateCastling(self: Chessboard, moves: var MoveList) =
|
|||
let
|
||||
sideToMove = self.position.sideToMove
|
||||
castlingRights = self.canCastle()
|
||||
kingSquare = self.getBitboard(King, sideToMove).toSquare()
|
||||
kingPiece = self.getPiece(kingSquare)
|
||||
kingSquare = self.position.getBitboard(King, sideToMove).toSquare()
|
||||
kingPiece = self.position.getPiece(kingSquare)
|
||||
if castlingRights.king:
|
||||
moves.add(createMove(kingSquare, kingPiece.kingSideCastling(), Castle))
|
||||
if castlingRights.queen:
|
||||
|
@ -296,7 +296,7 @@ proc generateMoves*(self: var Chessboard, moves: var MoveList, capturesOnly: boo
|
|||
var destinationMask: Bitboard
|
||||
if not self.inCheck():
|
||||
# Not in check: cannot move over friendly pieces
|
||||
destinationMask = not self.getOccupancyFor(sideToMove)
|
||||
destinationMask = not self.position.getOccupancyFor(sideToMove)
|
||||
else:
|
||||
# We *are* in check (from a single piece, because the two checks
|
||||
# case was handled above already). If the piece is a slider, we'll
|
||||
|
@ -309,8 +309,8 @@ proc generateMoves*(self: var Chessboard, moves: var MoveList, capturesOnly: boo
|
|||
checker = self.position.checkers.lowestSquare()
|
||||
checkerBB = checker.toBitboard()
|
||||
# epTarget = self.position.enPassantSquare
|
||||
# checkerPiece = self.getPiece(checker)
|
||||
destinationMask = getRayBetween(checker, self.getBitboard(King, sideToMove).toSquare()) or checkerBB
|
||||
# checkerPiece = self.position.getPiece(checker)
|
||||
destinationMask = getRayBetween(checker, self.position.getBitboard(King, sideToMove).toSquare()) or checkerBB
|
||||
# TODO: This doesn't really work. I've addressed the issue for now, but it's kinda ugly. Find a better
|
||||
# solution
|
||||
# if checkerPiece.kind == Pawn and checkerBB.backwardRelativeTo(checkerPiece.color).toSquare() == epTarget:
|
||||
|
@ -322,7 +322,7 @@ proc generateMoves*(self: var Chessboard, moves: var MoveList, capturesOnly: boo
|
|||
if capturesOnly:
|
||||
# Note: This does not cover en passant (which is good because it's a capture,
|
||||
# but the "fix" stands on flimsy ground)
|
||||
destinationMask = destinationMask and self.getOccupancyFor(nonSideToMove)
|
||||
destinationMask = destinationMask and self.position.getOccupancyFor(nonSideToMove)
|
||||
self.generatePawnMoves(moves, destinationMask)
|
||||
self.generateKnightMoves(moves, destinationMask)
|
||||
self.generateRookMoves(moves, destinationMask)
|
||||
|
@ -340,7 +340,7 @@ proc doMove*(self: var Chessboard, move: Move) =
|
|||
self.positions.add(self.position)
|
||||
|
||||
# Final checks
|
||||
let piece = self.getPiece(move.startSquare)
|
||||
let piece = self.position.getPiece(move.startSquare)
|
||||
when not defined(danger):
|
||||
doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}"
|
||||
|
||||
|
@ -383,8 +383,8 @@ proc doMove*(self: var Chessboard, move: Move) =
|
|||
if move.isEnPassant():
|
||||
# Make the en passant pawn disappear
|
||||
let epPawnSquare = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
|
||||
self.position.zobristKey = self.position.zobristKey xor self.getPiece(epPawnSquare).getKey(epPawnSquare)
|
||||
self.removePiece(epPawnSquare)
|
||||
self.position.zobristKey = self.position.zobristKey xor self.position.getPiece(epPawnSquare).getKey(epPawnSquare)
|
||||
self.position.removePiece(epPawnSquare)
|
||||
|
||||
if move.isCastling() or piece.kind == King:
|
||||
# If the king has moved, all castling rights for the side to
|
||||
|
@ -399,15 +399,15 @@ proc doMove*(self: var Chessboard, move: Move) =
|
|||
|
||||
if move.targetSquare == piece.kingSideCastling():
|
||||
source = piece.color.kingSideRook()
|
||||
rook = self.getPiece(source)
|
||||
rook = self.position.getPiece(source)
|
||||
target = rook.kingSideCastling()
|
||||
|
||||
elif move.targetSquare == piece.queenSideCastling():
|
||||
source = piece.color.queenSideRook()
|
||||
rook = self.getPiece(source)
|
||||
rook = self.position.getPiece(source)
|
||||
target = rook.queenSideCastling()
|
||||
|
||||
self.movePiece(source, target)
|
||||
self.position.movePiece(source, target)
|
||||
self.position.zobristKey = self.position.zobristKey xor piece.getKey(source)
|
||||
self.position.zobristKey = self.position.zobristKey xor piece.getKey(target)
|
||||
|
||||
|
@ -421,9 +421,9 @@ proc doMove*(self: var Chessboard, move: Move) =
|
|||
|
||||
if move.isCapture():
|
||||
# Get rid of captured pieces
|
||||
let captured = self.getPiece(move.targetSquare)
|
||||
let captured = self.position.getPiece(move.targetSquare)
|
||||
self.position.zobristKey = self.position.zobristKey xor captured.getKey(move.targetSquare)
|
||||
self.removePiece(move.targetSquare)
|
||||
self.position.removePiece(move.targetSquare)
|
||||
# If a rook has been captured, castling on that side is prohibited
|
||||
if captured.kind == Rook:
|
||||
if move.targetSquare == captured.color.kingSideRook():
|
||||
|
@ -432,13 +432,13 @@ proc doMove*(self: var Chessboard, move: Move) =
|
|||
self.position.castlingAvailability[captured.color].queen = false
|
||||
|
||||
# Move the piece to its target square
|
||||
self.movePiece(move)
|
||||
self.position.movePiece(move)
|
||||
self.position.zobristKey = self.position.zobristKey xor piece.getKey(move.startSquare)
|
||||
self.position.zobristKey = self.position.zobristKey xor piece.getKey(move.targetSquare)
|
||||
if move.isPromotion():
|
||||
# Move is a pawn promotion: get rid of the pawn
|
||||
# and spawn a new piece
|
||||
self.removePiece(move.targetSquare)
|
||||
self.position.removePiece(move.targetSquare)
|
||||
self.position.zobristKey = self.position.zobristKey xor piece.getKey(move.targetSquare)
|
||||
var spawnedPiece: Piece
|
||||
case move.getPromotionType():
|
||||
|
@ -454,9 +454,9 @@ proc doMove*(self: var Chessboard, move: Move) =
|
|||
# Unreachable
|
||||
discard
|
||||
self.position.zobristKey = self.position.zobristKey xor spawnedPiece.getKey(move.targetSquare)
|
||||
self.spawnPiece(move.targetSquare, spawnedPiece)
|
||||
self.position.spawnPiece(move.targetSquare, spawnedPiece)
|
||||
# Updates checks and pins for the (new) side to move
|
||||
self.updateChecksAndPins()
|
||||
self.position.updateChecksAndPins()
|
||||
# Last updates to zobrist key
|
||||
if self.position.castlingAvailability[piece.color].king:
|
||||
self.position.zobristKey = self.position.zobristKey xor getKingSideCastlingKey(piece.color)
|
||||
|
@ -496,7 +496,7 @@ proc testPiece(piece: Piece, kind: PieceKind, color: PieceColor) =
|
|||
|
||||
|
||||
proc testPieceCount(board: Chessboard, kind: PieceKind, color: PieceColor, count: int) =
|
||||
let pieces = board.countPieces(kind, color)
|
||||
let pieces = board.position.countPieces(kind, color)
|
||||
doAssert pieces == count, &"expected {count} pieces of kind {kind} and color {color}, got {pieces} instead"
|
||||
|
||||
|
||||
|
@ -553,45 +553,45 @@ proc basicTests* =
|
|||
|
||||
# Pawns
|
||||
for loc in ["a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2"]:
|
||||
testPiece(board.getPiece(loc), Pawn, White)
|
||||
testPiece(board.position.getPiece(loc), Pawn, White)
|
||||
for loc in ["a7", "b7", "c7", "d7", "e7", "f7", "g7", "h7"]:
|
||||
testPiece(board.getPiece(loc), Pawn, Black)
|
||||
testPiece(board.position.getPiece(loc), Pawn, Black)
|
||||
# Rooks
|
||||
testPiece(board.getPiece("a1"), Rook, White)
|
||||
testPiece(board.getPiece("h1"), Rook, White)
|
||||
testPiece(board.getPiece("a8"), Rook, Black)
|
||||
testPiece(board.getPiece("h8"), Rook, Black)
|
||||
testPiece(board.position.getPiece("a1"), Rook, White)
|
||||
testPiece(board.position.getPiece("h1"), Rook, White)
|
||||
testPiece(board.position.getPiece("a8"), Rook, Black)
|
||||
testPiece(board.position.getPiece("h8"), Rook, Black)
|
||||
# Knights
|
||||
testPiece(board.getPiece("b1"), Knight, White)
|
||||
testPiece(board.getPiece("g1"), Knight, White)
|
||||
testPiece(board.getPiece("b8"), Knight, Black)
|
||||
testPiece(board.getPiece("g8"), Knight, Black)
|
||||
testPiece(board.position.getPiece("b1"), Knight, White)
|
||||
testPiece(board.position.getPiece("g1"), Knight, White)
|
||||
testPiece(board.position.getPiece("b8"), Knight, Black)
|
||||
testPiece(board.position.getPiece("g8"), Knight, Black)
|
||||
# Bishops
|
||||
testPiece(board.getPiece("c1"), Bishop, White)
|
||||
testPiece(board.getPiece("f1"), Bishop, White)
|
||||
testPiece(board.getPiece("c8"), Bishop, Black)
|
||||
testPiece(board.getPiece("f8"), Bishop, Black)
|
||||
testPiece(board.position.getPiece("c1"), Bishop, White)
|
||||
testPiece(board.position.getPiece("f1"), Bishop, White)
|
||||
testPiece(board.position.getPiece("c8"), Bishop, Black)
|
||||
testPiece(board.position.getPiece("f8"), Bishop, Black)
|
||||
# Kings
|
||||
testPiece(board.getPiece("e1"), King, White)
|
||||
testPiece(board.getPiece("e8"), King, Black)
|
||||
testPiece(board.position.getPiece("e1"), King, White)
|
||||
testPiece(board.position.getPiece("e8"), King, Black)
|
||||
# Queens
|
||||
testPiece(board.getPiece("d1"), Queen, White)
|
||||
testPiece(board.getPiece("d8"), Queen, Black)
|
||||
testPiece(board.position.getPiece("d1"), Queen, White)
|
||||
testPiece(board.position.getPiece("d8"), Queen, Black)
|
||||
|
||||
# Ensure our bitboards match with the board
|
||||
let
|
||||
whitePawns = board.getBitboard(Pawn, White)
|
||||
whiteKnights = board.getBitboard(Knight, White)
|
||||
whiteBishops = board.getBitboard(Bishop, White)
|
||||
whiteRooks = board.getBitboard(Rook, White)
|
||||
whiteQueens = board.getBitboard(Queen, White)
|
||||
whiteKing = board.getBitboard(King, White)
|
||||
blackPawns = board.getBitboard(Pawn, Black)
|
||||
blackKnights = board.getBitboard(Knight, Black)
|
||||
blackBishops = board.getBitboard(Bishop, Black)
|
||||
blackRooks = board.getBitboard(Rook, Black)
|
||||
blackQueens = board.getBitboard(Queen, Black)
|
||||
blackKing = board.getBitboard(King, Black)
|
||||
whitePawns = board.position.getBitboard(Pawn, White)
|
||||
whiteKnights = board.position.getBitboard(Knight, White)
|
||||
whiteBishops = board.position.getBitboard(Bishop, White)
|
||||
whiteRooks = board.position.getBitboard(Rook, White)
|
||||
whiteQueens = board.position.getBitboard(Queen, White)
|
||||
whiteKing = board.position.getBitboard(King, White)
|
||||
blackPawns = board.position.getBitboard(Pawn, Black)
|
||||
blackKnights = board.position.getBitboard(Knight, Black)
|
||||
blackBishops = board.position.getBitboard(Bishop, Black)
|
||||
blackRooks = board.position.getBitboard(Rook, Black)
|
||||
blackQueens = board.position.getBitboard(Queen, Black)
|
||||
blackKing = board.position.getBitboard(King, Black)
|
||||
whitePawnSquares = @[makeSquare(6'i8, 0'i8), makeSquare(6, 1), makeSquare(6, 2), makeSquare(6, 3), makeSquare(6, 4), makeSquare(6, 5), makeSquare(6, 6), makeSquare(6, 7)]
|
||||
whiteKnightSquares = @[makeSquare(7'i8, 1'i8), makeSquare(7, 6)]
|
||||
whiteBishopSquares = @[makeSquare(7'i8, 2'i8), makeSquare(7, 5)]
|
||||
|
|
|
@ -183,7 +183,6 @@ func `$`*(self: Move): string =
|
|||
result &= ")"
|
||||
|
||||
|
||||
|
||||
func toAlgebraic*(self: Move): string =
|
||||
if self == nullMove():
|
||||
return "null"
|
||||
|
|
|
@ -51,6 +51,7 @@ func isValid*(a: Square): bool {.inline.} = a.int8 in 0..63
|
|||
func isLightSquare*(a: Square): bool {.inline.} = (a.int8 and 2) == 0
|
||||
|
||||
# Overridden operators for our distinct type
|
||||
func `xor`*(a: Square, b: SomeInteger): Square {.inline.} = Square(a.int8 xor b)
|
||||
func `==`*(a, b: Square): bool {.inline.} = a.int8 == b.int8
|
||||
func `!=`*(a, b: Square): bool {.inline.} = a.int8 != b.int8
|
||||
func `<`*(a: Square, b: SomeInteger): bool {.inline.} = a.int8 < b.int8
|
||||
|
|
|
@ -11,10 +11,18 @@
|
|||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
|
||||
|
||||
import bitboards
|
||||
import magics
|
||||
import pieces
|
||||
import zobrist
|
||||
import moves
|
||||
import rays
|
||||
|
||||
export bitboards, magics, pieces, zobrist, moves, rays
|
||||
|
||||
|
||||
type
|
||||
|
@ -57,6 +65,11 @@ type
|
|||
mailbox*: array[Square(0)..Square(63), Piece]
|
||||
|
||||
|
||||
func inCheck*(self: Position): bool {.inline.} =
|
||||
## Returns if the current side to move is in check
|
||||
return self.checkers != 0
|
||||
|
||||
|
||||
func getKingStartingSquare*(color: PieceColor): Square {.inline.} =
|
||||
## Retrieves the starting square of the king
|
||||
## for the given color
|
||||
|
@ -90,3 +103,484 @@ func getOccupancy*(self: Position): Bitboard {.inline.} =
|
|||
## Get the occupancy bitboard for every piece on
|
||||
## the chessboard
|
||||
result = self.getOccupancyFor(Black) or self.getOccupancyFor(White)
|
||||
|
||||
|
||||
func getPawnAttacks*(self: Position, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the locations of the pawns attacking the given square
|
||||
return self.getBitboard(Pawn, attacker) and getPawnAttacks(attacker, square)
|
||||
|
||||
|
||||
func getKingAttacks*(self: Position, square: Square, attacker: PieceColor): Bitboard {.inline.} =
|
||||
## Returns the location of the king if it is attacking the given square
|
||||
result = Bitboard(0)
|
||||
let
|
||||
king = self.getBitboard(King, attacker)
|
||||
squareBB = square.toBitboard()
|
||||
if (getKingAttacks(square) and squareBB) != 0:
|
||||
result = result or king
|
||||
|
||||
|
||||
func getKnightAttacks*(self: Position, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the locations of the knights attacking the given square
|
||||
let
|
||||
knights = self.getBitboard(Knight, attacker)
|
||||
squareBB = square.toBitboard()
|
||||
result = Bitboard(0)
|
||||
for knight in knights:
|
||||
if (getKnightAttacks(knight) and squareBB) != 0:
|
||||
result = result or knight.toBitboard()
|
||||
|
||||
|
||||
proc getSlidingAttacks*(self: Position, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Returns the locations of the sliding pieces attacking the given square
|
||||
let
|
||||
queens = self.getBitboard(Queen, attacker)
|
||||
rooks = self.getBitboard(Rook, attacker) or queens
|
||||
bishops = self.getBitboard(Bishop, attacker) or queens
|
||||
occupancy = self.getOccupancy()
|
||||
squareBB = square.toBitboard()
|
||||
result = Bitboard(0)
|
||||
for rook in rooks:
|
||||
let
|
||||
blockers = occupancy and Rook.getRelevantBlockers(rook)
|
||||
moves = getRookMoves(rook, blockers)
|
||||
# Attack set intersects our chosen square
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or rook.toBitboard()
|
||||
for bishop in bishops:
|
||||
let
|
||||
blockers = occupancy and Bishop.getRelevantBlockers(bishop)
|
||||
moves = getBishopMoves(bishop, blockers)
|
||||
if (moves and squareBB) != 0:
|
||||
result = result or bishop.toBitboard()
|
||||
|
||||
|
||||
proc getAttacksTo*(self: Position, square: Square, attacker: PieceColor): Bitboard =
|
||||
## Computes the attack bitboard for the given square from
|
||||
## the given side
|
||||
result = Bitboard(0) 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 isOccupancyAttacked*(self: Position, square: Square, occupancy: Bitboard): bool =
|
||||
## Returns whether the given square would be attacked by the
|
||||
## enemy side if the board had the given occupancy. This function
|
||||
## is necessary, for example, to make sure sliding attacks can check the
|
||||
## king properly: due to how we generate our attack bitboards, if
|
||||
## the king moved backwards along a ray from a slider we would not
|
||||
## consider it to be in check (because the ray stops at the first
|
||||
## blocker). In order to fix that, in generateKingMoves() we use this
|
||||
## function and pass in the board's occupancy without the moving king so
|
||||
## that we can pick the correct magic bitboard and ray. Also, since this
|
||||
## function doesn't need to generate all the attacks to know whether a
|
||||
## given square is unsafe, it can short circuit at the first attack and
|
||||
## exit early, unlike getAttacksTo
|
||||
let
|
||||
nonSideToMove = self.sideToMove.opposite()
|
||||
knights = self.getBitboard(Knight, nonSideToMove)
|
||||
|
||||
if (getKnightAttacks(square) and knights) != 0:
|
||||
return true
|
||||
|
||||
let king = self.getBitboard(King, nonSideToMove)
|
||||
|
||||
if (getKingAttacks(square) and king) != 0:
|
||||
return true
|
||||
|
||||
let
|
||||
queens = self.getBitboard(Queen, nonSideToMove)
|
||||
bishops = self.getBitboard(Bishop, nonSideToMove) or queens
|
||||
|
||||
if (getBishopMoves(square, occupancy) and bishops) != 0:
|
||||
return true
|
||||
|
||||
let rooks = self.getBitboard(Rook, nonSideToMove) or queens
|
||||
|
||||
if (getRookMoves(square, occupancy) and rooks) != 0:
|
||||
return true
|
||||
|
||||
if self.getPawnAttacks(square, nonSideToMove) != 0:
|
||||
return true
|
||||
|
||||
|
||||
proc canCastle*(self: Position): tuple[queen, king: bool] =
|
||||
## Returns if the current side to move can castle
|
||||
if self.inCheck():
|
||||
return (false, false)
|
||||
let
|
||||
sideToMove = self.sideToMove
|
||||
occupancy = self.getOccupancy()
|
||||
result = self.castlingAvailability[sideToMove]
|
||||
if result.king:
|
||||
result.king = (kingSideCastleRay(sideToMove) and occupancy) == 0
|
||||
if result.queen:
|
||||
result.queen = (queenSideCastleRay(sideToMove) and occupancy) == 0
|
||||
if result.king:
|
||||
# There are no pieces in between our friendly king and
|
||||
# rook: check for attacks
|
||||
let
|
||||
king = self.getBitboard(King, sideToMove).toSquare()
|
||||
for square in getRayBetween(king, sideToMove.kingSideRook()):
|
||||
if self.isOccupancyAttacked(square, occupancy):
|
||||
result.king = false
|
||||
break
|
||||
|
||||
if result.queen:
|
||||
let
|
||||
king: Square = self.getBitboard(King, sideToMove).toSquare()
|
||||
# The king always moves two squares, but the queen side rook moves
|
||||
# 3 squares. We only need to check for attacks on the squares where
|
||||
# the king moves to and not any further. We subtract 3 instead of 2
|
||||
# because getRayBetween ignores the start and target squares in the
|
||||
# ray it returns so we have to extend it by one
|
||||
destination = makeSquare(rankFromSquare(king), fileFromSquare(king) - 3)
|
||||
for square in getRayBetween(king, destination):
|
||||
if self.isOccupancyAttacked(square, occupancy):
|
||||
result.queen = false
|
||||
break
|
||||
|
||||
|
||||
func countPieces*(self: Position, kind: PieceKind, color: PieceColor): int {.inline.} =
|
||||
## Returns the number of pieces with
|
||||
## the given color and type in the
|
||||
## position
|
||||
return self.pieces[color][kind].countSquares()
|
||||
|
||||
|
||||
func getPiece*(self: Position, square: Square): Piece {.inline.} =
|
||||
## Gets the piece at the given square in
|
||||
## the position
|
||||
return self.mailbox[square]
|
||||
|
||||
|
||||
func getPiece*(self: Position, square: string): Piece {.inline.} =
|
||||
## Gets the piece on the given square
|
||||
## in algebraic notation
|
||||
return self.getPiece(square.toSquare())
|
||||
|
||||
|
||||
proc removePieceFromBitboard*(self: var Position, square: Square) =
|
||||
## Removes a piece at the given square from
|
||||
## its respective bitboard
|
||||
let piece = self.getPiece(square)
|
||||
self.pieces[piece.color][piece.kind].clearBit(square)
|
||||
|
||||
|
||||
proc addPieceToBitboard*(self: var Position, square: Square, piece: Piece) =
|
||||
## Adds the given piece at the given square to
|
||||
## its respective bitboard
|
||||
self.pieces[piece.color][piece.kind].setBit(square)
|
||||
|
||||
|
||||
proc spawnPiece*(self: var Position, square: Square, piece: Piece) =
|
||||
## Spawns a new piece at the given square
|
||||
when not defined(danger):
|
||||
doAssert self.getPiece(square).kind == Empty
|
||||
self.addPieceToBitboard(square, piece)
|
||||
self.mailbox[square] = piece
|
||||
|
||||
|
||||
proc removePiece*(self: var Position, square: Square) =
|
||||
## Removes a piece from the board, updating necessary
|
||||
## metadata
|
||||
when not defined(danger):
|
||||
let piece = self.getPiece(square)
|
||||
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
||||
self.removePieceFromBitboard(square)
|
||||
self.mailbox[square] = nullPiece()
|
||||
|
||||
|
||||
proc movePiece*(self: var Position, move: Move) =
|
||||
## Internal helper to move a piece from
|
||||
## its current square to a target square
|
||||
let piece = self.getPiece(move.startSquare)
|
||||
when not defined(danger):
|
||||
let targetSquare = self.getPiece(move.targetSquare)
|
||||
if targetSquare.color != None:
|
||||
raise newException(AccessViolationDefect, &"{piece} at {move.startSquare} attempted to overwrite {targetSquare} at {move.targetSquare}: {move}")
|
||||
# Update positional metadata
|
||||
self.removePiece(move.startSquare)
|
||||
self.spawnPiece(move.targetSquare, piece)
|
||||
|
||||
|
||||
proc movePiece*(self: var Position, startSquare, targetSquare: Square) =
|
||||
## Moves a piece from the given start square to the given
|
||||
## target square
|
||||
self.movePiece(createMove(startSquare, targetSquare))
|
||||
|
||||
|
||||
func countPieces*(self: Position, piece: Piece): int {.inline.} =
|
||||
## Returns the number of pieces in the position that
|
||||
## are of the same type and color as the given piece
|
||||
return self.countPieces(piece.kind, piece.color)
|
||||
|
||||
|
||||
proc updateChecksAndPins*(self: var Position) =
|
||||
## 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.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
friendlyKing = self.getBitboard(King, sideToMove).toSquare()
|
||||
friendlyPieces = self.getOccupancyFor(sideToMove)
|
||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
||||
|
||||
# Update checks
|
||||
self.checkers = self.getAttacksTo(friendlyKing, nonSideToMove)
|
||||
# Update pins
|
||||
self.diagonalPins = Bitboard(0)
|
||||
self.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()
|
||||
# Is the pinning ray obstructed by any of our friendly pieces? If so, the
|
||||
# piece is pinned
|
||||
if (pinningRay and friendlyPieces).countSquares() == 1:
|
||||
self.diagonalPins = self.diagonalPins or pinningRay
|
||||
|
||||
for piece in canPinOrthogonally:
|
||||
let pinningRay = getRayBetween(friendlyKing, piece) or piece.toBitboard()
|
||||
if (pinningRay and friendlyPieces).countSquares() == 1:
|
||||
self.orthogonalPins = self.orthogonalPins or pinningRay
|
||||
|
||||
|
||||
proc hash*(self: var Position) =
|
||||
## Computes the zobrist hash of the position
|
||||
## This only needs to be called when a position
|
||||
## is loaded the first time, as all subsequent
|
||||
## hashes are updated incrementally at every
|
||||
## call to doMove()
|
||||
self.zobristKey = ZobristKey(0)
|
||||
|
||||
if self.sideToMove == Black:
|
||||
self.zobristKey = self.zobristKey xor getBlackToMoveKey()
|
||||
|
||||
for sq in self.getOccupancy():
|
||||
self.zobristKey = self.zobristKey xor self.getPiece(sq).getKey(sq)
|
||||
|
||||
if self.castlingAvailability[White].king:
|
||||
self.zobristKey = self.zobristKey xor getKingSideCastlingKey(White)
|
||||
if self.castlingAvailability[White].queen:
|
||||
self.zobristKey = self.zobristKey xor getQueenSideCastlingKey(White)
|
||||
if self.castlingAvailability[Black].king:
|
||||
self.zobristKey = self.zobristKey xor getKingSideCastlingKey(Black)
|
||||
if self.castlingAvailability[Black].queen:
|
||||
self.zobristKey = self.zobristKey xor getQueenSideCastlingKey(Black)
|
||||
|
||||
if self.enPassantSquare != nullSquare():
|
||||
self.zobristKey = self.zobristKey xor getEnPassantKey(fileFromSquare(self.enPassantSquare))
|
||||
|
||||
|
||||
proc loadFEN*(fen: string): Position =
|
||||
## Initializes a position from the given
|
||||
## FEN string
|
||||
result = Position(enPassantSquare: nullSquare())
|
||||
var
|
||||
# Current square in the grid
|
||||
row: int8 = 0
|
||||
column: int8 = 0
|
||||
# Current section in the FEN string
|
||||
section = 0
|
||||
# Current index into the FEN string
|
||||
index = 0
|
||||
# Temporary variable to store a piece
|
||||
piece: Piece
|
||||
|
||||
# Make sure the mailbox is actually empty
|
||||
for sq in Square(0)..Square(63):
|
||||
result.mailbox[sq] = nullPiece()
|
||||
|
||||
# See https://en.wikipedia.org/wiki/Forsyth%E2%80%93Edwards_Notation
|
||||
while index <= fen.high():
|
||||
var c = fen[index]
|
||||
if c == ' ':
|
||||
# Next section
|
||||
inc(section)
|
||||
inc(index)
|
||||
continue
|
||||
case section:
|
||||
of 0:
|
||||
# Piece placement data
|
||||
case c.toLowerAscii():
|
||||
# Piece
|
||||
of 'r', 'n', 'b', 'q', 'k', 'p':
|
||||
let square = makeSquare(row, column)
|
||||
piece = c.fromChar()
|
||||
result.pieces[piece.color][piece.kind].setBit(square)
|
||||
result.mailbox[square] = piece
|
||||
inc(column)
|
||||
of '/':
|
||||
# Next row
|
||||
inc(row)
|
||||
column = 0
|
||||
of '0'..'9':
|
||||
# Skip x columns
|
||||
let x = int(uint8(c) - uint8('0'))
|
||||
if x > 8:
|
||||
raise newException(ValueError, &"invalid FEN: invalid column skip size ({x} > 8)")
|
||||
column += int8(x)
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown piece identifier '{c}'")
|
||||
of 1:
|
||||
# Active color
|
||||
case c:
|
||||
of 'w':
|
||||
result.sideToMove = White
|
||||
of 'b':
|
||||
result.sideToMove = Black
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: invalid active color identifier '{c}'")
|
||||
of 2:
|
||||
# Castling availability
|
||||
case c:
|
||||
# TODO
|
||||
of '-':
|
||||
discard
|
||||
of 'K':
|
||||
result.castlingAvailability[White].king = true
|
||||
of 'Q':
|
||||
result.castlingAvailability[White].queen = true
|
||||
of 'k':
|
||||
result.castlingAvailability[Black].king = true
|
||||
of 'q':
|
||||
result.castlingAvailability[Black].queen = true
|
||||
else:
|
||||
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
|
||||
of 3:
|
||||
# En passant target square
|
||||
case c:
|
||||
of '-':
|
||||
# Field is already uninitialized to the correct state
|
||||
discard
|
||||
else:
|
||||
result.enPassantSquare = fen[index..index+1].toSquare()
|
||||
# Square metadata is 2 bytes long
|
||||
inc(index)
|
||||
of 4:
|
||||
# Halfmove clock
|
||||
var s = ""
|
||||
while not fen[index].isSpaceAscii():
|
||||
s.add(fen[index])
|
||||
inc(index)
|
||||
# Backtrack so the space is seen by the
|
||||
# next iteration of the loop
|
||||
dec(index)
|
||||
result.halfMoveClock = parseInt(s).uint16
|
||||
of 5:
|
||||
# Fullmove number
|
||||
var s = ""
|
||||
while index <= fen.high():
|
||||
s.add(fen[index])
|
||||
inc(index)
|
||||
result.fullMoveCount = parseInt(s).uint16
|
||||
else:
|
||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||
inc(index)
|
||||
result.updateChecksAndPins()
|
||||
result.hash()
|
||||
|
||||
|
||||
proc `$`*(self: Position): string =
|
||||
result &= "- - - - - - - -"
|
||||
var file = 8
|
||||
for i in 0..7:
|
||||
result &= "\n"
|
||||
for j in 0..7:
|
||||
let piece = self.mailbox[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
result &= "x "
|
||||
continue
|
||||
result &= &"{piece.toChar()} "
|
||||
result &= &"{file}"
|
||||
dec(file)
|
||||
result &= "\n- - - - - - - -"
|
||||
result &= "\na b c d e f g h"
|
||||
|
||||
|
||||
proc toFEN*(self: Position): string =
|
||||
## Returns a FEN string of the
|
||||
## position
|
||||
var skip: int
|
||||
# Piece placement data
|
||||
for i in 0..7:
|
||||
skip = 0
|
||||
for j in 0..7:
|
||||
let piece = self.mailbox[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
inc(skip)
|
||||
elif skip > 0:
|
||||
result &= &"{skip}{piece.toChar()}"
|
||||
skip = 0
|
||||
else:
|
||||
result &= piece.toChar()
|
||||
if skip > 0:
|
||||
result &= $skip
|
||||
if i < 7:
|
||||
result &= "/"
|
||||
result &= " "
|
||||
# Active color
|
||||
result &= (if self.sideToMove == White: "w" else: "b")
|
||||
result &= " "
|
||||
# Castling availability
|
||||
let castleWhite = self.castlingAvailability[White]
|
||||
let castleBlack = self.castlingAvailability[Black]
|
||||
if not (castleBlack.king or castleBlack.queen or castleWhite.king or castleWhite.queen):
|
||||
result &= "-"
|
||||
else:
|
||||
if castleWhite.king:
|
||||
result &= "K"
|
||||
if castleWhite.queen:
|
||||
result &= "Q"
|
||||
if castleBlack.king:
|
||||
result &= "k"
|
||||
if castleBlack.queen:
|
||||
result &= "q"
|
||||
result &= " "
|
||||
# En passant target
|
||||
if self.enPassantSquare == nullSquare():
|
||||
result &= "-"
|
||||
else:
|
||||
result &= self.enPassantSquare.toAlgebraic()
|
||||
result &= " "
|
||||
# Halfmove clock
|
||||
result &= $self.halfMoveClock
|
||||
result &= " "
|
||||
# Fullmove number
|
||||
result &= $self.fullMoveCount
|
||||
|
||||
|
||||
proc pretty*(self: Position): string =
|
||||
## Returns a colored version of the
|
||||
## position for easier visualization
|
||||
var file = 8
|
||||
for i in 0..7:
|
||||
if i > 0:
|
||||
result &= "\n"
|
||||
for j in 0..7:
|
||||
# Equivalent to (i + j) mod 2
|
||||
# (I'm just evil)
|
||||
if ((i + j) and 1) == 0:
|
||||
result &= "\x1b[39;44;1m"
|
||||
else:
|
||||
result &= "\x1b[39;40;1m"
|
||||
let piece = self.mailbox[makeSquare(i, j)]
|
||||
if piece.kind == Empty:
|
||||
result &= " \x1b[0m"
|
||||
else:
|
||||
result &= &"{piece.toPretty()} \x1b[0m"
|
||||
result &= &" \x1b[33;1m{file}\x1b[0m"
|
||||
dec(file)
|
||||
|
||||
result &= "\n\x1b[31;1ma b c d e f g h"
|
||||
result &= "\x1b[0m"
|
|
@ -45,9 +45,10 @@ type
|
|||
currentExtensionCount: uint8
|
||||
|
||||
|
||||
proc newSearchManager*(board: Chessboard, transpositions: TTable): SearchManager =
|
||||
proc newSearchManager*(position: Position, transpositions: TTable): SearchManager =
|
||||
new(result)
|
||||
result.board = board
|
||||
result.board = newChessboard()
|
||||
result.board.position = position
|
||||
result.bestMoveRoot = nullMove()
|
||||
result.transpositionTable = transpositions
|
||||
|
||||
|
@ -79,7 +80,7 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
|||
# second goal we want to use our least valuable pieces to do so (this
|
||||
# is why we multiply the score of the captured piece by 10, to give
|
||||
# it priority)
|
||||
result += 10 * self.board.getPieceScore(move.targetSquare) - self.board.getPieceScore(move.startSquare)
|
||||
result += 10 * self.board.position.getPieceScore(move.targetSquare) - self.board.position.getPieceScore(move.startSquare)
|
||||
if move.isPromotion():
|
||||
# Promotions are a good idea to search first
|
||||
var piece: Piece
|
||||
|
@ -94,12 +95,12 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
|||
piece = Piece(kind: Queen, color: sideToMove)
|
||||
else:
|
||||
discard # Unreachable
|
||||
result += self.board.getPieceScore(piece, move.targetSquare)
|
||||
if self.board.getPawnAttacks(move.targetSquare, nonSideToMove) != 0:
|
||||
result += self.board.position.getPieceScore(piece, move.targetSquare)
|
||||
if self.board.position.getPawnAttacks(move.targetSquare, nonSideToMove) != 0:
|
||||
# Moving on a square attacked by an enemy pawn is _usually_ a very bad
|
||||
# idea. Assume the piece is lost and give a malus based on the fact that
|
||||
# losing a piece this way is dumb
|
||||
result -= self.board.getPieceScore(move.startSquare) * 2
|
||||
result -= self.board.position.getPieceScore(move.startSquare) * 2
|
||||
|
||||
|
||||
proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
||||
|
@ -152,7 +153,7 @@ proc getSearchExtension(self: SearchManager, move: Move): int {.used.} =
|
|||
# if we can do other interesting things!
|
||||
inc(self.currentExtensionCount)
|
||||
return 1
|
||||
let piece = self.board.getPiece(move.targetSquare)
|
||||
let piece = self.board.position.getPiece(move.targetSquare)
|
||||
# If a pawn has just moved to its second-last rank, extend to
|
||||
# see if a promotion would yield some good position
|
||||
if piece.kind == Pawn:
|
||||
|
|
|
@ -56,7 +56,7 @@ proc perft*(board: var Chessboard, ply: int, verbose = false, divide = false, bu
|
|||
echo &"Ply (from root): {board.position.plyFromRoot}"
|
||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
||||
echo &"Turn: {board.position.sideToMove}"
|
||||
echo &"Piece: {board.getPiece(move.startSquare).kind}"
|
||||
echo &"Piece: {board.position.getPiece(move.startSquare).kind}"
|
||||
echo &"Flags: {move.getFlags()}"
|
||||
echo &"In check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
||||
echo &"Can castle:\n - King side: {(if canCastle.king: \"yes\" else: \"no\")}\n - Queen side: {(if canCastle.queen: \"yes\" else: \"no\")}"
|
||||
|
@ -137,10 +137,10 @@ proc handleMoveCommand(board: var Chessboard, command: seq[string]): Move {.disc
|
|||
# we have to figure out all the flags by ourselves (whether it's a double
|
||||
# push, a capture, a promotion, etc.)
|
||||
|
||||
if board.getPiece(targetSquare).kind != Empty:
|
||||
if board.position.getPiece(targetSquare).kind != Empty:
|
||||
flags.add(Capture)
|
||||
|
||||
if board.getPiece(startSquare).kind == Pawn and abs(rankFromSquare(startSquare) - rankFromSquare(targetSquare)) == 2:
|
||||
if board.position.getPiece(startSquare).kind == Pawn and abs(rankFromSquare(startSquare) - rankFromSquare(targetSquare)) == 2:
|
||||
flags.add(DoublePush)
|
||||
|
||||
if len(moveString) == 5:
|
||||
|
@ -160,12 +160,13 @@ proc handleMoveCommand(board: var Chessboard, command: seq[string]): Move {.disc
|
|||
|
||||
|
||||
var move = createMove(startSquare, targetSquare, flags)
|
||||
let piece = board.getPiece(move.startSquare)
|
||||
let piece = board.position.getPiece(move.startSquare)
|
||||
if piece.kind == King and move.startSquare == board.position.sideToMove.getKingStartingSquare():
|
||||
if move.targetSquare in [piece.kingSideCastling(), piece.queenSideCastling()]:
|
||||
move.flags = move.flags or Castle.uint16
|
||||
elif move.targetSquare == board.position.enPassantSquare:
|
||||
move.flags = move.flags or EnPassant.uint16
|
||||
elif piece.kind == Pawn and targetSquare == board.position.enPassantSquare:
|
||||
# I hate en passant I hate en passant I hate en passant I hate en passant I hate en passant I hate en passant
|
||||
flags.add(EnPassant)
|
||||
result = board.makeMove(move)
|
||||
if result == nullMove():
|
||||
echo &"Error: move: {moveString} is illegal"
|
||||
|
@ -343,7 +344,8 @@ 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 <square>: Print which pieces are currently attacking the given square
|
||||
- atk <square>: Print which opponent pieces are attacking the given square
|
||||
- def <square>: Print which friendly pieces are attacking the given square
|
||||
- pins: Print the current pin masks, if any
|
||||
- checks: Print the current check mask, if in check
|
||||
- skip: Make a null move (i.e. pass your turn). Useful for debugging. Very much illegal
|
||||
|
@ -381,7 +383,7 @@ proc commandLoop*: int =
|
|||
echo HELP_TEXT
|
||||
of "skip":
|
||||
board.position.sideToMove = board.position.sideToMove.opposite()
|
||||
board.updateChecksAndPins()
|
||||
board.position.updateChecksAndPins()
|
||||
of "go":
|
||||
handleGoCommand(board, cmd)
|
||||
of "position", "pos":
|
||||
|
@ -402,10 +404,19 @@ proc commandLoop*: int =
|
|||
echo "error: atk: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove.opposite())
|
||||
echo board.position.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove.opposite())
|
||||
except ValueError:
|
||||
echo "error: atk: invalid square"
|
||||
continue
|
||||
of "def":
|
||||
if len(cmd) != 2:
|
||||
echo "error: def: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.position.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove)
|
||||
except ValueError:
|
||||
echo "error: def: invalid square"
|
||||
continue
|
||||
of "ep":
|
||||
let target = board.position.enPassantSquare
|
||||
if target != nullSquare():
|
||||
|
@ -417,7 +428,7 @@ proc commandLoop*: int =
|
|||
echo "error: get: invalid number of arguments"
|
||||
continue
|
||||
try:
|
||||
echo board.getPiece(cmd[1])
|
||||
echo board.position.getPiece(cmd[1])
|
||||
except ValueError:
|
||||
echo "error: get: invalid square"
|
||||
continue
|
||||
|
|
|
@ -89,10 +89,10 @@ proc parseUCIMove(session: UCISession, move: string): tuple[move: Move, command:
|
|||
# Since the client tells us just the source and target square of the move,
|
||||
# we have to figure out all the flags by ourselves (whether it's a double
|
||||
# push, a capture, a promotion, etc.)
|
||||
if session.board.getPiece(targetSquare).kind != Empty:
|
||||
if session.board.position.getPiece(targetSquare).kind != Empty:
|
||||
flags.add(Capture)
|
||||
|
||||
if session.board.getPiece(startSquare).kind == Pawn and abs(rankFromSquare(startSquare) - rankFromSquare(targetSquare)) == 2:
|
||||
if session.board.position.getPiece(startSquare).kind == Pawn and abs(rankFromSquare(startSquare) - rankFromSquare(targetSquare)) == 2:
|
||||
flags.add(DoublePush)
|
||||
|
||||
if len(move) == 5:
|
||||
|
@ -108,7 +108,7 @@ proc parseUCIMove(session: UCISession, move: string): tuple[move: Move, command:
|
|||
flags.add(PromoteToRook)
|
||||
else:
|
||||
return
|
||||
let piece = session.board.getPiece(startSquare)
|
||||
let piece = session.board.position.getPiece(startSquare)
|
||||
if piece.kind == King and startSquare == session.board.position.sideToMove.getKingStartingSquare():
|
||||
if targetSquare in [piece.kingSideCastling(), piece.queenSideCastling()]:
|
||||
flags.add(Castle)
|
||||
|
@ -303,7 +303,7 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
|
|||
echo &"info string created {session.hashTableSize} MiB TT"
|
||||
session.transpositionTable = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
||||
var command = args.command
|
||||
var searcher = newSearchManager(session.board, session.transpositionTable)
|
||||
var searcher = newSearchManager(session.board.position, session.transpositionTable)
|
||||
session.currentSearch.store(searcher)
|
||||
var
|
||||
timeRemaining = (if session.board.position.sideToMove == White: command.wtime else: command.btime)
|
||||
|
|
Loading…
Reference in New Issue