Minor refactoring and cleanup. Switch away from boehm GC as that's too slow

This commit is contained in:
Mattia Giambirtone 2024-05-01 16:54:08 +02:00
parent 70490f5698
commit da82878ebb
6 changed files with 62 additions and 71 deletions

View File

@ -3,7 +3,4 @@
-d:danger
--passL:"-flto"
--passC:"-flto -march=native -mtune=native"
--mm:boehm
--stackTrace
--lineTrace
--debugger:native
--mm:atomicArc

View File

@ -32,7 +32,7 @@ export pieces, position, bitboards, moves, magics, rays, zobrist
type
Chessboard* = ref object
Chessboard* = object
## A chessboard
# The actual board where pieces live
@ -45,13 +45,12 @@ type
# A bunch of simple utility functions and forward declarations
proc toFEN*(self: Chessboard): string
proc updateChecksAndPins*(self: Chessboard)
proc hash*(self: Chessboard)
proc updateChecksAndPins*(self: var Chessboard)
proc hash*(self: var Chessboard)
proc newChessboard*: Chessboard =
## Returns a new, empty chessboard
new(result)
for i in 0..63:
result.grid[i] = nullPiece()
result.position = Position(enPassantSquare: nullSquare(), sideToMove: White)
@ -102,7 +101,7 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
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.pieces[piece.color][piece.kind].setBit(square)
result.grid[square] = piece
inc(column)
of '/':
@ -133,13 +132,13 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
of '-':
discard
of 'K':
result.position.castlingAvailability[White.int].king = true
result.position.castlingAvailability[White].king = true
of 'Q':
result.position.castlingAvailability[White.int].queen = true
result.position.castlingAvailability[White].queen = true
of 'k':
result.position.castlingAvailability[Black.int].king = true
result.position.castlingAvailability[Black].king = true
of 'q':
result.position.castlingAvailability[Black.int].queen = true
result.position.castlingAvailability[Black].queen = true
else:
raise newException(ValueError, &"invalid FEN: unknown symbol '{c}' found in castlingRights availability section")
of 3:
@ -186,7 +185,7 @@ func countPieces*(self: Chessboard, kind: PieceKind, color: PieceColor): int {.i
## Returns the number of pieces with
## the given color and type in the
## current position
return self.position.pieces[color][kind][].countSquares()
return self.position.pieces[color][kind].countSquares()
func getPiece*(self: Chessboard, square: Square): Piece {.inline.} =
@ -200,20 +199,20 @@ func getPiece*(self: Chessboard, square: string): Piece {.inline.} =
return self.getPiece(square.toSquare())
proc removePieceFromBitboard*(self: Chessboard, square: Square) =
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)
self.position.pieces[piece.color][piece.kind].clearBit(square)
proc addPieceToBitboard*(self: Chessboard, square: Square, piece: Piece) =
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)
self.position.pieces[piece.color][piece.kind].setBit(square)
proc spawnPiece*(self: Chessboard, square: Square, piece: Piece) =
proc spawnPiece*(self: var Chessboard, square: Square, piece: Piece) =
## Internal helper to "spawn" a given piece at the given
## square
when not defined(danger):
@ -222,7 +221,7 @@ proc spawnPiece*(self: Chessboard, square: Square, piece: Piece) =
self.grid[square] = piece
proc removePiece*(self: Chessboard, square: Square) =
proc removePiece*(self: var Chessboard, square: Square) =
## Removes a piece from the board, updating necessary
## metadata
when not defined(danger):
@ -232,7 +231,7 @@ proc removePiece*(self: Chessboard, square: Square) =
self.grid[square] = nullPiece()
proc movePiece*(self: Chessboard, move: Move) =
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)
@ -245,7 +244,7 @@ proc movePiece*(self: Chessboard, move: Move) =
self.spawnPiece(move.targetSquare, piece)
proc movePiece*(self: Chessboard, startSquare, targetSquare: Square) =
proc movePiece*(self: var Chessboard, startSquare, targetSquare: Square) =
self.movePiece(createMove(startSquare, targetSquare))
@ -377,7 +376,7 @@ proc isOccupancyAttacked*(self: Chessboard, square: Square, occupancy: Bitboard)
return true
proc updateChecksAndPins*(self: Chessboard) =
proc updateChecksAndPins*(self: var Chessboard) =
## Updates internal metadata about checks and
## pinned pieces
@ -428,7 +427,7 @@ proc canCastle*(self: Chessboard): tuple[queen, king: bool] =
let
sideToMove = self.position.sideToMove
occupancy = self.getOccupancy()
result = self.position.castlingAvailability[sideToMove.int]
result = self.position.castlingAvailability[sideToMove]
if result.king:
result.king = (kingSideCastleRay(sideToMove) and occupancy) == 0
if result.queen:
@ -458,35 +457,35 @@ proc canCastle*(self: Chessboard): tuple[queen, king: bool] =
break
proc update*(self: Chessboard) =
proc update*(self: var Chessboard) =
## Updates the internal grid representation
## according to the positional data stored
## in the chessboard
for i in 0..63:
self.grid[i] = nullPiece()
for sq in self.position.pieces[White][Pawn][]:
for sq in self.position.pieces[White][Pawn]:
self.grid[sq] = Piece(color: White, kind: Pawn)
for sq in self.position.pieces[Black][Pawn][]:
for sq in self.position.pieces[Black][Pawn]:
self.grid[sq] = Piece(color: Black, kind: Pawn)
for sq in self.position.pieces[White][Bishop][]:
for sq in self.position.pieces[White][Bishop]:
self.grid[sq] = Piece(color: White, kind: Bishop)
for sq in self.position.pieces[Black][Bishop][]:
for sq in self.position.pieces[Black][Bishop]:
self.grid[sq] = Piece(color: Black, kind: Bishop)
for sq in self.position.pieces[White][Knight][]:
for sq in self.position.pieces[White][Knight]:
self.grid[sq] = Piece(color: White, kind: Knight)
for sq in self.position.pieces[Black][Knight][]:
for sq in self.position.pieces[Black][Knight]:
self.grid[sq] = Piece(color: Black, kind: Knight)
for sq in self.position.pieces[White][Rook][]:
for sq in self.position.pieces[White][Rook]:
self.grid[sq] = Piece(color: White, kind: Rook)
for sq in self.position.pieces[Black][Rook][]:
for sq in self.position.pieces[Black][Rook]:
self.grid[sq] = Piece(color: Black, kind: Rook)
for sq in self.position.pieces[White][Queen][]:
for sq in self.position.pieces[White][Queen]:
self.grid[sq] = Piece(color: White, kind: Queen)
for sq in self.position.pieces[Black][Queen][]:
for sq in self.position.pieces[Black][Queen]:
self.grid[sq] = Piece(color: Black, kind: Queen)
for sq in self.position.pieces[White][King][]:
for sq in self.position.pieces[White][King]:
self.grid[sq] = Piece(color: White, kind: King)
for sq in self.position.pieces[Black][King][]:
for sq in self.position.pieces[Black][King]:
self.grid[sq] = Piece(color: Black, kind: King)
@ -559,8 +558,8 @@ proc toFEN*(self: Chessboard): string =
result &= (if self.position.sideToMove == White: "w" else: "b")
result &= " "
# Castling availability
let castleWhite = self.position.castlingAvailability[White.int]
let castleBlack = self.position.castlingAvailability[Black.int]
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:
@ -586,7 +585,7 @@ proc toFEN*(self: Chessboard): string =
result &= $self.position.fullMoveCount
proc drawByRepetition*(self: Chessboard): bool =
proc drawByRepetition*(self: var Chessboard): bool =
## Returns whether the current position is a draw
## by repetition
# TODO: Improve this
@ -601,7 +600,7 @@ proc drawByRepetition*(self: Chessboard): bool =
dec(i)
proc hash*(self: Chessboard) =
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
@ -615,13 +614,13 @@ proc hash*(self: Chessboard) =
for sq in self.getOccupancy():
self.position.zobristKey = self.position.zobristKey xor self.getPiece(sq).getKey(sq)
if self.position.castlingAvailability[White.int].king:
if self.position.castlingAvailability[White].king:
self.position.zobristKey = self.position.zobristKey xor getKingSideCastlingKey(White)
if self.position.castlingAvailability[White.int].queen:
if self.position.castlingAvailability[White].queen:
self.position.zobristKey = self.position.zobristKey xor getQueenSideCastlingKey(White)
if self.position.castlingAvailability[Black.int].king:
if self.position.castlingAvailability[Black].king:
self.position.zobristKey = self.position.zobristKey xor getKingSideCastlingKey(Black)
if self.position.castlingAvailability[Black.int].queen:
if self.position.castlingAvailability[Black].queen:
self.position.zobristKey = self.position.zobristKey xor getQueenSideCastlingKey(Black)
if self.position.enPassantSquare != nullSquare():

