Refactor chessboard code and move most logic to Position
This commit is contained in:
parent
4cb994a3a6
commit
fe7e64e9c3
|
@ -262,9 +262,9 @@ func shortKnightDownRightRelativeTo*(self: Bitboard, side: PieceColor): Bitboard
|
||||||
# We precompute as much stuff as possible: lookup tables are fast!
|
# 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
|
## 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()
|
let king = i.toBitboard()
|
||||||
# It doesn't really matter which side we generate
|
# It doesn't really matter which side we generate
|
||||||
# the move for, they're identical for both
|
# the move for, they're identical for both
|
||||||
|
@ -284,9 +284,9 @@ func computeKingBitboards: array[64, Bitboard] {.compileTime.} =
|
||||||
result[i] = movements
|
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
|
## Precomputes all the movement bitboards for knights
|
||||||
for i in 0'u64..63:
|
for i in Square(0)..Square(63):
|
||||||
let knight = i.toBitboard()
|
let knight = i.toBitboard()
|
||||||
# It doesn't really matter which side we generate
|
# It doesn't really matter which side we generate
|
||||||
# the move for, they're identical for both
|
# the move for, they're identical for both
|
||||||
|
@ -302,28 +302,19 @@ func computeKnightBitboards: array[64, Bitboard] {.compileTime.} =
|
||||||
result[i] = movements
|
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
|
## Precomputes all the attack bitboards for pawns
|
||||||
## of the given color
|
## of the given color
|
||||||
for i in 0'u64..63:
|
for i in Square(0)..Square(63):
|
||||||
let
|
let pawn = i.toBitboard()
|
||||||
pawn = i.toBitboard()
|
result[i] = pawn.backwardLeftRelativeTo(color) or pawn.backwardRightRelativeTo(color)
|
||||||
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
|
|
||||||
|
|
||||||
const
|
const
|
||||||
KING_BITBOARDS = computeKingBitboards()
|
KING_BITBOARDS = computeKingBitboards()
|
||||||
KNIGHT_BITBOARDS = computeKnightBitboards()
|
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 getKingAttacks*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square]
|
||||||
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square.int]
|
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square]
|
||||||
func getPawnAttacks*(color: PieceColor, square: Square): Bitboard {.inline.} = PAWN_ATTACKS[color.int][square.int]
|
func getPawnAttacks*(color: PieceColor, square: Square): Bitboard {.inline.} = PAWN_ATTACKS[color][square]
|
||||||
|
|
|
@ -13,8 +13,6 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
## Implementation of a simple chessboard
|
## Implementation of a simple chessboard
|
||||||
import std/strformat
|
|
||||||
import std/strutils
|
|
||||||
|
|
||||||
|
|
||||||
import pieces
|
import pieces
|
||||||
|
@ -41,10 +39,7 @@ type
|
||||||
positions*: seq[Position]
|
positions*: seq[Position]
|
||||||
|
|
||||||
|
|
||||||
# A bunch of simple utility functions and forward declarations
|
|
||||||
proc toFEN*(self: Chessboard): string
|
proc toFEN*(self: Chessboard): string
|
||||||
proc updateChecksAndPins*(self: var Chessboard)
|
|
||||||
proc hash*(self: var Chessboard)
|
|
||||||
|
|
||||||
|
|
||||||
proc newChessboard*: Chessboard =
|
proc newChessboard*: Chessboard =
|
||||||
|
@ -54,118 +49,11 @@ proc newChessboard*: Chessboard =
|
||||||
result.position.mailbox[i] = nullPiece()
|
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 =
|
proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||||
## Initializes a chessboard with the
|
## Initializes a chessboard with the
|
||||||
## position encoded by the given FEN string
|
## position encoded by the given FEN string
|
||||||
result = newChessboard()
|
result = newChessboard()
|
||||||
var
|
result.position = loadFEN(fen)
|
||||||
# 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()
|
|
||||||
|
|
||||||
|
|
||||||
proc newDefaultChessboard*: Chessboard {.inline.} =
|
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")
|
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.} =
|
func inCheck*(self: Chessboard): bool {.inline.} =
|
||||||
## Returns if the current side to move is in check
|
## 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
|
## Returns if the current side to move can castle
|
||||||
if self.inCheck():
|
return self.position.canCastle()
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
proc `$`*(self: Chessboard): string =
|
proc `$`*(self: Chessboard): string = $self.position
|
||||||
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 pretty*(self: Chessboard): string =
|
proc pretty*(self: Chessboard): string =
|
||||||
## Returns a colored version of the
|
## Returns a colored version of the
|
||||||
## board for easier visualization
|
## current position for easier visualization
|
||||||
var file = 8
|
return self.position.pretty()
|
||||||
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"
|
|
||||||
|
|
||||||
|
|
||||||
proc toFEN*(self: Chessboard): string =
|
proc toFEN*(self: Chessboard): string =
|
||||||
## Returns a FEN string of the current
|
## Returns a FEN string of the current
|
||||||
## position in the chessboard
|
## position in the chessboard
|
||||||
var skip: int
|
return self.position.toFEN()
|
||||||
# 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
|
|
||||||
|
|
||||||
|
|
||||||
proc drawByRepetition*(self: var Chessboard): bool =
|
proc drawByRepetition*(self: var Chessboard): bool =
|
||||||
|
@ -559,29 +101,3 @@ proc drawByRepetition*(self: var Chessboard): bool =
|
||||||
return true
|
return true
|
||||||
dec(i)
|
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
|
# Stolen from https://www.chessprogramming.org/PeSTO's_Evaluation_Function
|
||||||
const
|
const
|
||||||
PAWN_MIDDLEGAME_SCORES: array[64, Score] = [
|
PAWN_MIDDLEGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
98, 134, 61, 95, 68, 126, 34, -11,
|
98, 134, 61, 95, 68, 126, 34, -11,
|
||||||
-6, 7, 26, 31, 65, 56, 25, -20,
|
-6, 7, 26, 31, 65, 56, 25, -20,
|
||||||
|
@ -32,7 +32,7 @@ const
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
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,
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
178, 173, 158, 134, 147, 132, 165, 187,
|
178, 173, 158, 134, 147, 132, 165, 187,
|
||||||
94, 100, 85, 67, 56, 53, 82, 84,
|
94, 100, 85, 67, 56, 53, 82, 84,
|
||||||
|
@ -43,7 +43,7 @@ const
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
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,
|
-167, -89, -34, -49, 61, -97, -15, -107,
|
||||||
-73, -41, 72, 36, 23, 62, 7, -17,
|
-73, -41, 72, 36, 23, 62, 7, -17,
|
||||||
-47, 60, 37, 65, 84, 129, 73, 44,
|
-47, 60, 37, 65, 84, 129, 73, 44,
|
||||||
|
@ -54,7 +54,7 @@ const
|
||||||
-105, -21, -58, -33, -17, -28, -19, -23,
|
-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,
|
-58, -38, -13, -28, -31, -27, -63, -99,
|
||||||
-25, -8, -25, -2, -9, -25, -24, -52,
|
-25, -8, -25, -2, -9, -25, -24, -52,
|
||||||
-24, -20, 10, 9, -1, -9, -19, -41,
|
-24, -20, 10, 9, -1, -9, -19, -41,
|
||||||
|
@ -65,7 +65,7 @@ const
|
||||||
-29, -51, -23, -15, -22, -18, -50, -64,
|
-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,
|
-29, 4, -82, -37, -25, -42, 7, -8,
|
||||||
-26, 16, -18, -13, 30, 59, 18, -47,
|
-26, 16, -18, -13, 30, 59, 18, -47,
|
||||||
-16, 37, 43, 40, 35, 50, 37, -2,
|
-16, 37, 43, 40, 35, 50, 37, -2,
|
||||||
|
@ -76,7 +76,7 @@ const
|
||||||
-33, -3, -14, -21, -13, -12, -39, -21,
|
-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,
|
-14, -21, -11, -8, -7, -9, -17, -24,
|
||||||
-8, -4, 7, -12, -3, -13, -4, -14,
|
-8, -4, 7, -12, -3, -13, -4, -14,
|
||||||
2, -8, 0, -1, -2, 6, 0, 4,
|
2, -8, 0, -1, -2, 6, 0, 4,
|
||||||
|
@ -87,7 +87,7 @@ const
|
||||||
-23, -9, -23, -5, -9, -16, -5, -17,
|
-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,
|
32, 42, 32, 51, 63, 9, 31, 43,
|
||||||
27, 32, 58, 62, 80, 67, 26, 44,
|
27, 32, 58, 62, 80, 67, 26, 44,
|
||||||
-5, 19, 26, 36, 17, 45, 61, 16,
|
-5, 19, 26, 36, 17, 45, 61, 16,
|
||||||
|
@ -98,7 +98,7 @@ const
|
||||||
-19, -13, 1, 17, 16, 7, -37, -26,
|
-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,
|
13, 10, 18, 15, 12, 12, 8, 5,
|
||||||
11, 13, 13, 11, -3, 3, 8, 3,
|
11, 13, 13, 11, -3, 3, 8, 3,
|
||||||
7, 7, 7, 5, 4, -3, -5, -3,
|
7, 7, 7, 5, 4, -3, -5, -3,
|
||||||
|
@ -109,7 +109,7 @@ const
|
||||||
-9, 2, 3, -1, -5, -13, 4, -20,
|
-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,
|
-28, 0, 29, 12, 59, 44, 43, 45,
|
||||||
-24, -39, -5, 1, -16, 57, 28, 54,
|
-24, -39, -5, 1, -16, 57, 28, 54,
|
||||||
-13, -17, 7, 8, 29, 56, 47, 57,
|
-13, -17, 7, 8, 29, 56, 47, 57,
|
||||||
|
@ -120,7 +120,7 @@ const
|
||||||
-1, -18, -9, 10, -15, -25, -31, -50,
|
-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,
|
-9, 22, 22, 27, 27, 19, 10, 20,
|
||||||
-17, 20, 32, 41, 58, 25, 30, 0,
|
-17, 20, 32, 41, 58, 25, 30, 0,
|
||||||
-20, 6, 9, 49, 47, 35, 19, 9,
|
-20, 6, 9, 49, 47, 35, 19, 9,
|
||||||
|
@ -131,7 +131,7 @@ const
|
||||||
-33, -28, -22, -43, -5, -32, -20, -41,
|
-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,
|
-65, 23, 16, -15, -56, -34, 2, 13,
|
||||||
29, -1, -20, -7, -8, -4, -38, -29,
|
29, -1, -20, -7, -8, -4, -38, -29,
|
||||||
-9, 24, 2, -16, -20, 6, 22, -22,
|
-9, 24, 2, -16, -20, 6, 22, -22,
|
||||||
|
@ -142,7 +142,7 @@ const
|
||||||
-15, 36, 12, -54, 8, -28, 24, 14,
|
-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,
|
-74, -35, -18, -18, -11, 15, 4, -17,
|
||||||
-12, 17, 14, 17, 17, 38, 23, 11,
|
-12, 17, 14, 17, 17, 38, 23, 11,
|
||||||
10, 17, 23, 15, 20, 45, 44, 13,
|
10, 17, 23, 15, 20, 45, 44, 13,
|
||||||
|
@ -154,10 +154,10 @@ const
|
||||||
]
|
]
|
||||||
|
|
||||||
# Bishop, King, Knight, Pawn, Queen, Rook
|
# Bishop, King, Knight, Pawn, Queen, Rook
|
||||||
MIDDLEGAME_WEIGHTS: array[6, Score] = [365, 0, 337, 82, 1025, 477]
|
MIDDLEGAME_WEIGHTS: array[PieceKind.Bishop..PieceKind.Rook, Score] = [365, 0, 337, 82, 1025, 477]
|
||||||
ENDGAME_WEIGHTS: array[6, Score] = [297, 0, 281, 94, 936, 512]
|
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,
|
BISHOP_MIDDLEGAME_SCORES,
|
||||||
KING_MIDDLEGAME_SCORES,
|
KING_MIDDLEGAME_SCORES,
|
||||||
KNIGHT_MIDDLEGAME_SCORES,
|
KNIGHT_MIDDLEGAME_SCORES,
|
||||||
|
@ -166,7 +166,7 @@ const
|
||||||
ROOK_MIDDLEGAME_SCORES
|
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,
|
BISHOP_ENDGAME_SCORES,
|
||||||
KING_ENDGAME_SCORES,
|
KING_ENDGAME_SCORES,
|
||||||
KNIGHT_ENDGAME_SCORES,
|
KNIGHT_ENDGAME_SCORES,
|
||||||
|
@ -180,18 +180,18 @@ const
|
||||||
|
|
||||||
|
|
||||||
var
|
var
|
||||||
MIDDLEGAME_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[2, array[6, array[64, Score]]]
|
ENDGAME_VALUE_TABLES: array[PieceColor.White..PieceColor.Black, array[PieceKind.Bishop..PieceKind.Rook, array[Square(0)..Square(63), Score]]]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc initializeTables =
|
proc initializeTables =
|
||||||
for kind in [Bishop, King, Knight, Pawn, Queen, Rook]:
|
for kind in [Bishop, King, Knight, Pawn, Queen, Rook]:
|
||||||
for sq in 0..63:
|
for sq in Square(0)..Square(63):
|
||||||
MIDDLEGAME_VALUE_TABLES[White.int][kind.int][sq] = MIDDLEGAME_WEIGHTS[kind.int] + MIDDLEGAME_PSQ_TABLES[kind.int][sq]
|
MIDDLEGAME_VALUE_TABLES[White][kind][sq] = MIDDLEGAME_WEIGHTS[kind] + MIDDLEGAME_PSQ_TABLES[kind][sq]
|
||||||
ENDGAME_VALUE_TABLES[White.int][kind.int][sq] = ENDGAME_WEIGHTS[kind.int] + ENDGAME_PSQ_TABLES[kind.int][sq]
|
ENDGAME_VALUE_TABLES[White][kind][sq] = ENDGAME_WEIGHTS[kind] + ENDGAME_PSQ_TABLES[kind][sq]
|
||||||
MIDDLEGAME_VALUE_TABLES[Black.int][kind.int][sq] = MIDDLEGAME_WEIGHTS[kind.int] + MIDDLEGAME_PSQ_TABLES[kind.int][sq xor 56]
|
MIDDLEGAME_VALUE_TABLES[Black][kind][sq] = MIDDLEGAME_WEIGHTS[kind] + MIDDLEGAME_PSQ_TABLES[kind][sq xor 56]
|
||||||
ENDGAME_VALUE_TABLES[Black.int][kind.int][sq] = ENDGAME_WEIGHTS[kind.int] + ENDGAME_PSQ_TABLES[kind.int][sq xor 56]
|
ENDGAME_VALUE_TABLES[Black][kind][sq] = ENDGAME_WEIGHTS[kind] + ENDGAME_PSQ_TABLES[kind][sq xor 56]
|
||||||
|
|
||||||
|
|
||||||
initializeTables()
|
initializeTables()
|
||||||
|
@ -202,12 +202,12 @@ func highestEval*: Score {.inline.} = Score(25_000)
|
||||||
func mateScore*: Score {.inline.} = lowestEval() + 1
|
func mateScore*: Score {.inline.} = lowestEval() + 1
|
||||||
|
|
||||||
|
|
||||||
func getGamePhase(board: Chessboard): int {.inline.} =
|
func getGamePhase(position: Position): int {.inline.} =
|
||||||
## Computes the game phase according to
|
## Computes the game phase according to
|
||||||
## how many pieces are left on the board
|
## how many pieces are left on the board
|
||||||
result = 0
|
result = 0
|
||||||
for sq in board.getOccupancy():
|
for sq in position.getOccupancy():
|
||||||
case board.getPiece(sq).kind:
|
case position.getPiece(sq).kind:
|
||||||
of Bishop, Knight:
|
of Bishop, Knight:
|
||||||
inc(result)
|
inc(result)
|
||||||
of Queen:
|
of Queen:
|
||||||
|
@ -221,26 +221,26 @@ func getGamePhase(board: Chessboard): int {.inline.} =
|
||||||
result = min(24, result)
|
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
|
## Returns the value of the piece located at
|
||||||
## the given square
|
## the given square
|
||||||
let
|
let
|
||||||
piece = board.getPiece(square)
|
piece = position.getPiece(square)
|
||||||
middleGameScore = MIDDLEGAME_VALUE_TABLES[piece.color.int][piece.kind.int][square.int]
|
middleGameScore = MIDDLEGAME_VALUE_TABLES[piece.color][piece.kind][square]
|
||||||
endGameScore = ENDGAME_VALUE_TABLES[piece.color.int][piece.kind.int][square.int]
|
endGameScore = ENDGAME_VALUE_TABLES[piece.color][piece.kind][square]
|
||||||
middleGamePhase = board.getGamePhase()
|
middleGamePhase = position.getGamePhase()
|
||||||
endGamePhase = 24 - middleGamePhase
|
endGamePhase = 24 - middleGamePhase
|
||||||
|
|
||||||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
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
|
## Returns the value the given piece would have if it
|
||||||
## were at the given square
|
## were at the given square
|
||||||
let
|
let
|
||||||
middleGameScore = MIDDLEGAME_VALUE_TABLES[piece.color.int][piece.kind.int][square.int]
|
middleGameScore = MIDDLEGAME_VALUE_TABLES[piece.color][piece.kind][square]
|
||||||
endGameScore = ENDGAME_VALUE_TABLES[piece.color.int][piece.kind.int][square.int]
|
endGameScore = ENDGAME_VALUE_TABLES[piece.color][piece.kind][square]
|
||||||
middleGamePhase = board.getGamePhase()
|
middleGamePhase = position.getGamePhase()
|
||||||
endGamePhase = 24 - middleGamePhase
|
endGamePhase = 24 - middleGamePhase
|
||||||
|
|
||||||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
||||||
|
@ -250,33 +250,33 @@ proc evaluateMaterial(board: ChessBoard): Score =
|
||||||
## Returns a material and position evaluation
|
## Returns a material and position evaluation
|
||||||
## for the current side to move
|
## for the current side to move
|
||||||
let
|
let
|
||||||
middleGamePhase = board.getGamePhase()
|
middleGamePhase = board.position.getGamePhase()
|
||||||
endGamePhase = 24 - middleGamePhase
|
endGamePhase = 24 - middleGamePhase
|
||||||
var
|
var
|
||||||
# White, Black
|
# White, Black
|
||||||
middleGameScores: array[2, Score] = [0, 0]
|
middleGameScores: array[PieceColor.White..PieceColor.Black, Score] = [0, 0]
|
||||||
endGameScores: array[2, Score] = [0, 0]
|
endGameScores: array[PieceColor.White..PieceColor.Black, Score] = [0, 0]
|
||||||
|
|
||||||
for sq in board.getOccupancy():
|
for sq in board.position.getOccupancy():
|
||||||
let piece = board.getPiece(sq)
|
let piece = board.position.getPiece(sq)
|
||||||
middleGameScores[piece.color.int] += MIDDLEGAME_VALUE_TABLES[piece.color.int][piece.kind.int][sq.int]
|
middleGameScores[piece.color] += MIDDLEGAME_VALUE_TABLES[piece.color][piece.kind][sq]
|
||||||
endGameScores[piece.color.int] += ENDGAME_VALUE_TABLES[piece.color.int][piece.kind.int][sq.int]
|
endGameScores[piece.color] += ENDGAME_VALUE_TABLES[piece.color][piece.kind][sq]
|
||||||
|
|
||||||
let
|
let
|
||||||
sideToMove = board.position.sideToMove
|
sideToMove = board.position.sideToMove
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
middleGameScore = middleGameScores[sideToMove.int] - middleGameScores[nonSideToMove.int]
|
middleGameScore = middleGameScores[sideToMove] - middleGameScores[nonSideToMove]
|
||||||
endGameScore = endGameScores[sideToMove.int] - endGameScores[nonSideToMove.int]
|
endGameScore = endGameScores[sideToMove] - endGameScores[nonSideToMove]
|
||||||
|
|
||||||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
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
|
## Evaluates the pawn structure of the current
|
||||||
## position for the side to move
|
## position for the side to move
|
||||||
let
|
let
|
||||||
sideToMove = board.position.sideToMove
|
sideToMove = position.sideToMove
|
||||||
friendlyPawns = board.getOccupancyFor(sideToMove)
|
friendlyPawns = position.getOccupancyFor(sideToMove)
|
||||||
|
|
||||||
# Doubled pawns are a bad idea
|
# Doubled pawns are a bad idea
|
||||||
var doubledPawns = 0
|
var doubledPawns = 0
|
||||||
|
@ -299,9 +299,9 @@ proc evaluatePawnStructure(board: Chessboard): Score {.used.} =
|
||||||
# stronger
|
# stronger
|
||||||
var
|
var
|
||||||
strongPawnIncrement = Score(0)
|
strongPawnIncrement = Score(0)
|
||||||
for pawn in board.getBitboard(Pawn, White):
|
for pawn in position.getBitboard(Pawn, White):
|
||||||
if board.getPawnAttacks(pawn, White) != 0:
|
if position.getPawnAttacks(pawn, White) != 0:
|
||||||
strongPawnIncrement += board.getPieceScore(pawn) div Score(4)
|
strongPawnIncrement += position.getPieceScore(pawn) div Score(4)
|
||||||
|
|
||||||
return DOUBLED_PAWNS_MALUS[doubledPawns] + ISOLATED_PAWN_MALUS[isolatedPawns] + strongPawnIncrement
|
return DOUBLED_PAWNS_MALUS[doubledPawns] + ISOLATED_PAWN_MALUS[isolatedPawns] + strongPawnIncrement
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,10 @@ proc generatePawnMoves(self: var Chessboard, moves: var MoveList, destinationMas
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
pawns = self.getBitboard(Pawn, sideToMove)
|
pawns = self.position.getBitboard(Pawn, sideToMove)
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.position.getOccupancy()
|
||||||
# We can only capture enemy pieces (except the king)
|
# We can only capture enemy pieces (except the king)
|
||||||
enemyPieces = self.getOccupancyFor(nonSideToMove)
|
enemyPieces = self.position.getOccupancyFor(nonSideToMove)
|
||||||
epTarget = self.position.enPassantSquare
|
epTarget = self.position.enPassantSquare
|
||||||
diagonalPins = self.position.diagonalPins
|
diagonalPins = self.position.diagonalPins
|
||||||
orthogonalPins = self.position.orthogonalPins
|
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
|
# TODO: Give names to ranks and files so we don't have to assume a
|
||||||
# specific board layout when calling get(Rank|File)Mask
|
# specific board layout when calling get(Rank|File)Mask
|
||||||
startingRank = if sideToMove == White: getRankMask(6) else: getRankMask(1)
|
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
|
# 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
|
# 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)
|
# (see pos fen rnbqkbnr/pppp1ppp/8/2P5/K7/8/PPPP1PPP/RNBQ1BNR b kq - 0 1 moves b7b5 c5b6)
|
||||||
let epPawnSquare = epPawn.toSquare()
|
let epPawnSquare = epPawn.toSquare()
|
||||||
let epPiece = self.getPiece(epPawnSquare)
|
let epPiece = self.position.getPiece(epPawnSquare)
|
||||||
self.removePiece(epPawnSquare)
|
self.position.removePiece(epPawnSquare)
|
||||||
if not self.isOccupancyAttacked(friendlyKing, newOccupancy):
|
if not self.position.isOccupancyAttacked(friendlyKing, newOccupancy):
|
||||||
# En passant does not create a check on the king: all good
|
# En passant does not create a check on the king: all good
|
||||||
moves.add(createMove(friendlyPawn, epBitboard, EnPassant))
|
moves.add(createMove(friendlyPawn, epBitboard, EnPassant))
|
||||||
self.spawnPiece(epPawnSquare, epPiece)
|
self.position.spawnPiece(epPawnSquare, epPiece)
|
||||||
if epRight != 0:
|
if epRight != 0:
|
||||||
# Note that this isn't going to be the same pawn from the previous if block!
|
# Note that this isn't going to be the same pawn from the previous if block!
|
||||||
let
|
let
|
||||||
friendlyPawn = epBitboard.backwardLeftRelativeTo(sideToMove)
|
friendlyPawn = epBitboard.backwardLeftRelativeTo(sideToMove)
|
||||||
newOccupancy = occupancy and not epPawn and not friendlyPawn or epBitboard
|
newOccupancy = occupancy and not epPawn and not friendlyPawn or epBitboard
|
||||||
let epPawnSquare = epPawn.toSquare()
|
let epPawnSquare = epPawn.toSquare()
|
||||||
let epPiece = self.getPiece(epPawnSquare)
|
let epPiece = self.position.getPiece(epPawnSquare)
|
||||||
self.removePiece(epPawnSquare)
|
self.position.removePiece(epPawnSquare)
|
||||||
if not self.isOccupancyAttacked(friendlyKing, newOccupancy):
|
if not self.position.isOccupancyAttacked(friendlyKing, newOccupancy):
|
||||||
# En passant does not create a check on the king: all good
|
# En passant does not create a check on the king: all good
|
||||||
moves.add(createMove(friendlyPawn, epBitboard, EnPassant))
|
moves.add(createMove(friendlyPawn, epBitboard, EnPassant))
|
||||||
self.spawnPiece(epPawnSquare, epPiece)
|
self.position.spawnPiece(epPawnSquare, epPiece)
|
||||||
|
|
||||||
|
|
||||||
proc generateRookMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
proc generateRookMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.position.getOccupancy()
|
||||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, sideToMove.opposite())
|
enemyPieces = self.position.getOccupancyFor(sideToMove.opposite()) and not self.position.getBitboard(King, sideToMove.opposite())
|
||||||
rooks = self.getBitboard(Rook, sideToMove)
|
rooks = self.position.getBitboard(Rook, sideToMove)
|
||||||
queens = self.getBitboard(Queen, sideToMove)
|
queens = self.position.getBitboard(Queen, sideToMove)
|
||||||
movableRooks = not self.position.diagonalPins and (queens or rooks)
|
movableRooks = not self.position.diagonalPins and (queens or rooks)
|
||||||
pinMask = self.position.orthogonalPins
|
pinMask = self.position.orthogonalPins
|
||||||
pinnedRooks = movableRooks and pinMask
|
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) =
|
proc generateBishopMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.position.getOccupancy()
|
||||||
enemyPieces = self.getOccupancyFor(sideToMove.opposite()) and not self.getBitboard(King, sideToMove.opposite())
|
enemyPieces = self.position.getOccupancyFor(sideToMove.opposite()) and not self.position.getBitboard(King, sideToMove.opposite())
|
||||||
bishops = self.getBitboard(Bishop, sideToMove)
|
bishops = self.position.getBitboard(Bishop, sideToMove)
|
||||||
queens = self.getBitboard(Queen, sideToMove)
|
queens = self.position.getBitboard(Queen, sideToMove)
|
||||||
movableBishops = not self.position.orthogonalPins and (queens or bishops)
|
movableBishops = not self.position.orthogonalPins and (queens or bishops)
|
||||||
pinMask = self.position.diagonalPins
|
pinMask = self.position.diagonalPins
|
||||||
pinnedBishops = movableBishops and pinMask
|
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) =
|
proc generateKingMoves(self: Chessboard, moves: var MoveList, capturesOnly=false) =
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
king = self.getBitboard(King, sideToMove)
|
king = self.position.getBitboard(King, sideToMove)
|
||||||
occupancy = self.getOccupancy()
|
occupancy = self.position.getOccupancy()
|
||||||
nonSideToMove = sideToMove.opposite()
|
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())
|
bitboard = getKingAttacks(king.toSquare())
|
||||||
noKingOccupancy = occupancy and not king
|
noKingOccupancy = occupancy and not king
|
||||||
if not capturesOnly:
|
if not capturesOnly:
|
||||||
for square in bitboard and not occupancy:
|
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))
|
moves.add(createMove(king, square))
|
||||||
for square in bitboard and enemyPieces:
|
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))
|
moves.add(createMove(king, square, Capture))
|
||||||
|
|
||||||
|
|
||||||
proc generateKnightMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
proc generateKnightMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
knights = self.getBitboard(Knight, sideToMove)
|
knights = self.position.getBitboard(Knight, sideToMove)
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
pinned = self.position.diagonalPins or self.position.orthogonalPins
|
pinned = self.position.diagonalPins or self.position.orthogonalPins
|
||||||
unpinnedKnights = knights and not pinned
|
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:
|
for square in unpinnedKnights:
|
||||||
let bitboard = getKnightAttacks(square)
|
let bitboard = getKnightAttacks(square)
|
||||||
for target in bitboard and destinationMask and not enemyPieces:
|
for target in bitboard and destinationMask and not enemyPieces:
|
||||||
|
@ -264,8 +264,8 @@ proc generateCastling(self: Chessboard, moves: var MoveList) =
|
||||||
let
|
let
|
||||||
sideToMove = self.position.sideToMove
|
sideToMove = self.position.sideToMove
|
||||||
castlingRights = self.canCastle()
|
castlingRights = self.canCastle()
|
||||||
kingSquare = self.getBitboard(King, sideToMove).toSquare()
|
kingSquare = self.position.getBitboard(King, sideToMove).toSquare()
|
||||||
kingPiece = self.getPiece(kingSquare)
|
kingPiece = self.position.getPiece(kingSquare)
|
||||||
if castlingRights.king:
|
if castlingRights.king:
|
||||||
moves.add(createMove(kingSquare, kingPiece.kingSideCastling(), Castle))
|
moves.add(createMove(kingSquare, kingPiece.kingSideCastling(), Castle))
|
||||||
if castlingRights.queen:
|
if castlingRights.queen:
|
||||||
|
@ -296,7 +296,7 @@ proc generateMoves*(self: var Chessboard, moves: var MoveList, capturesOnly: boo
|
||||||
var destinationMask: Bitboard
|
var destinationMask: Bitboard
|
||||||
if not self.inCheck():
|
if not self.inCheck():
|
||||||
# Not in check: cannot move over friendly pieces
|
# Not in check: cannot move over friendly pieces
|
||||||
destinationMask = not self.getOccupancyFor(sideToMove)
|
destinationMask = not self.position.getOccupancyFor(sideToMove)
|
||||||
else:
|
else:
|
||||||
# We *are* in check (from a single piece, because the two checks
|
# 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
|
# 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()
|
checker = self.position.checkers.lowestSquare()
|
||||||
checkerBB = checker.toBitboard()
|
checkerBB = checker.toBitboard()
|
||||||
# epTarget = self.position.enPassantSquare
|
# epTarget = self.position.enPassantSquare
|
||||||
# checkerPiece = self.getPiece(checker)
|
# checkerPiece = self.position.getPiece(checker)
|
||||||
destinationMask = getRayBetween(checker, self.getBitboard(King, sideToMove).toSquare()) or checkerBB
|
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
|
# TODO: This doesn't really work. I've addressed the issue for now, but it's kinda ugly. Find a better
|
||||||
# solution
|
# solution
|
||||||
# if checkerPiece.kind == Pawn and checkerBB.backwardRelativeTo(checkerPiece.color).toSquare() == epTarget:
|
# 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:
|
if capturesOnly:
|
||||||
# Note: This does not cover en passant (which is good because it's a capture,
|
# Note: This does not cover en passant (which is good because it's a capture,
|
||||||
# but the "fix" stands on flimsy ground)
|
# 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.generatePawnMoves(moves, destinationMask)
|
||||||
self.generateKnightMoves(moves, destinationMask)
|
self.generateKnightMoves(moves, destinationMask)
|
||||||
self.generateRookMoves(moves, destinationMask)
|
self.generateRookMoves(moves, destinationMask)
|
||||||
|
@ -340,7 +340,7 @@ proc doMove*(self: var Chessboard, move: Move) =
|
||||||
self.positions.add(self.position)
|
self.positions.add(self.position)
|
||||||
|
|
||||||
# Final checks
|
# Final checks
|
||||||
let piece = self.getPiece(move.startSquare)
|
let piece = self.position.getPiece(move.startSquare)
|
||||||
when not defined(danger):
|
when not defined(danger):
|
||||||
doAssert piece.kind != Empty and piece.color != None, &"{move} {self.toFEN()}"
|
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():
|
if move.isEnPassant():
|
||||||
# Make the en passant pawn disappear
|
# Make the en passant pawn disappear
|
||||||
let epPawnSquare = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
|
let epPawnSquare = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
|
||||||
self.position.zobristKey = self.position.zobristKey xor self.getPiece(epPawnSquare).getKey(epPawnSquare)
|
self.position.zobristKey = self.position.zobristKey xor self.position.getPiece(epPawnSquare).getKey(epPawnSquare)
|
||||||
self.removePiece(epPawnSquare)
|
self.position.removePiece(epPawnSquare)
|
||||||
|
|
||||||
if move.isCastling() or piece.kind == King:
|
if move.isCastling() or piece.kind == King:
|
||||||
# If the king has moved, all castling rights for the side to
|
# 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():
|
if move.targetSquare == piece.kingSideCastling():
|
||||||
source = piece.color.kingSideRook()
|
source = piece.color.kingSideRook()
|
||||||
rook = self.getPiece(source)
|
rook = self.position.getPiece(source)
|
||||||
target = rook.kingSideCastling()
|
target = rook.kingSideCastling()
|
||||||
|
|
||||||
elif move.targetSquare == piece.queenSideCastling():
|
elif move.targetSquare == piece.queenSideCastling():
|
||||||
source = piece.color.queenSideRook()
|
source = piece.color.queenSideRook()
|
||||||
rook = self.getPiece(source)
|
rook = self.position.getPiece(source)
|
||||||
target = rook.queenSideCastling()
|
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(source)
|
||||||
self.position.zobristKey = self.position.zobristKey xor piece.getKey(target)
|
self.position.zobristKey = self.position.zobristKey xor piece.getKey(target)
|
||||||
|
|
||||||
|
@ -421,9 +421,9 @@ proc doMove*(self: var Chessboard, move: Move) =
|
||||||
|
|
||||||
if move.isCapture():
|
if move.isCapture():
|
||||||
# Get rid of captured pieces
|
# 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.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 a rook has been captured, castling on that side is prohibited
|
||||||
if captured.kind == Rook:
|
if captured.kind == Rook:
|
||||||
if move.targetSquare == captured.color.kingSideRook():
|
if move.targetSquare == captured.color.kingSideRook():
|
||||||
|
@ -432,13 +432,13 @@ proc doMove*(self: var Chessboard, move: Move) =
|
||||||
self.position.castlingAvailability[captured.color].queen = false
|
self.position.castlingAvailability[captured.color].queen = false
|
||||||
|
|
||||||
# Move the piece to its target square
|
# 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.startSquare)
|
||||||
self.position.zobristKey = self.position.zobristKey xor piece.getKey(move.targetSquare)
|
self.position.zobristKey = self.position.zobristKey xor piece.getKey(move.targetSquare)
|
||||||
if move.isPromotion():
|
if move.isPromotion():
|
||||||
# Move is a pawn promotion: get rid of the pawn
|
# Move is a pawn promotion: get rid of the pawn
|
||||||
# and spawn a new piece
|
# 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)
|
self.position.zobristKey = self.position.zobristKey xor piece.getKey(move.targetSquare)
|
||||||
var spawnedPiece: Piece
|
var spawnedPiece: Piece
|
||||||
case move.getPromotionType():
|
case move.getPromotionType():
|
||||||
|
@ -454,9 +454,9 @@ proc doMove*(self: var Chessboard, move: Move) =
|
||||||
# Unreachable
|
# Unreachable
|
||||||
discard
|
discard
|
||||||
self.position.zobristKey = self.position.zobristKey xor spawnedPiece.getKey(move.targetSquare)
|
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
|
# Updates checks and pins for the (new) side to move
|
||||||
self.updateChecksAndPins()
|
self.position.updateChecksAndPins()
|
||||||
# Last updates to zobrist key
|
# Last updates to zobrist key
|
||||||
if self.position.castlingAvailability[piece.color].king:
|
if self.position.castlingAvailability[piece.color].king:
|
||||||
self.position.zobristKey = self.position.zobristKey xor getKingSideCastlingKey(piece.color)
|
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) =
|
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"
|
doAssert pieces == count, &"expected {count} pieces of kind {kind} and color {color}, got {pieces} instead"
|
||||||
|
|
||||||
|
|
||||||
|
@ -553,45 +553,45 @@ proc basicTests* =
|
||||||
|
|
||||||
# Pawns
|
# Pawns
|
||||||
for loc in ["a2", "b2", "c2", "d2", "e2", "f2", "g2", "h2"]:
|
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"]:
|
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
|
# Rooks
|
||||||
testPiece(board.getPiece("a1"), Rook, White)
|
testPiece(board.position.getPiece("a1"), Rook, White)
|
||||||
testPiece(board.getPiece("h1"), Rook, White)
|
testPiece(board.position.getPiece("h1"), Rook, White)
|
||||||
testPiece(board.getPiece("a8"), Rook, Black)
|
testPiece(board.position.getPiece("a8"), Rook, Black)
|
||||||
testPiece(board.getPiece("h8"), Rook, Black)
|
testPiece(board.position.getPiece("h8"), Rook, Black)
|
||||||
# Knights
|
# Knights
|
||||||
testPiece(board.getPiece("b1"), Knight, White)
|
testPiece(board.position.getPiece("b1"), Knight, White)
|
||||||
testPiece(board.getPiece("g1"), Knight, White)
|
testPiece(board.position.getPiece("g1"), Knight, White)
|
||||||
testPiece(board.getPiece("b8"), Knight, Black)
|
testPiece(board.position.getPiece("b8"), Knight, Black)
|
||||||
testPiece(board.getPiece("g8"), Knight, Black)
|
testPiece(board.position.getPiece("g8"), Knight, Black)
|
||||||
# Bishops
|
# Bishops
|
||||||
testPiece(board.getPiece("c1"), Bishop, White)
|
testPiece(board.position.getPiece("c1"), Bishop, White)
|
||||||
testPiece(board.getPiece("f1"), Bishop, White)
|
testPiece(board.position.getPiece("f1"), Bishop, White)
|
||||||
testPiece(board.getPiece("c8"), Bishop, Black)
|
testPiece(board.position.getPiece("c8"), Bishop, Black)
|
||||||
testPiece(board.getPiece("f8"), Bishop, Black)
|
testPiece(board.position.getPiece("f8"), Bishop, Black)
|
||||||
# Kings
|
# Kings
|
||||||
testPiece(board.getPiece("e1"), King, White)
|
testPiece(board.position.getPiece("e1"), King, White)
|
||||||
testPiece(board.getPiece("e8"), King, Black)
|
testPiece(board.position.getPiece("e8"), King, Black)
|
||||||
# Queens
|
# Queens
|
||||||
testPiece(board.getPiece("d1"), Queen, White)
|
testPiece(board.position.getPiece("d1"), Queen, White)
|
||||||
testPiece(board.getPiece("d8"), Queen, Black)
|
testPiece(board.position.getPiece("d8"), Queen, Black)
|
||||||
|
|
||||||
# Ensure our bitboards match with the board
|
# Ensure our bitboards match with the board
|
||||||
let
|
let
|
||||||
whitePawns = board.getBitboard(Pawn, White)
|
whitePawns = board.position.getBitboard(Pawn, White)
|
||||||
whiteKnights = board.getBitboard(Knight, White)
|
whiteKnights = board.position.getBitboard(Knight, White)
|
||||||
whiteBishops = board.getBitboard(Bishop, White)
|
whiteBishops = board.position.getBitboard(Bishop, White)
|
||||||
whiteRooks = board.getBitboard(Rook, White)
|
whiteRooks = board.position.getBitboard(Rook, White)
|
||||||
whiteQueens = board.getBitboard(Queen, White)
|
whiteQueens = board.position.getBitboard(Queen, White)
|
||||||
whiteKing = board.getBitboard(King, White)
|
whiteKing = board.position.getBitboard(King, White)
|
||||||
blackPawns = board.getBitboard(Pawn, Black)
|
blackPawns = board.position.getBitboard(Pawn, Black)
|
||||||
blackKnights = board.getBitboard(Knight, Black)
|
blackKnights = board.position.getBitboard(Knight, Black)
|
||||||
blackBishops = board.getBitboard(Bishop, Black)
|
blackBishops = board.position.getBitboard(Bishop, Black)
|
||||||
blackRooks = board.getBitboard(Rook, Black)
|
blackRooks = board.position.getBitboard(Rook, Black)
|
||||||
blackQueens = board.getBitboard(Queen, Black)
|
blackQueens = board.position.getBitboard(Queen, Black)
|
||||||
blackKing = board.getBitboard(King, 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)]
|
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)]
|
whiteKnightSquares = @[makeSquare(7'i8, 1'i8), makeSquare(7, 6)]
|
||||||
whiteBishopSquares = @[makeSquare(7'i8, 2'i8), makeSquare(7, 5)]
|
whiteBishopSquares = @[makeSquare(7'i8, 2'i8), makeSquare(7, 5)]
|
||||||
|
|
|
@ -183,7 +183,6 @@ func `$`*(self: Move): string =
|
||||||
result &= ")"
|
result &= ")"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func toAlgebraic*(self: Move): string =
|
func toAlgebraic*(self: Move): string =
|
||||||
if self == nullMove():
|
if self == nullMove():
|
||||||
return "null"
|
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
|
func isLightSquare*(a: Square): bool {.inline.} = (a.int8 and 2) == 0
|
||||||
|
|
||||||
# Overridden operators for our distinct type
|
# 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, 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
|
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.
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
import std/strformat
|
||||||
|
import std/strutils
|
||||||
|
|
||||||
|
|
||||||
import bitboards
|
import bitboards
|
||||||
import magics
|
import magics
|
||||||
import pieces
|
import pieces
|
||||||
import zobrist
|
import zobrist
|
||||||
|
import moves
|
||||||
|
import rays
|
||||||
|
|
||||||
|
export bitboards, magics, pieces, zobrist, moves, rays
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -57,6 +65,11 @@ type
|
||||||
mailbox*: array[Square(0)..Square(63), Piece]
|
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.} =
|
func getKingStartingSquare*(color: PieceColor): Square {.inline.} =
|
||||||
## Retrieves the starting square of the king
|
## Retrieves the starting square of the king
|
||||||
## for the given color
|
## for the given color
|
||||||
|
@ -90,3 +103,484 @@ func getOccupancy*(self: Position): Bitboard {.inline.} =
|
||||||
## Get the occupancy bitboard for every piece on
|
## Get the occupancy bitboard for every piece on
|
||||||
## the chessboard
|
## the chessboard
|
||||||
result = self.getOccupancyFor(Black) or self.getOccupancyFor(White)
|
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
|
currentExtensionCount: uint8
|
||||||
|
|
||||||
|
|
||||||
proc newSearchManager*(board: Chessboard, transpositions: TTable): SearchManager =
|
proc newSearchManager*(position: Position, transpositions: TTable): SearchManager =
|
||||||
new(result)
|
new(result)
|
||||||
result.board = board
|
result.board = newChessboard()
|
||||||
|
result.board.position = position
|
||||||
result.bestMoveRoot = nullMove()
|
result.bestMoveRoot = nullMove()
|
||||||
result.transpositionTable = transpositions
|
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
|
# 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
|
# is why we multiply the score of the captured piece by 10, to give
|
||||||
# it priority)
|
# 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():
|
if move.isPromotion():
|
||||||
# Promotions are a good idea to search first
|
# Promotions are a good idea to search first
|
||||||
var piece: Piece
|
var piece: Piece
|
||||||
|
@ -94,12 +95,12 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
||||||
piece = Piece(kind: Queen, color: sideToMove)
|
piece = Piece(kind: Queen, color: sideToMove)
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
discard # Unreachable
|
||||||
result += self.board.getPieceScore(piece, move.targetSquare)
|
result += self.board.position.getPieceScore(piece, move.targetSquare)
|
||||||
if self.board.getPawnAttacks(move.targetSquare, nonSideToMove) != 0:
|
if self.board.position.getPawnAttacks(move.targetSquare, nonSideToMove) != 0:
|
||||||
# Moving on a square attacked by an enemy pawn is _usually_ a very bad
|
# 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
|
# idea. Assume the piece is lost and give a malus based on the fact that
|
||||||
# losing a piece this way is dumb
|
# 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) =
|
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!
|
# if we can do other interesting things!
|
||||||
inc(self.currentExtensionCount)
|
inc(self.currentExtensionCount)
|
||||||
return 1
|
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
|
# If a pawn has just moved to its second-last rank, extend to
|
||||||
# see if a promotion would yield some good position
|
# see if a promotion would yield some good position
|
||||||
if piece.kind == Pawn:
|
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 &"Ply (from root): {board.position.plyFromRoot}"
|
||||||
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
echo &"Move: {move.startSquare.toAlgebraic()}{move.targetSquare.toAlgebraic()}"
|
||||||
echo &"Turn: {board.position.sideToMove}"
|
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 &"Flags: {move.getFlags()}"
|
||||||
echo &"In check: {(if board.inCheck(): \"yes\" else: \"no\")}"
|
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\")}"
|
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
|
# we have to figure out all the flags by ourselves (whether it's a double
|
||||||
# push, a capture, a promotion, etc.)
|
# push, a capture, a promotion, etc.)
|
||||||
|
|
||||||
if board.getPiece(targetSquare).kind != Empty:
|
if board.position.getPiece(targetSquare).kind != Empty:
|
||||||
flags.add(Capture)
|
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)
|
flags.add(DoublePush)
|
||||||
|
|
||||||
if len(moveString) == 5:
|
if len(moveString) == 5:
|
||||||
|
@ -160,12 +160,13 @@ proc handleMoveCommand(board: var Chessboard, command: seq[string]): Move {.disc
|
||||||
|
|
||||||
|
|
||||||
var move = createMove(startSquare, targetSquare, flags)
|
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 piece.kind == King and move.startSquare == board.position.sideToMove.getKingStartingSquare():
|
||||||
if move.targetSquare in [piece.kingSideCastling(), piece.queenSideCastling()]:
|
if move.targetSquare in [piece.kingSideCastling(), piece.queenSideCastling()]:
|
||||||
move.flags = move.flags or Castle.uint16
|
move.flags = move.flags or Castle.uint16
|
||||||
elif move.targetSquare == board.position.enPassantSquare:
|
elif piece.kind == Pawn and targetSquare == board.position.enPassantSquare:
|
||||||
move.flags = move.flags or EnPassant.uint16
|
# 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)
|
result = board.makeMove(move)
|
||||||
if result == nullMove():
|
if result == nullMove():
|
||||||
echo &"Error: move: {moveString} is illegal"
|
echo &"Error: move: {moveString} is illegal"
|
||||||
|
@ -343,7 +344,8 @@ const HELP_TEXT = """Nimfish help menu:
|
||||||
- fen: Shorthand for "position fen"
|
- fen: Shorthand for "position fen"
|
||||||
- pos <args>: Shorthand for "position <args>"
|
- pos <args>: Shorthand for "position <args>"
|
||||||
- get <square>: Get the piece on the given square
|
- 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
|
- pins: Print the current pin masks, if any
|
||||||
- checks: Print the current check mask, if in check
|
- 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
|
- 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
|
echo HELP_TEXT
|
||||||
of "skip":
|
of "skip":
|
||||||
board.position.sideToMove = board.position.sideToMove.opposite()
|
board.position.sideToMove = board.position.sideToMove.opposite()
|
||||||
board.updateChecksAndPins()
|
board.position.updateChecksAndPins()
|
||||||
of "go":
|
of "go":
|
||||||
handleGoCommand(board, cmd)
|
handleGoCommand(board, cmd)
|
||||||
of "position", "pos":
|
of "position", "pos":
|
||||||
|
@ -402,10 +404,19 @@ proc commandLoop*: int =
|
||||||
echo "error: atk: invalid number of arguments"
|
echo "error: atk: invalid number of arguments"
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
echo board.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove.opposite())
|
echo board.position.getAttacksTo(cmd[1].toSquare(), board.position.sideToMove.opposite())
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo "error: atk: invalid square"
|
echo "error: atk: invalid square"
|
||||||
continue
|
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":
|
of "ep":
|
||||||
let target = board.position.enPassantSquare
|
let target = board.position.enPassantSquare
|
||||||
if target != nullSquare():
|
if target != nullSquare():
|
||||||
|
@ -417,7 +428,7 @@ proc commandLoop*: int =
|
||||||
echo "error: get: invalid number of arguments"
|
echo "error: get: invalid number of arguments"
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
echo board.getPiece(cmd[1])
|
echo board.position.getPiece(cmd[1])
|
||||||
except ValueError:
|
except ValueError:
|
||||||
echo "error: get: invalid square"
|
echo "error: get: invalid square"
|
||||||
continue
|
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,
|
# 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
|
# we have to figure out all the flags by ourselves (whether it's a double
|
||||||
# push, a capture, a promotion, etc.)
|
# push, a capture, a promotion, etc.)
|
||||||
if session.board.getPiece(targetSquare).kind != Empty:
|
if session.board.position.getPiece(targetSquare).kind != Empty:
|
||||||
flags.add(Capture)
|
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)
|
flags.add(DoublePush)
|
||||||
|
|
||||||
if len(move) == 5:
|
if len(move) == 5:
|
||||||
|
@ -108,7 +108,7 @@ proc parseUCIMove(session: UCISession, move: string): tuple[move: Move, command:
|
||||||
flags.add(PromoteToRook)
|
flags.add(PromoteToRook)
|
||||||
else:
|
else:
|
||||||
return
|
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 piece.kind == King and startSquare == session.board.position.sideToMove.getKingStartingSquare():
|
||||||
if targetSquare in [piece.kingSideCastling(), piece.queenSideCastling()]:
|
if targetSquare in [piece.kingSideCastling(), piece.queenSideCastling()]:
|
||||||
flags.add(Castle)
|
flags.add(Castle)
|
||||||
|
@ -303,7 +303,7 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
|
||||||
echo &"info string created {session.hashTableSize} MiB TT"
|
echo &"info string created {session.hashTableSize} MiB TT"
|
||||||
session.transpositionTable = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
session.transpositionTable = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
||||||
var command = args.command
|
var command = args.command
|
||||||
var searcher = newSearchManager(session.board, session.transpositionTable)
|
var searcher = newSearchManager(session.board.position, session.transpositionTable)
|
||||||
session.currentSearch.store(searcher)
|
session.currentSearch.store(searcher)
|
||||||
var
|
var
|
||||||
timeRemaining = (if session.board.position.sideToMove == White: command.wtime else: command.btime)
|
timeRemaining = (if session.board.position.sideToMove == White: command.wtime else: command.btime)
|
||||||
|
|
Loading…
Reference in New Issue