From da82878ebb410fecfcd4dd5f63ea4ff803c11453 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Wed, 1 May 2024 16:54:08 +0200 Subject: [PATCH] Minor refactoring and cleanup. Switch away from boehm GC as that's too slow --- Chess/nim.cfg | 5 +- Chess/nimfish/nimfishpkg/board.nim | 81 +++++++++++++-------------- Chess/nimfish/nimfishpkg/movegen.nim | 26 ++++----- Chess/nimfish/nimfishpkg/position.nim | 13 ++--- Chess/nimfish/nimfishpkg/tui.nim | 6 +- Chess/nimfish/nimfishpkg/uci.nim | 2 +- 6 files changed, 62 insertions(+), 71 deletions(-) diff --git a/Chess/nim.cfg b/Chess/nim.cfg index f4ac049..f2b04e9 100644 --- a/Chess/nim.cfg +++ b/Chess/nim.cfg @@ -3,7 +3,4 @@ -d:danger --passL:"-flto" --passC:"-flto -march=native -mtune=native" ---mm:boehm ---stackTrace ---lineTrace ---debugger:native \ No newline at end of file +--mm:atomicArc diff --git a/Chess/nimfish/nimfishpkg/board.nim b/Chess/nimfish/nimfishpkg/board.nim index 2cad726..8ba2a7b 100644 --- a/Chess/nimfish/nimfishpkg/board.nim +++ b/Chess/nimfish/nimfishpkg/board.nim @@ -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(): diff --git a/Chess/nimfish/nimfishpkg/movegen.nim b/Chess/nimfish/nimfishpkg/movegen.nim index b50662d..a8fcc1f 100644 --- a/Chess/nimfish/nimfishpkg/movegen.nim +++ b/Chess/nimfish/nimfishpkg/movegen.nim @@ -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 diff --git a/Chess/nimfish/nimfishpkg/position.nim b/Chess/nimfish/nimfishpkg/position.nim index b224f52..66198ab 100644 --- a/Chess/nimfish/nimfishpkg/position.nim +++ b/Chess/nimfish/nimfishpkg/position.nim @@ -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 diff --git a/Chess/nimfish/nimfishpkg/tui.nim b/Chess/nimfish/nimfishpkg/tui.nim index 42a8b0f..b1adb77 100644 --- a/Chess/nimfish/nimfishpkg/tui.nim +++ b/Chess/nimfish/nimfishpkg/tui.nim @@ -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 diff --git a/Chess/nimfish/nimfishpkg/uci.nim b/Chess/nimfish/nimfishpkg/uci.nim index bd8d6c0..304090a 100644 --- a/Chess/nimfish/nimfishpkg/uci.nim +++ b/Chess/nimfish/nimfishpkg/uci.nim @@ -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)