View File

@ -32,7 +32,7 @@ export bitboards, magics, pieces, moves, position, rays, board
proc generatePawnMoves(self: Chessboard, moves: var MoveList, destinationMask: Bitboard) =
proc generatePawnMoves(self: var Chessboard, moves: var MoveList, destinationMask: Bitboard) =
let
sideToMove = self.position.sideToMove
nonSideToMove = sideToMove.opposite()
@ -272,7 +272,7 @@ proc generateCastling(self: Chessboard, moves: var MoveList) =
moves.add(createMove(kingSquare, kingPiece.queenSideCastling(), Castle))
proc generateMoves*(self: Chessboard, moves: var MoveList, capturesOnly: bool = false) =
proc generateMoves*(self: var Chessboard, moves: var MoveList, capturesOnly: bool = false) =
## Generates the list of all possible legal moves
## in the current position. If capturesOnly is
## true, only capture moves are generated
@ -330,7 +330,7 @@ proc generateMoves*(self: Chessboard, moves: var MoveList, capturesOnly: bool =
# Queens are just handled rooks + bishops
proc doMove*(self: Chessboard, move: Move) =
proc doMove*(self: var Chessboard, move: Move) =
## Internal function called by makeMove after
## performing legality checks. Can be used in
## performance-critical paths where a move is
@ -388,7 +388,7 @@ proc doMove*(self: Chessboard, move: Move) =
if move.isCastling() or piece.kind == King:
# If the king has moved, all castling rights for the side to
# move are revoked
self.position.castlingAvailability[piece.color.int] = (false, false)
self.position.castlingAvailability[piece.color] = (false, false)
if move.isCastling():
# Move the rook where it belongs
var
@ -414,9 +414,9 @@ proc doMove*(self: Chessboard, move: Move) =
# If a rook on either side moves, castling rights are permanently revoked
# on that side
if move.startSquare == piece.color.kingSideRook():
self.position.castlingAvailability[piece.color.int].king = false
self.position.castlingAvailability[piece.color].king = false
elif move.startSquare == piece.color.queenSideRook():
self.position.castlingAvailability[piece.color.int].queen = false
self.position.castlingAvailability[piece.color].queen = false
if move.isCapture():
# Get rid of captured pieces
@ -426,9 +426,9 @@ proc doMove*(self: Chessboard, move: Move) =
# If a rook has been captured, castling on that side is prohibited
if captured.kind == Rook:
if move.targetSquare == captured.color.kingSideRook():
self.position.castlingAvailability[captured.color.int].king = false
self.position.castlingAvailability[captured.color].king = false
elif move.targetSquare == captured.color.queenSideRook():
self.position.castlingAvailability[captured.color.int].queen = false
self.position.castlingAvailability[captured.color].queen = false
# Move the piece to its target square
self.movePiece(move)
@ -457,21 +457,21 @@ proc doMove*(self: Chessboard, move: Move) =
# Updates checks and pins for the (new) side to move
self.updateChecksAndPins()
# Last updates to zobrist key
if self.position.castlingAvailability[piece.color.int].king:
if self.position.castlingAvailability[piece.color].king:
self.position.zobristKey = self.position.zobristKey xor getKingSideCastlingKey(piece.color)
if self.position.castlingAvailability[piece.color.int].queen:
if self.position.castlingAvailability[piece.color].queen:
self.position.zobristKey = self.position.zobristKey xor getQueenSideCastlingKey(piece.color)
discard self.drawByRepetition()
proc isLegal*(self: Chessboard, move: Move): bool {.inline.} =
proc isLegal*(self: var Chessboard, move: Move): bool {.inline.} =
## Returns whether the given move is legal
var moves = newMoveList()
self.generateMoves(moves)
return move in moves
proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} =
proc makeMove*(self: var Chessboard, move: Move): Move {.discardable.} =
## Makes a move on the board
result = move
# Updates checks and pins for the side to move
@ -480,7 +480,7 @@ proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} =
self.doMove(move)
proc unmakeMove*(self: Chessboard) =
proc unmakeMove*(self: var Chessboard) =
## Reverts to the previous board position
if self.positions.len() == 0:
return

