From d9e183caf3fd04bc1c1f72416cf69fe9d4b60cc8 Mon Sep 17 00:00:00 2001 From: nocturn9x Date: Mon, 13 May 2024 17:11:24 +0200 Subject: [PATCH] Hopefully fix pondering bugs --- Chess/nimfish/nimfishpkg/position.nim | 7 +- Chess/nimfish/nimfishpkg/search.nim | 44 ++++++------ Chess/nimfish/nimfishpkg/transpositions.nim | 9 +-- Chess/nimfish/nimfishpkg/uci.nim | 74 ++++++++++----------- 4 files changed, 63 insertions(+), 71 deletions(-) diff --git a/Chess/nimfish/nimfishpkg/position.nim b/Chess/nimfish/nimfishpkg/position.nim index 379292a..625be4c 100644 --- a/Chess/nimfish/nimfishpkg/position.nim +++ b/Chess/nimfish/nimfishpkg/position.nim @@ -110,12 +110,7 @@ func getOccupancy*(self: Position): Bitboard {.inline.} = proc getPawnAttacks*(self: Position, square: Square, attacker: PieceColor): Bitboard {.inline.} = ## Returns the locations of the pawns attacking the given square - try: - return self.getBitboard(Pawn, attacker) and getPawnAttacks(attacker, square) - except IndexDefect: - echo square - echo square.int - echo square.toBitboard() + return self.getBitboard(Pawn, attacker) and getPawnAttacks(attacker, square) proc getKingAttacks*(self: Position, square: Square, attacker: PieceColor): Bitboard {.inline.} = diff --git a/Chess/nimfish/nimfishpkg/search.nim b/Chess/nimfish/nimfishpkg/search.nim index 09becb7..adb3c45 100644 --- a/Chess/nimfish/nimfishpkg/search.nim +++ b/Chess/nimfish/nimfishpkg/search.nim @@ -107,9 +107,10 @@ type SearchManager* = object ## A simple state storage ## for our search - searchFlag: ptr Atomic[bool] - stopFlag: ptr Atomic[bool] - board: Chessboard + searching: Atomic[bool] + stop: Atomic[bool] + pondering: Atomic[bool] + board*: Chessboard bestRootScore: Score searchStart: MonoTime hardLimit: MonoTime @@ -126,29 +127,29 @@ type selectiveDepth: int -proc newSearchManager*(position: Position, positions: seq[Position], transpositions: ptr TTable, stopFlag, searchFlag: ptr Atomic[bool], +proc newSearchManager*(position: Position, positions: seq[Position], transpositions: ptr TTable, history: ptr HistoryTable, killers: ptr KillersTable): SearchManager = - var board = newChessboard() - board.position = position - board.positions = positions - result = SearchManager(board: board, transpositionTable: transpositions, stopFlag: stopFlag, - searchFlag: searchFlag, history: history, killers: killers) + result = SearchManager(board: newChessboard(), transpositionTable: transpositions, stop: Atomic[bool](), + searching: Atomic[bool](), pondering: Atomic[bool](), history: history, + killers: killers) + result.board.position = position + result.board.positions = positions for i in 0..MAX_DEPTH: for j in 0..MAX_DEPTH: result.pvMoves[i][j] = nullMove() -proc isSearching*(self: SearchManager): bool = +proc isSearching*(self: var SearchManager): bool = ## Returns whether a search for the best ## move is in progress - result = self.searchFlag[].load() + result = self.searching.load() -proc stop*(self: SearchManager) = +proc stop*(self: var SearchManager) = ## Stops the search if it is ## running if self.isSearching(): - self.stopFlag[].store(true) + self.stop.store(true) proc isKillerMove(self: SearchManager, move: Move, ply: int): bool = @@ -246,7 +247,9 @@ proc reorderMoves(self: SearchManager, moves: var MoveList, ply: int) = proc timedOut(self: SearchManager): bool = getMonoTime() >= self.hardLimit -proc cancelled(self: SearchManager): bool = self.stopFlag[].load() +proc isPondering*(self: var SearchManager): bool = self.pondering.load() +proc stopPondering*(self: var SearchManager) = self.pondering.store(false) +proc cancelled(self: var SearchManager): bool = self.stop.load() proc elapsedTime(self: SearchManager): int64 = (getMonoTime() - self.searchStart).inMilliseconds() @@ -272,13 +275,13 @@ proc log(self: SearchManager, depth: int) = echo logMsg -proc shouldStop(self: SearchManager): bool = +proc shouldStop(self: var SearchManager): bool = ## Returns whether searching should ## stop if self.cancelled(): # Search has been cancelled! return true - if self.timedOut(): + if self.timedOut() and not self.isPondering(): # We ran out of time! return true if self.maxNodes > 0 and self.nodeCount >= self.maxNodes: @@ -644,6 +647,7 @@ proc findBestLine*(self: var SearchManager, timeRemaining, increment: int64, max result = @[] var pv: array[256, Move] self.maxNodes = maxNodes + self.pondering.store(ponder) self.searchMoves = searchMoves self.searchStart = getMonoTime() self.hardLimit = self.searchStart + initDuration(milliseconds=maxSearchTime) @@ -651,7 +655,7 @@ proc findBestLine*(self: var SearchManager, timeRemaining, increment: int64, max var maxDepth = maxDepth if maxDepth == -1: maxDepth = 30 - self.searchFlag[].store(true) + self.searching.store(true) # Iterative deepening loop var score = Score(0) for depth in 1..min(MAX_DEPTH, maxDepth): @@ -668,10 +672,10 @@ proc findBestLine*(self: var SearchManager, timeRemaining, increment: int64, max # Soft time management: don't start a new search iteration # if the soft limit has expired, as it is unlikely to complete # anyway - if getMonoTime() >= self.softLimit: + if getMonoTime() >= self.softLimit and not self.isPondering(): break - self.searchFlag[].store(false) - self.stopFlag[].store(false) + self.searching.store(false) + self.stop.store(false) for move in pv: if move == nullMove(): break diff --git a/Chess/nimfish/nimfishpkg/transpositions.nim b/Chess/nimfish/nimfishpkg/transpositions.nim index 54f0fca..eee278c 100644 --- a/Chess/nimfish/nimfishpkg/transpositions.nim +++ b/Chess/nimfish/nimfishpkg/transpositions.nim @@ -81,7 +81,7 @@ proc newTranspositionTable*(size: uint64): TTable = result.size = numEntries -func clear*(self: var TTable) = +func clear*(self: var TTable) {.inline.} = ## Clears the transposition table ## without releasing the memory ## associated with it @@ -97,13 +97,6 @@ func resize*(self: var TTable, newSize: uint64) = self.size = numEntries -func destroy*(self: var TTable) = - ## Permanently and irreversibly - ## destroys the transposition table - self.data = @[] - self.size = 0 - - func getIndex(self: TTable, key: ZobristKey): uint64 = ## Retrieves the index of the given ## zobrist key in our transposition table diff --git a/Chess/nimfish/nimfishpkg/uci.nim b/Chess/nimfish/nimfishpkg/uci.nim index 4b8fd54..bc4bd54 100644 --- a/Chess/nimfish/nimfishpkg/uci.nim +++ b/Chess/nimfish/nimfishpkg/uci.nim @@ -15,7 +15,6 @@ ## Implementation of a UCI compatible server import std/strutils import std/strformat -import std/atomics import board @@ -38,6 +37,7 @@ type ## doesn't like sharing references across thread (despite ## the fact that it should be safe to do so) searchState: ptr SearchManager + printMove: ptr bool # Size of the transposition table (in megabytes) hashTableSize: uint64 # # Atomic boolean flag to interrupt the search @@ -173,6 +173,8 @@ proc handleUCIGoCommand(session: UCISession, command: seq[string]): UCICommand = of "infinite": result.wtime = int32.high() result.btime = int32.high() + of "ponder": + result.ponder = true of "wtime": result.wtime = command[current].parseInt() of "btime": @@ -329,7 +331,6 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.} board.position = session.position board.positions = session.history let command = args.command - var searcher = session.searchState[] var timeRemaining = (if session.position.sideToMove == White: command.wtime else: command.btime) increment = (if session.position.sideToMove == White: command.winc else: command.binc) @@ -339,11 +340,12 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.} increment = 0 elif timeRemaining == 0: timeRemaining = int32.high() - var line = searcher.findBestLine(timeRemaining, increment, command.depth, command.nodes, command.searchmoves, timePerMove, command.ponder) - if line.len() == 1: + var line = session.searchState[].findBestLine(timeRemaining, increment, command.depth, command.nodes, command.searchmoves, timePerMove, command.ponder) + if session.printMove[]: + if line.len() == 1: echo &"bestmove {line[0].toAlgebraic()}" - else: - echo &"bestmove {line[0].toAlgebraic()} ponder {line[1].toAlgebraic()}" + else: + echo &"bestmove {line[0].toAlgebraic()} ponder {line[1].toAlgebraic()}" proc startUCISession* = @@ -359,14 +361,13 @@ proc startUCISession* = # God forbid we try to use atomic ARC like it was intended. Raw pointers # it is then... sigh var - stopFlag = cast[ptr Atomic[bool]](alloc0(sizeof(Atomic[bool]))) - searchFlag = cast[ptr Atomic[bool]](alloc0(sizeof(Atomic[bool]))) transpositionTable = cast[ptr TTable](alloc0(sizeof(TTable))) historyTable = cast[ptr HistoryTable](alloc0(sizeof(HistoryTable))) killerMoves = cast[ptr KillersTable](alloc0(sizeof(KillersTable))) transpositionTable[] = newTranspositionTable(session.hashTableSize * 1024 * 1024) session.searchState = cast[ptr SearchManager](alloc0(sizeof(SearchManager))) - session.searchState[] = newSearchManager(session.position, session.history, transpositionTable, stopFlag, searchFlag, historyTable, killerMoves) + session.searchState[] = newSearchManager(session.position, session.history, transpositionTable, historyTable, killerMoves) + session.printMove = cast[ptr bool](alloc0(sizeof(bool))) # Initialize history table for color in PieceColor.White..PieceColor.Black: for i in Square(0)..Square(63): @@ -408,14 +409,9 @@ proc startUCISession* = of Debug: session.debug = cmd.on of NewGame: - if transpositionTable[].size() == 0: - if session.debug: - echo &"info string allocating new TT of size {session.hashTableSize} MiB" - transpositionTable[] = newTranspositionTable(session.hashTableSize * 1024 * 1024) - else: - if session.debug: - echo &"info string clearing out TT of size {session.hashTableSize} MiB" - transpositionTable[].clear() + if session.debug: + echo &"info string clearing out TT of size {session.hashTableSize} MiB" + transpositionTable[].clear() # Re-Initialize history table for color in PieceColor.White..PieceColor.Black: for i in Square(0)..Square(63): @@ -430,24 +426,25 @@ proc startUCISession* = echo "info string ponder move has ben hit" if not session.searchState[].isSearching(): continue - joinThread(searchThread) + session.searchState[].stopPondering() if session.debug: - echo "info string ponder search stopped" - createThread(searchThread, bestMove, (session, cmd)) - if session.debug: - echo "info string search started" + echo "info string switched to normal search" of Go: - when not defined(historyPenalty): - # Scale our history coefficients - for color in PieceColor.White..PieceColor.Black: - for source in Square(0)..Square(63): - for target in Square(0)..Square(63): - historyTable[color][source][target] = historyTable[color][source][target] div 2 - if searchThread.running: - joinThread(searchThread) - createThread(searchThread, bestMove, (session, cmd)) - if session.debug: - echo "info string search started" + session.printMove[] = true + if not cmd.ponder and session.searchState[].isPondering(): + session.searchState[].stopPondering() + else: + when not defined(historyPenalty): + # Scale our history coefficients + for color in PieceColor.White..PieceColor.Black: + for source in Square(0)..Square(63): + for target in Square(0)..Square(63): + historyTable[color][source][target] = historyTable[color][source][target] div 2 + if searchThread.running: + joinThread(searchThread) + createThread(searchThread, bestMove, (session, cmd)) + if session.debug: + echo "info string search started" of Stop: if not session.searchState[].isSearching(): continue @@ -474,10 +471,13 @@ proc startUCISession* = else: discard of Position: - # Due to the way the whole thing is designed, the - # position is actually set when the command is parsed - # rather than when it is processed here - discard + if session.searchState[].isPondering(): + session.printMove[] = false + session.searchState[].stopPondering() + session.searchState[].stop() + joinThread(searchThread) + session.searchState[].board.position = session.position + session.searchState[].board.positions = session.history else: discard except IOError: