Fix bugs with LMR (needs testing). Move to ptrs. General refactoring
This commit is contained in:
parent
245a5d75e8
commit
2a1f020edd
|
@ -7,7 +7,4 @@ nimfish/nimfishpkg/resources/*.epd
|
|||
nimfish/nimfishpkg/resources/*.pgn
|
||||
# Python
|
||||
__pycache__
|
||||
fast-chess
|
||||
log.txt
|
||||
config.json
|
||||
*.log
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
--cc:clang
|
||||
-o:"bin/nimfish"
|
||||
-d:debug
|
||||
-d:danger
|
||||
--passL:"-flto -lmimalloc"
|
||||
--passC:"-flto -march=native -mtune=native"
|
||||
-d:useMalloc
|
||||
--mm:atomicArc
|
||||
# --stackTrace
|
||||
# --lineTrace
|
||||
--mm:atomicArc
|
|
@ -246,24 +246,24 @@ proc getPieceScore*(position: Position, piece: Piece, square: Square): Score =
|
|||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
||||
|
||||
|
||||
proc evaluateMaterial(board: ChessBoard): Score =
|
||||
proc evaluateMaterial(position: Position): Score =
|
||||
## Returns a material and position evaluation
|
||||
## for the current side to move
|
||||
let
|
||||
middleGamePhase = board.position.getGamePhase()
|
||||
middleGamePhase = position.getGamePhase()
|
||||
endGamePhase = 24 - middleGamePhase
|
||||
var
|
||||
# White, Black
|
||||
middleGameScores: array[PieceColor.White..PieceColor.Black, Score] = [0, 0]
|
||||
endGameScores: array[PieceColor.White..PieceColor.Black, Score] = [0, 0]
|
||||
|
||||
for sq in board.position.getOccupancy():
|
||||
let piece = board.position.getPiece(sq)
|
||||
for sq in position.getOccupancy():
|
||||
let piece = position.getPiece(sq)
|
||||
middleGameScores[piece.color] += MIDDLEGAME_VALUE_TABLES[piece.color][piece.kind][sq]
|
||||
endGameScores[piece.color] += ENDGAME_VALUE_TABLES[piece.color][piece.kind][sq]
|
||||
|
||||
let
|
||||
sideToMove = board.position.sideToMove
|
||||
sideToMove = position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
middleGameScore = middleGameScores[sideToMove] - middleGameScores[nonSideToMove]
|
||||
endGameScore = endGameScores[sideToMove] - endGameScores[nonSideToMove]
|
||||
|
@ -297,8 +297,7 @@ proc evaluatePawnStructure(position: Position): Score {.used.} =
|
|||
inc(isolatedPawns)
|
||||
# Pawns that are defended by another pawn are
|
||||
# stronger
|
||||
var
|
||||
strongPawnIncrement = Score(0)
|
||||
var strongPawnIncrement = Score(0)
|
||||
for pawn in position.getBitboard(Pawn, White):
|
||||
if position.getPawnAttacks(pawn, White) != 0:
|
||||
strongPawnIncrement += position.getPieceScore(pawn) div Score(4)
|
||||
|
@ -306,9 +305,9 @@ proc evaluatePawnStructure(position: Position): Score {.used.} =
|
|||
return DOUBLED_PAWNS_MALUS[doubledPawns] + ISOLATED_PAWN_MALUS[isolatedPawns] + strongPawnIncrement
|
||||
|
||||
|
||||
proc evaluate*(board: Chessboard): Score =
|
||||
proc evaluate*(position: Position): Score =
|
||||
## Evaluates the current position
|
||||
|
||||
result = board.evaluateMaterial()
|
||||
result = position.evaluateMaterial()
|
||||
when defined(evalPawns):
|
||||
result += board.evaluatePawnStructure()
|
||||
result += position.evaluatePawnStructure()
|
||||
|
|
|
@ -63,6 +63,7 @@ iterator pairs*(self: MoveList): tuple[i: int, move: Move] =
|
|||
var i = 0
|
||||
for item in self:
|
||||
yield (i, item)
|
||||
inc(i)
|
||||
|
||||
|
||||
func `$`*(self: MoveList): string =
|
||||
|
|
|
@ -25,17 +25,18 @@ import std/algorithm
|
|||
import std/monotimes
|
||||
import std/strformat
|
||||
|
||||
import threading/smartptrs
|
||||
|
||||
const NUM_KILLERS = 2
|
||||
|
||||
|
||||
type
|
||||
HistoryTable* = array[PieceColor.White..PieceColor.Black, array[Square(0)..Square(63), array[Square(0)..Square(63), Score]]]
|
||||
KillersTable* = seq[array[2, Move]]
|
||||
SearchManager* = ref object
|
||||
KillersTable* = seq[array[NUM_KILLERS, Move]]
|
||||
SearchManager* = object
|
||||
## A simple state storage
|
||||
## for our search
|
||||
searchFlag: SharedPtr[Atomic[bool]]
|
||||
stopFlag: SharedPtr[Atomic[bool]]
|
||||
searchFlag: ptr Atomic[bool]
|
||||
stopFlag: ptr Atomic[bool]
|
||||
board: Chessboard
|
||||
bestMoveRoot: Move
|
||||
bestRootScore: Score
|
||||
|
@ -44,16 +45,17 @@ type
|
|||
softLimit: MonoTime
|
||||
nodeCount: uint64
|
||||
maxNodes: uint64
|
||||
currentMove: Move
|
||||
currentMoveNumber: int
|
||||
searchMoves: seq[Move]
|
||||
previousBestMove: Move
|
||||
transpositionTable: SharedPtr[TTable]
|
||||
history: SharedPtr[HistoryTable]
|
||||
killers: SharedPtr[KillersTable]
|
||||
transpositionTable: ptr TTable
|
||||
history: ptr HistoryTable
|
||||
killers: ptr KillersTable
|
||||
|
||||
|
||||
proc newSearchManager*(board: Chessboard, transpositions: SharedPtr[TTable], stopFlag, searchFlag: SharedPtr[Atomic[bool]],
|
||||
history: SharedPtr[HistoryTable], killers: SharedPtr[KillersTable]): SearchManager =
|
||||
new(result)
|
||||
proc newSearchManager*(board: Chessboard, transpositions: ptr TTable, stopFlag, searchFlag: ptr Atomic[bool],
|
||||
history: ptr HistoryTable, killers: ptr KillersTable): SearchManager =
|
||||
result = SearchManager(board: board, bestMoveRoot: nullMove(), transpositionTable: transpositions, stopFlag: stopFlag,
|
||||
searchFlag: searchFlag, history: history, killers: killers)
|
||||
|
||||
|
@ -71,7 +73,17 @@ proc stop*(self: SearchManager) =
|
|||
self.stopFlag[].store(true)
|
||||
|
||||
|
||||
proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
||||
proc isKillerMove(self: SearchManager, move: Move, ply: int): bool =
|
||||
## Returns whether the given move is a killer move
|
||||
when not defined(killers):
|
||||
return false
|
||||
else:
|
||||
for killer in self.killers[][ply]:
|
||||
if killer == move:
|
||||
return true
|
||||
|
||||
|
||||
proc getEstimatedMoveScore(self: SearchManager, move: Move, ply: int): Score =
|
||||
## Returns an estimated static score for the move used
|
||||
## during move ordering
|
||||
result = Score(0)
|
||||
|
@ -84,7 +96,10 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
|||
|
||||
let query = self.transpositionTable[].get(self.board.position.zobristKey)
|
||||
if query.success and query.entry.bestMove != nullMove() and query.entry.bestMove == move:
|
||||
return highestEval() + 1
|
||||
return highestEval() - 1
|
||||
|
||||
if self.isKillerMove(move, ply):
|
||||
result += self.board.position.getPieceScore(move.startSquare) * 5
|
||||
|
||||
if not move.isCapture():
|
||||
# History euristic bonus
|
||||
|
@ -121,12 +136,12 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
|||
result -= self.board.position.getPieceScore(move.startSquare) * 2
|
||||
|
||||
|
||||
proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
||||
proc reorderMoves(self: SearchManager, moves: var MoveList, ply: int) =
|
||||
## Reorders the list of moves in-place, trying
|
||||
## to place the best ones first
|
||||
|
||||
proc orderer(a, b: Move): int {.closure.} =
|
||||
return cmp(self.getEstimatedMoveScore(a), self.getEstimatedMoveScore(b))
|
||||
return cmp(self.getEstimatedMoveScore(a, ply), self.getEstimatedMoveScore(b, ply))
|
||||
|
||||
# Ignore null moves beyond the lenght of the movelist
|
||||
sort(moves.data.toOpenArray(0, moves.len - 1), orderer, SortOrder.Descending)
|
||||
|
@ -134,12 +149,12 @@ proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
|||
|
||||
proc timedOut(self: SearchManager): bool = getMonoTime() >= self.hardLimit
|
||||
proc cancelled(self: SearchManager): bool = self.stopFlag[].load()
|
||||
proc elapsedTime(self: SearchManager): int64 = (getMonoTime() - self.searchStart).inMilliseconds()
|
||||
|
||||
|
||||
proc log(self: SearchManager, depth: int) =
|
||||
let
|
||||
elapsed = getMonoTime() - self.searchStart
|
||||
elapsedMsec = elapsed.inMilliseconds.uint64
|
||||
elapsedMsec = self.elapsedTime().uint64
|
||||
nps = 1000 * (self.nodeCount div max(elapsedMsec, 1))
|
||||
var logMsg = &"info depth {depth} time {elapsedMsec} nodes {self.nodeCount} nps {nps}"
|
||||
logMsg &= &" hashfull {self.transpositionTable[].getFillEstimate()}"
|
||||
|
@ -171,7 +186,7 @@ proc getSearchExtension(self: SearchManager, move: Move): int {.used.} =
|
|||
return 1
|
||||
|
||||
|
||||
proc qsearch(self: SearchManager, ply: uint8, alpha, beta: Score): Score =
|
||||
proc qsearch(self: var SearchManager, ply: int, alpha, beta: Score): Score =
|
||||
## Negamax search with a/b pruning that is restricted to
|
||||
## capture moves (commonly called quiescent search). The
|
||||
## purpose of this extra search step is to mitigate the
|
||||
|
@ -189,7 +204,7 @@ proc qsearch(self: SearchManager, ply: uint8, alpha, beta: Score): Score =
|
|||
return
|
||||
if ply == 127:
|
||||
return Score(0)
|
||||
let score = self.board.evaluate()
|
||||
let score = self.board.position.evaluate()
|
||||
if score >= beta:
|
||||
# Same as with the regular alpha-beta search
|
||||
return score
|
||||
|
@ -197,7 +212,7 @@ proc qsearch(self: SearchManager, ply: uint8, alpha, beta: Score): Score =
|
|||
return Score(0)
|
||||
var moves = newMoveList()
|
||||
self.board.generateMoves(moves, capturesOnly=true)
|
||||
self.reorderMoves(moves)
|
||||
self.reorderMoves(moves, ply)
|
||||
var bestScore = score
|
||||
var alpha = max(alpha, score)
|
||||
for move in moves:
|
||||
|
@ -219,13 +234,40 @@ proc qsearch(self: SearchManager, ply: uint8, alpha, beta: Score): Score =
|
|||
return bestScore
|
||||
|
||||
|
||||
proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.discardable.} =
|
||||
proc storeKillerMove(self: SearchManager, ply: int, move: Move) =
|
||||
## Stores a killer move into our killers table at the given
|
||||
## ply
|
||||
|
||||
# Stolen from https://rustic-chess.org/search/ordering/killers.html
|
||||
|
||||
# First killer move must not be the same as the one we're storing
|
||||
let first = self.killers[][ply][0]
|
||||
if first == move:
|
||||
return
|
||||
var j = self.killers[][ply].len() - 2
|
||||
while j >= 0:
|
||||
# Shift moves one spot down
|
||||
self.killers[][ply][j + 1] = self.killers[][ply][j];
|
||||
dec(j)
|
||||
self.killers[][ply][0] = move;
|
||||
|
||||
|
||||
proc shouldReduce(self: SearchManager, move: Move, depth, moveNumber: int): bool =
|
||||
## Returns whether the search should be reduced at the given
|
||||
## depth and move number
|
||||
return defined(searchLMR) and moveNumber >= 5 and depth > 3 and not move.isCapture()
|
||||
|
||||
|
||||
proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score): Score {.discardable.} =
|
||||
## Negamax search with various optimizations and search features
|
||||
if depth > 1 and self.shouldStop():
|
||||
# We do not let ourselves get cancelled at depth
|
||||
# one because then we wouldn't have a move to return.
|
||||
# In practice this should not be a problem
|
||||
return
|
||||
when defined(killers):
|
||||
if self.killers[].high() < ply:
|
||||
self.killers[].add([nullMove(), nullMove()])
|
||||
if ply > 0:
|
||||
let query = self.transpositionTable[].get(self.board.position.zobristKey, depth.uint8)
|
||||
if query.success:
|
||||
|
@ -248,7 +290,7 @@ proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.d
|
|||
depth = depth
|
||||
bestMove = nullMove()
|
||||
self.board.generateMoves(moves)
|
||||
self.reorderMoves(moves)
|
||||
self.reorderMoves(moves, ply)
|
||||
if moves.len() == 0:
|
||||
if self.board.inCheck():
|
||||
# Checkmate! We add the current ply
|
||||
|
@ -268,29 +310,33 @@ proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.d
|
|||
if ply == 0 and self.searchMoves.len() > 0 and move notin self.searchMoves:
|
||||
continue
|
||||
self.board.doMove(move)
|
||||
self.currentMove = move
|
||||
self.currentMoveNumber = i
|
||||
let extension = self.getSearchExtension(move)
|
||||
inc(self.nodeCount)
|
||||
# Find the best move for us (worst move
|
||||
# for our opponent, hence the negative sign)
|
||||
var score: Score
|
||||
|
||||
# Implementation of Principal Variation Search (PVS)
|
||||
if i == 0:
|
||||
# Due to our move ordering scheme, the first move is always the "best", so
|
||||
# search it always at full depth with the full search window
|
||||
score = -self.search(depth - 1 + extension, ply + 1, -beta, -alpha)
|
||||
# elif extension == 0 and depth > 3 and i >= 5 and not move.isCapture():
|
||||
# # Late Move Reductions: assume our move orderer did a good job,
|
||||
# # so it is not worth it to look at all moves at the same depth equally.
|
||||
# # If this move turns out to be better than we expected, we'll re-search
|
||||
# # it at full depth
|
||||
# const reduction = 1
|
||||
# # We first do a null-window search to see if there's a move that beats alpha
|
||||
# # (we don't care about the actual value, so we search in the range [alpha, alpha + 1]
|
||||
# # to increase the number of cutoffs)
|
||||
# score = -self.search(depth - 1 - reduction, ply + 1, -alpha - 1, -alpha)
|
||||
# if score > alpha:
|
||||
# score = -self.search(depth - 1 + extension, ply + 1, -alpha - 1, -alpha)
|
||||
elif extension == 0 and self.shouldReduce(move, depth, i):
|
||||
# Late Move Reductions: assume our move orderer did a good job,
|
||||
# so it is not worth it to look at all moves at the same depth equally.
|
||||
# If this move turns out to be better than we expected, we'll re-search
|
||||
# it at full depth
|
||||
const reduction = 1
|
||||
# We first do a null-window search to see if there's a move that beats alpha
|
||||
# (we don't care about the actual value, so we search in the range [alpha, alpha + 1]
|
||||
# to increase the number of cutoffs)
|
||||
score = -self.search(depth - 1 - reduction, ply + 1, -alpha - 1, -alpha)
|
||||
# If the null window search beats alpha, we do a full window reduced search to get a
|
||||
# better feel for the actual score of the position. If the score turns out to beat alpha
|
||||
# (but not beta) again, we'll re-search this at full depth later
|
||||
if score > alpha:
|
||||
score = -self.search(depth - 1 - reduction, ply + 1, -beta, -alpha)
|
||||
else:
|
||||
# Move wasn't reduced, just do a null window search
|
||||
score = -self.search(depth - 1 + extension, ply + 1, -alpha - 1, -alpha)
|
||||
|
@ -313,6 +359,10 @@ proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.d
|
|||
# quadratic bonus wrt. depth is usually the bonus that is used (though some
|
||||
# engines, namely Stockfish, use a linear bonus. Maybe we can investigate this)
|
||||
self.history[][sideToMove][move.startSquare][move.targetSquare] += Score(depth * depth)
|
||||
# Killer move heuristic: store moves that caused a beta cutoff according to the distance from
|
||||
# root that they occurred at, as they might be good refutations for future moves from the opponent.
|
||||
when defined(killers):
|
||||
self.storeKillerMove(ply, move)
|
||||
# This move was too good for us, opponent will not search it
|
||||
break
|
||||
if score > alpha:
|
||||
|
@ -321,22 +371,23 @@ proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.d
|
|||
if ply == 0:
|
||||
self.bestMoveRoot = move
|
||||
self.bestRootScore = bestScore
|
||||
else:
|
||||
when defined(noScaleHistory):
|
||||
if not move.isCapture() and self.history[][sideToMove][move.startSquare][move.targetSquare] > lowestEval():
|
||||
# Here, we punish moves that failed to raise alpha. This allows us to avoid scaling our values
|
||||
# after every search (which should retain more information about the explored subtreees) and
|
||||
# makes sure that moves that we thought were good but aren't are pushed further in the move list
|
||||
self.history[][sideToMove][move.startSquare][move.targetSquare] -= Score(depth * depth)
|
||||
else:
|
||||
discard
|
||||
# TODO
|
||||
# else:
|
||||
# when defined(noScaleHistory):
|
||||
# if not move.isCapture() and self.history[][sideToMove][move.startSquare][move.targetSquare] > lowestEval():
|
||||
# # Here, we punish moves that failed to raise alpha. This allows us to avoid scaling our values
|
||||
# # after every search (which should retain more information about the explored subtreees) and
|
||||
# # makes sure that moves that we thought were good but aren't are pushed further in the move list
|
||||
# self.history[][sideToMove][move.startSquare][move.targetSquare] -= Score(depth * depth)
|
||||
# else:
|
||||
# discard
|
||||
let nodeType = if bestScore >= beta: LowerBound elif bestScore <= alpha: UpperBound else: Exact
|
||||
self.transpositionTable[].store(depth.uint8, bestScore, self.board.position.zobristKey, bestMove, nodeType)
|
||||
|
||||
return bestScore
|
||||
|
||||
|
||||
proc findBestMove*(self: SearchManager, timeRemaining, increment: int64, maxDepth: int, maxNodes: uint64, searchMoves: seq[Move]): Move =
|
||||
proc findBestMove*(self: var SearchManager, timeRemaining, increment: int64, maxDepth: int, maxNodes: uint64, searchMoves: seq[Move]): Move =
|
||||
## Finds the best move in the current position
|
||||
## and returns it, limiting search time according
|
||||
## to the remaining time and increment values provided
|
||||
|
@ -351,7 +402,8 @@ proc findBestMove*(self: SearchManager, timeRemaining, increment: int64, maxDept
|
|||
# Apparently negative remaining time is a thing. Welp
|
||||
let
|
||||
maxSearchTime = max(1, (timeRemaining div 10) + (increment div 2))
|
||||
softLimit = max(1, maxSearchTime div 3)
|
||||
softLimit = maxSearchTime div 3
|
||||
echo maxSearchTime
|
||||
self.bestMoveRoot = nullMove()
|
||||
result = self.bestMoveRoot
|
||||
self.maxNodes = maxNodes
|
||||
|
@ -365,8 +417,6 @@ proc findBestMove*(self: SearchManager, timeRemaining, increment: int64, maxDept
|
|||
self.searchFlag[].store(true)
|
||||
# Iterative deepening loop
|
||||
for i in 1..maxDepth:
|
||||
if self.killers[].len() < i:
|
||||
self.killers[].add([nullMove(), nullMove()])
|
||||
# Search the previous best move first
|
||||
self.previousBestMove = self.bestMoveRoot
|
||||
self.search(i, 0, lowestEval(), highestEval())
|
||||
|
|
|
@ -458,7 +458,7 @@ proc commandLoop*: int =
|
|||
of "rep":
|
||||
echo "Position is drawn by repetition: ", if board.drawnByRepetition(): "yes" else: "no"
|
||||
of "eval":
|
||||
echo &"Eval: {board.evaluate()}"
|
||||
echo &"Eval: {board.position.evaluate()}"
|
||||
else:
|
||||
echo &"Unknown command '{cmd[0]}'. Type 'help' for more information."
|
||||
except IOError:
|
||||
|
|
|
@ -18,9 +18,6 @@ import std/strformat
|
|||
import std/atomics
|
||||
|
||||
|
||||
import threading/smartptrs
|
||||
|
||||
|
||||
import board
|
||||
import movegen
|
||||
import search
|
||||
|
@ -36,18 +33,18 @@ type
|
|||
# The current position
|
||||
position: Position
|
||||
# Atomic boolean flag to interrupt the search
|
||||
stopFlag: SharedPtr[Atomic[bool]]
|
||||
stopFlag: ptr Atomic[bool]
|
||||
# Atomic search flag used to know whether a search
|
||||
# is in progress
|
||||
searchFlag: SharedPtr[Atomic[bool]]
|
||||
searchFlag: ptr Atomic[bool]
|
||||
# Size of the transposition table (in megabytes)
|
||||
hashTableSize: uint64
|
||||
# The transposition table
|
||||
transpositionTable: SharedPtr[TTable]
|
||||
transpositionTable: ptr TTable
|
||||
# Storage for our history heuristic
|
||||
historyTable: SharedPtr[HistoryTable]
|
||||
historyTable: ptr HistoryTable
|
||||
# Storage for our killer move heuristic
|
||||
killerMoves: SharedPtr[KillersTable]
|
||||
killerMoves: ptr KillersTable
|
||||
|
||||
UCICommandType = enum
|
||||
## A UCI command type enumeration
|
||||
|
@ -327,10 +324,11 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
|
|||
var
|
||||
timeRemaining = (if session.position.sideToMove == White: command.wtime else: command.btime)
|
||||
increment = (if session.position.sideToMove == White: command.winc else: command.binc)
|
||||
if timeRemaining == 0:
|
||||
timeRemaining = int32.high()
|
||||
if command.moveTime != -1:
|
||||
timeRemaining = command.moveTime
|
||||
timeRemaining = 0
|
||||
increment = command.moveTime
|
||||
elif timeRemaining == 0:
|
||||
timeRemaining = int32.high()
|
||||
var move = searcher.findBestMove(timeRemaining, increment, command.depth, command.nodes, command.searchmoves)
|
||||
echo &"bestmove {move.toAlgebraic()}"
|
||||
|
||||
|
@ -345,13 +343,14 @@ proc startUCISession* =
|
|||
cmd: UCICommand
|
||||
cmdStr: string
|
||||
session = UCISession(hashTableSize: 64, position: startpos())
|
||||
session.transpositionTable = newSharedPtr(TTable)
|
||||
session.stopFlag = newSharedPtr(Atomic[bool])
|
||||
session.searchFlag = newSharedPtr(Atomic[bool])
|
||||
# God forbid we try to use atomic ARC like it was intended. Raw pointers
|
||||
# it is then... sigh
|
||||
session.transpositionTable = cast[ptr TTable](alloc0(sizeof(TTable)))
|
||||
session.stopFlag = cast[ptr Atomic[bool]](alloc0(sizeof(Atomic[bool])))
|
||||
session.searchFlag = cast[ptr Atomic[bool]](alloc0(sizeof(Atomic[bool])))
|
||||
session.transpositionTable[] = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
||||
session.historyTable = newSharedPtr(HistoryTable)
|
||||
session.killerMoves = newSharedPtr(KillersTable)
|
||||
session.stopFlag[].store(false)
|
||||
session.historyTable = cast[ptr HistoryTable](alloc0(sizeof(HistoryTable)))
|
||||
session.killerMoves = cast[ptr KillersTable](alloc0(sizeof(KillersTable)))
|
||||
# Fun fact, nim doesn't collect the memory of thread vars. Another stupid fucking design pitfall
|
||||
# of nim's AWESOME threading model. Someone is getting a pipebomb in their mailbox about this, mark
|
||||
# my fucking words. (for legal purposes THAT IS A JOKE). See https://github.com/nim-lang/Nim/issues/23165
|
||||
|
@ -412,7 +411,6 @@ proc startUCISession* =
|
|||
else:
|
||||
discard
|
||||
of Position:
|
||||
echo session.history
|
||||
discard
|
||||
else:
|
||||
discard
|
||||
|
|
Loading…
Reference in New Issue