View File

@ -25,7 +25,7 @@ type
# of whether the king or the rooks on either side
# moved, the actual checks for the legality of castling
# are done elsewhere
castlingAvailability*: array[2, tuple[queen, king: bool]]
castlingAvailability*: array[PieceColor.White..PieceColor.Black, tuple[queen, king: bool]]
# Number of half-moves that were performed
# to reach this position starting from the
# root of the tree
@ -42,7 +42,7 @@ type
# The side to move
sideToMove*: PieceColor
# Positional bitboards for all pieces
pieces*: array[2, array[6, Bitboard]]
pieces*: array[PieceColor.White..PieceColor.Black, array[PieceKind.Bishop..PieceKind.Rook, Bitboard]]
# Pieces pinned for the current side to move
diagonalPins*: Bitboard # Pinned diagonally (by a queen or bishop)
orthogonalPins*: Bitboard # Pinned orthogonally (by a queen or rook)
@ -66,14 +66,9 @@ func getKingStartingSquare*(color: PieceColor): Square {.inline.} =
discard
func `[]`*(self: array[2, array[6, Bitboard]], color: PieceColor): ptr array[6, Bitboard] {.inline.} = addr self[color.int]
func `[]`*(self: array[6, Bitboard], kind: PieceKind): ptr Bitboard {.inline.} = addr self[kind.int]
func `[]=`*(self: var array[6, Bitboard], kind: PieceKind, bitboard: Bitboard) {.inline.} = self[kind.int] = bitboard
func getBitboard*(self: Position, kind: PieceKind, color: PieceColor): Bitboard =
## Returns the positional bitboard for the given piece kind and color
return self.pieces[color.int][kind.int]
return self.pieces[color][kind]
func getBitboard*(self: Position, piece: Piece): Bitboard =
@ -84,7 +79,7 @@ func getBitboard*(self: Position, piece: Piece): Bitboard =
func getOccupancyFor*(self: Position, color: PieceColor): Bitboard =
## Get the occupancy bitboard for every piece of the given color
result = Bitboard(0)
for b in self.pieces[color][]:
for b in self.pieces[color]:
result = result or b

View File

@ -29,7 +29,7 @@ type
CountData = tuple[nodes: uint64, captures: uint64, castles: uint64, checks: uint64, promotions: uint64, enPassant: uint64, checkmates: uint64]
proc perft*(board: Chessboard, ply: int, verbose = false, divide = false, bulk = false, capturesOnly = false): CountData =
proc perft*(board: var Chessboard, ply: int, verbose = false, divide = false, bulk = false, capturesOnly = false): CountData =
## Counts (and debugs) the number of legal positions reached after
## the given number of ply
@ -134,7 +134,7 @@ proc perft*(board: Chessboard, ply: int, verbose = false, divide = false, bulk =
result.checkmates += next.checkmates
proc handleGoCommand(board: Chessboard, command: seq[string]) =
proc handleGoCommand(board: var Chessboard, command: seq[string]) =
if len(command) < 2:
echo &"Error: go: invalid number of arguments"
return
@ -191,7 +191,7 @@ proc handleGoCommand(board: Chessboard, command: seq[string]) =
echo &"Error: go: unknown subcommand '{command[1]}'"
proc handleMoveCommand(board: Chessboard, command: seq[string]): Move {.discardable.} =
proc handleMoveCommand(board: var Chessboard, command: seq[string]): Move {.discardable.} =
if len(command) != 2:
echo &"Error: move: invalid number of arguments"
return

View File

@ -303,7 +303,7 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
echo &"info string created {session.hashTableSize} MiB TT"
session.transpositionTable = newTranspositionTable(session.hashTableSize * 1024 * 1024)
var command = args.command
var searcher = newSearchManager(session.board.deepCopy(), session.transpositionTable)
var searcher = newSearchManager(session.board, session.transpositionTable)
session.currentSearch.store(searcher)
var
timeRemaining = (if session.board.position.sideToMove == White: command.wtime else: command.btime)