Fix crashing, implement LMR and tempo bonus
This commit is contained in:
parent
2a1f020edd
commit
7292356948
|
@ -98,6 +98,10 @@ func drawnByRepetition*(self: Chessboard): bool =
|
||||||
inc(count)
|
inc(count)
|
||||||
if count == 2:
|
if count == 2:
|
||||||
return true
|
return true
|
||||||
|
if self.positions[i].halfMoveClock == 0:
|
||||||
|
# Position was reached via a pawn move or
|
||||||
|
# capture: cannot repeat beyond this point!
|
||||||
|
return false
|
||||||
dec(i)
|
dec(i)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,6 +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
|
||||||
|
TEMPO_BONUS = Score(10)
|
||||||
PAWN_MIDDLEGAME_SCORES: array[Square(0)..Square(63), 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,
|
||||||
|
@ -311,3 +312,5 @@ proc evaluate*(position: Position): Score =
|
||||||
result = position.evaluateMaterial()
|
result = position.evaluateMaterial()
|
||||||
when defined(evalPawns):
|
when defined(evalPawns):
|
||||||
result += position.evaluatePawnStructure()
|
result += position.evaluatePawnStructure()
|
||||||
|
# Tempo bonus: gains 19.5 +/- 13.7
|
||||||
|
result += TEMPO_BONUS
|
||||||
|
|
|
@ -40,7 +40,7 @@ type
|
||||||
targetSquare*: Square
|
targetSquare*: Square
|
||||||
flags*: uint16
|
flags*: uint16
|
||||||
|
|
||||||
MoveList* = ref object
|
MoveList* = object
|
||||||
## A list of moves
|
## A list of moves
|
||||||
data*: array[218, Move]
|
data*: array[218, Move]
|
||||||
len: int8
|
len: int8
|
||||||
|
@ -203,6 +203,5 @@ func toAlgebraic*(self: Move): string =
|
||||||
|
|
||||||
|
|
||||||
proc newMoveList*: MoveList =
|
proc newMoveList*: MoveList =
|
||||||
new(result)
|
|
||||||
for i in 0..result.data.high():
|
for i in 0..result.data.high():
|
||||||
result.data[i] = nullMove()
|
result.data[i] = nullMove()
|
|
@ -19,6 +19,7 @@ import eval
|
||||||
import transpositions
|
import transpositions
|
||||||
|
|
||||||
|
|
||||||
|
import std/math
|
||||||
import std/times
|
import std/times
|
||||||
import std/atomics
|
import std/atomics
|
||||||
import std/algorithm
|
import std/algorithm
|
||||||
|
@ -26,38 +27,54 @@ import std/monotimes
|
||||||
import std/strformat
|
import std/strformat
|
||||||
|
|
||||||
|
|
||||||
const NUM_KILLERS = 2
|
const NUM_KILLERS* = 2
|
||||||
|
const MAX_DEPTH* = 255
|
||||||
|
|
||||||
|
|
||||||
|
func computeLMRTable: array[MAX_DEPTH, array[218, int]] {.compileTime.} =
|
||||||
|
## Precomputes the table containing reduction offsets at compile
|
||||||
|
## time
|
||||||
|
for i in 1..result.high():
|
||||||
|
for j in 1..result[0].high():
|
||||||
|
result[i][j] = round(0.8 + ln(i.float) * ln(j.float) * 0.4).int
|
||||||
|
|
||||||
|
|
||||||
|
const LMR_TABLE {.used.} = computeLMRTable()
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
HistoryTable* = array[PieceColor.White..PieceColor.Black, array[Square(0)..Square(63), array[Square(0)..Square(63), Score]]]
|
HistoryTable* = array[PieceColor.White..PieceColor.Black, array[Square(0)..Square(63), array[Square(0)..Square(63), Score]]]
|
||||||
KillersTable* = seq[array[NUM_KILLERS, Move]]
|
KillersTable* = array[MAX_DEPTH, array[NUM_KILLERS, Move]]
|
||||||
SearchManager* = object
|
SearchManager* = object
|
||||||
## A simple state storage
|
## A simple state storage
|
||||||
## for our search
|
## for our search
|
||||||
searchFlag: ptr Atomic[bool]
|
searchFlag: ptr Atomic[bool]
|
||||||
stopFlag: ptr Atomic[bool]
|
stopFlag: ptr Atomic[bool]
|
||||||
board: Chessboard
|
board: Chessboard
|
||||||
bestMoveRoot: Move
|
|
||||||
bestRootScore: Score
|
bestRootScore: Score
|
||||||
searchStart: MonoTime
|
searchStart: MonoTime
|
||||||
hardLimit: MonoTime
|
hardLimit: MonoTime
|
||||||
softLimit: MonoTime
|
softLimit: MonoTime
|
||||||
nodeCount: uint64
|
nodeCount: uint64
|
||||||
maxNodes: uint64
|
maxNodes: uint64
|
||||||
currentMove: Move
|
|
||||||
currentMoveNumber: int
|
|
||||||
searchMoves: seq[Move]
|
searchMoves: seq[Move]
|
||||||
previousBestMove: Move
|
|
||||||
transpositionTable: ptr TTable
|
transpositionTable: ptr TTable
|
||||||
history: ptr HistoryTable
|
history: ptr HistoryTable
|
||||||
killers: ptr KillersTable
|
killers: ptr KillersTable
|
||||||
|
# We keep one extra entry so we don't need any special casing
|
||||||
|
# inside the search function when constructing pv lines
|
||||||
|
pvMoves: array[MAX_DEPTH + 1, array[MAX_DEPTH + 1, Move]]
|
||||||
|
selectiveDepth: int
|
||||||
|
|
||||||
|
|
||||||
proc newSearchManager*(board: Chessboard, transpositions: ptr TTable, stopFlag, searchFlag: ptr Atomic[bool],
|
proc newSearchManager*(board: Chessboard, transpositions: ptr TTable, stopFlag, searchFlag: ptr Atomic[bool],
|
||||||
history: ptr HistoryTable, killers: ptr KillersTable): SearchManager =
|
history: ptr HistoryTable, killers: ptr KillersTable): SearchManager =
|
||||||
result = SearchManager(board: board, bestMoveRoot: nullMove(), transpositionTable: transpositions, stopFlag: stopFlag,
|
result = SearchManager(board: board, transpositionTable: transpositions, stopFlag: stopFlag,
|
||||||
searchFlag: searchFlag, history: history, killers: killers)
|
searchFlag: searchFlag, history: history, killers: killers)
|
||||||
|
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: SearchManager): bool =
|
||||||
|
@ -91,14 +108,11 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move, ply: int): Score =
|
||||||
sideToMove = self.board.position.sideToMove
|
sideToMove = self.board.position.sideToMove
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove = sideToMove.opposite()
|
||||||
|
|
||||||
if self.previousBestMove != nullMove() and move == self.previousBestMove:
|
|
||||||
return highestEval() + 1
|
|
||||||
|
|
||||||
let query = self.transpositionTable[].get(self.board.position.zobristKey)
|
let query = self.transpositionTable[].get(self.board.position.zobristKey)
|
||||||
if query.success and query.entry.bestMove != nullMove() and query.entry.bestMove == move:
|
if query.success and query.entry.bestMove != nullMove() and query.entry.bestMove == move:
|
||||||
return highestEval() - 1
|
return highestEval() + 1
|
||||||
|
|
||||||
if self.isKillerMove(move, ply):
|
if ply > 0 and self.isKillerMove(move, ply):
|
||||||
result += self.board.position.getPieceScore(move.startSquare) * 5
|
result += self.board.position.getPieceScore(move.startSquare) * 5
|
||||||
|
|
||||||
if not move.isCapture():
|
if not move.isCapture():
|
||||||
|
@ -156,10 +170,14 @@ proc log(self: SearchManager, depth: int) =
|
||||||
let
|
let
|
||||||
elapsedMsec = self.elapsedTime().uint64
|
elapsedMsec = self.elapsedTime().uint64
|
||||||
nps = 1000 * (self.nodeCount div max(elapsedMsec, 1))
|
nps = 1000 * (self.nodeCount div max(elapsedMsec, 1))
|
||||||
var logMsg = &"info depth {depth} time {elapsedMsec} nodes {self.nodeCount} nps {nps}"
|
var logMsg = &"info depth {depth} seldepth {self.selectiveDepth} time {elapsedMsec} nodes {self.nodeCount} nps {nps}"
|
||||||
logMsg &= &" hashfull {self.transpositionTable[].getFillEstimate()}"
|
logMsg &= &" hashfull {self.transpositionTable[].getFillEstimate()} score cp {self.bestRootScore}"
|
||||||
if self.bestMoveRoot != nullMove():
|
if self.pvMoves[0][0] != nullMove():
|
||||||
logMsg &= &" score cp {self.bestRootScore} pv {self.bestMoveRoot.toAlgebraic()}"
|
logMsg &= " pv "
|
||||||
|
for move in self.pvMoves[0]:
|
||||||
|
if move == nullMove():
|
||||||
|
break
|
||||||
|
logMsg &= &"{move.toAlgebraic()} "
|
||||||
echo logMsg
|
echo logMsg
|
||||||
|
|
||||||
|
|
||||||
|
@ -186,6 +204,23 @@ proc getSearchExtension(self: SearchManager, move: Move): int {.used.} =
|
||||||
return 1
|
return 1
|
||||||
|
|
||||||
|
|
||||||
|
proc getReduction(self: SearchManager, move: Move, depth, ply, moveNumber: int, isPV: bool): int =
|
||||||
|
## Returns the amount a search depth can be reduced to
|
||||||
|
|
||||||
|
# Move reduction gains: 54.1 +/- 25.5
|
||||||
|
if moveNumber > 3 and depth > 2:
|
||||||
|
result = LMR_TABLE[depth][moveNumber]
|
||||||
|
if not isPV:
|
||||||
|
# Reduce non PV nodes more
|
||||||
|
inc(result)
|
||||||
|
|
||||||
|
if self.board.inCheck():
|
||||||
|
# Reduce less when opponent is in check
|
||||||
|
dec(result)
|
||||||
|
# Keep the reduction in the right range
|
||||||
|
result = result.clamp(0, depth - 1)
|
||||||
|
|
||||||
|
|
||||||
proc qsearch(self: var SearchManager, ply: int, 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
|
## Negamax search with a/b pruning that is restricted to
|
||||||
## capture moves (commonly called quiescent search). The
|
## capture moves (commonly called quiescent search). The
|
||||||
|
@ -202,7 +237,7 @@ proc qsearch(self: var SearchManager, ply: int, alpha, beta: Score): Score =
|
||||||
## exist
|
## exist
|
||||||
if self.shouldStop():
|
if self.shouldStop():
|
||||||
return
|
return
|
||||||
if ply == 127:
|
if ply == MAX_DEPTH:
|
||||||
return Score(0)
|
return Score(0)
|
||||||
let score = self.board.position.evaluate()
|
let score = self.board.position.evaluate()
|
||||||
if score >= beta:
|
if score >= beta:
|
||||||
|
@ -234,7 +269,7 @@ proc qsearch(self: var SearchManager, ply: int, alpha, beta: Score): Score =
|
||||||
return bestScore
|
return bestScore
|
||||||
|
|
||||||
|
|
||||||
proc storeKillerMove(self: SearchManager, ply: int, move: Move) =
|
proc storeKillerMove(self: SearchManager, ply: int, move: Move) {.used.} =
|
||||||
## Stores a killer move into our killers table at the given
|
## Stores a killer move into our killers table at the given
|
||||||
## ply
|
## ply
|
||||||
|
|
||||||
|
@ -249,25 +284,21 @@ proc storeKillerMove(self: SearchManager, ply: int, move: Move) =
|
||||||
# Shift moves one spot down
|
# Shift moves one spot down
|
||||||
self.killers[][ply][j + 1] = self.killers[][ply][j];
|
self.killers[][ply][j + 1] = self.killers[][ply][j];
|
||||||
dec(j)
|
dec(j)
|
||||||
self.killers[][ply][0] = move;
|
self.killers[][ply][0] = move
|
||||||
|
|
||||||
|
|
||||||
proc shouldReduce(self: SearchManager, move: Move, depth, moveNumber: int): bool =
|
proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score, isPV: bool): Score {.discardable.} =
|
||||||
## 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
|
## Negamax search with various optimizations and search features
|
||||||
|
|
||||||
|
# Clear the PV table
|
||||||
|
for i in 0..MAX_DEPTH:
|
||||||
|
self.pvMoves[ply][i] = nullMove()
|
||||||
if depth > 1 and self.shouldStop():
|
if depth > 1 and self.shouldStop():
|
||||||
# We do not let ourselves get cancelled at depth
|
# We do not let ourselves get cancelled at depth
|
||||||
# one because then we wouldn't have a move to return.
|
# one because then we wouldn't have a move to return.
|
||||||
# In practice this should not be a problem
|
# In practice this should not be a problem
|
||||||
return
|
return
|
||||||
when defined(killers):
|
self.selectiveDepth = max(self.selectiveDepth, ply)
|
||||||
if self.killers[].high() < ply:
|
|
||||||
self.killers[].add([nullMove(), nullMove()])
|
|
||||||
if ply > 0:
|
if ply > 0:
|
||||||
let query = self.transpositionTable[].get(self.board.position.zobristKey, depth.uint8)
|
let query = self.transpositionTable[].get(self.board.position.zobristKey, depth.uint8)
|
||||||
if query.success:
|
if query.success:
|
||||||
|
@ -305,14 +336,14 @@ proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score): Score
|
||||||
var alpha = alpha
|
var alpha = alpha
|
||||||
let
|
let
|
||||||
sideToMove = self.board.position.sideToMove
|
sideToMove = self.board.position.sideToMove
|
||||||
nonSideToMove = sideToMove.opposite()
|
nonSideToMove {.used.} = sideToMove.opposite()
|
||||||
for i, move in moves:
|
for i, move in moves:
|
||||||
if ply == 0 and self.searchMoves.len() > 0 and move notin self.searchMoves:
|
if ply == 0 and self.searchMoves.len() > 0 and move notin self.searchMoves:
|
||||||
continue
|
continue
|
||||||
self.board.doMove(move)
|
self.board.doMove(move)
|
||||||
self.currentMove = move
|
let
|
||||||
self.currentMoveNumber = i
|
extension = self.getSearchExtension(move)
|
||||||
let extension = self.getSearchExtension(move)
|
reduction = self.getReduction(move, depth, ply, i, isPV)
|
||||||
inc(self.nodeCount)
|
inc(self.nodeCount)
|
||||||
# Find the best move for us (worst move
|
# Find the best move for us (worst move
|
||||||
# for our opponent, hence the negative sign)
|
# for our opponent, hence the negative sign)
|
||||||
|
@ -321,30 +352,30 @@ proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score): Score
|
||||||
if i == 0:
|
if i == 0:
|
||||||
# Due to our move ordering scheme, the first move is always the "best", so
|
# 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
|
# search it always at full depth with the full search window
|
||||||
score = -self.search(depth - 1 + extension, ply + 1, -beta, -alpha)
|
score = -self.search(depth - 1 + extension, ply + 1, -beta, -alpha, isPV)
|
||||||
elif extension == 0 and self.shouldReduce(move, depth, i):
|
elif reduction > 0:
|
||||||
# Late Move Reductions: assume our move orderer did a good job,
|
# 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.
|
# 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
|
# If this move turns out to be better than we expected, we'll re-search
|
||||||
# it at full depth
|
# 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 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]
|
# (we don't care about the actual value, so we search in the range [alpha, alpha + 1]
|
||||||
# to increase the number of cutoffs)
|
# to increase the number of cutoffs)
|
||||||
score = -self.search(depth - 1 - reduction, ply + 1, -alpha - 1, -alpha)
|
score = -self.search(depth - 1 - reduction, ply + 1, -alpha - 1, -alpha, isPV=false)
|
||||||
# If the null window search beats alpha, we do a full window reduced search to get a
|
# 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
|
# 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
|
# (but not beta) again, we'll re-search this at full depth later
|
||||||
if score > alpha:
|
if score > alpha:
|
||||||
score = -self.search(depth - 1 - reduction, ply + 1, -beta, -alpha)
|
score = -self.search(depth - 1 - reduction, ply + 1, -beta, -alpha, isPV=false)
|
||||||
else:
|
else:
|
||||||
# Move wasn't reduced, just do a null window search
|
# Move wasn't reduced, just do a null window search
|
||||||
score = -self.search(depth - 1 + extension, ply + 1, -alpha - 1, -alpha)
|
score = -self.search(depth - 1 + extension, ply + 1, -alpha - 1, -alpha, isPV=false)
|
||||||
if i > 0 and score > alpha and score < beta:
|
if i > 0 and score > alpha and score < beta:
|
||||||
# The move failed high (and not low, which would mean it was too good for us and
|
# The position failed high (and not low, which would mean it was too good for us and
|
||||||
# our opponent wouldn't let us play it) in the null window search, search it
|
# our opponent wouldn't let us play it) in the null window search, search it
|
||||||
# again with the full depth and full window
|
# again with the full depth and full window
|
||||||
score = -self.search(depth - 1 + extension, ply + 1, -beta, -alpha)
|
score = -self.search(depth - 1 + extension, ply + 1, -beta, -alpha, isPV)
|
||||||
self.board.unmakeMove()
|
self.board.unmakeMove()
|
||||||
# When a search is cancelled or times out, we need
|
# When a search is cancelled or times out, we need
|
||||||
# to make sure the entire call stack unwinds back
|
# to make sure the entire call stack unwinds back
|
||||||
|
@ -356,7 +387,7 @@ proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score): Score
|
||||||
if not move.isCapture() and self.history[][sideToMove][move.startSquare][move.targetSquare] < highestEval():
|
if not move.isCapture() and self.history[][sideToMove][move.startSquare][move.targetSquare] < highestEval():
|
||||||
# History euristic: keep track of moves that caused a beta cutoff and order
|
# History euristic: keep track of moves that caused a beta cutoff and order
|
||||||
# them early in subsequent searches, as they might be really good later. A
|
# them early in subsequent searches, as they might be really good later. A
|
||||||
# quadratic bonus wrt. depth is usually the bonus that is used (though some
|
# quadratic bonus wrt. depth is usually the value that is used (though some
|
||||||
# engines, namely Stockfish, use a linear bonus. Maybe we can investigate this)
|
# engines, namely Stockfish, use a linear bonus. Maybe we can investigate this)
|
||||||
self.history[][sideToMove][move.startSquare][move.targetSquare] += Score(depth * depth)
|
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
|
# Killer move heuristic: store moves that caused a beta cutoff according to the distance from
|
||||||
|
@ -369,8 +400,17 @@ proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score): Score
|
||||||
alpha = score
|
alpha = score
|
||||||
bestMove = move
|
bestMove = move
|
||||||
if ply == 0:
|
if ply == 0:
|
||||||
self.bestMoveRoot = move
|
self.bestRootScore = score
|
||||||
self.bestRootScore = bestScore
|
if isPV:
|
||||||
|
# This loop is why pvMoves has one extra move.
|
||||||
|
# We can just do ply + 1 and i + 1 without ever
|
||||||
|
# fearing about buffer overflows
|
||||||
|
for i, pv in self.pvMoves[ply + 1]:
|
||||||
|
if pv == nullMove():
|
||||||
|
self.pvMoves[ply][i + 1] = nullMove()
|
||||||
|
break
|
||||||
|
self.pvMoves[ply][i + 1] = pv
|
||||||
|
self.pvMoves[ply][0] = move
|
||||||
# TODO
|
# TODO
|
||||||
# else:
|
# else:
|
||||||
# when defined(noScaleHistory):
|
# when defined(noScaleHistory):
|
||||||
|
@ -397,15 +437,14 @@ proc findBestMove*(self: var SearchManager, timeRemaining, increment: int64, max
|
||||||
## nodes. If searchMoves is provided and is not empty, search will
|
## nodes. If searchMoves is provided and is not empty, search will
|
||||||
## be restricted to the moves in the list. Note that regardless of
|
## be restricted to the moves in the list. Note that regardless of
|
||||||
## any time limitations or explicit cancellations, the search will
|
## any time limitations or explicit cancellations, the search will
|
||||||
## not stop until it has at least cleared depth one
|
## not stop until it has at least cleared depth one. Search depth
|
||||||
|
## is always constrained to at most MAX_DEPTH ply from the root
|
||||||
|
|
||||||
# Apparently negative remaining time is a thing. Welp
|
# Apparently negative remaining time is a thing. Welp
|
||||||
let
|
let
|
||||||
maxSearchTime = max(1, (timeRemaining div 10) + (increment div 2))
|
maxSearchTime = max(1, (timeRemaining div 10) + (increment div 2))
|
||||||
softLimit = maxSearchTime div 3
|
softLimit = maxSearchTime div 3
|
||||||
echo maxSearchTime
|
result = nullMove()
|
||||||
self.bestMoveRoot = nullMove()
|
|
||||||
result = self.bestMoveRoot
|
|
||||||
self.maxNodes = maxNodes
|
self.maxNodes = maxNodes
|
||||||
self.searchMoves = searchMoves
|
self.searchMoves = searchMoves
|
||||||
self.searchStart = getMonoTime()
|
self.searchStart = getMonoTime()
|
||||||
|
@ -416,23 +455,18 @@ proc findBestMove*(self: var SearchManager, timeRemaining, increment: int64, max
|
||||||
maxDepth = 30
|
maxDepth = 30
|
||||||
self.searchFlag[].store(true)
|
self.searchFlag[].store(true)
|
||||||
# Iterative deepening loop
|
# Iterative deepening loop
|
||||||
for i in 1..maxDepth:
|
for i in 1..min(MAX_DEPTH, maxDepth):
|
||||||
# Search the previous best move first
|
self.search(i, 0, lowestEval(), highestEval(), true)
|
||||||
self.previousBestMove = self.bestMoveRoot
|
if self.pvMoves[0][0] != nullMove():
|
||||||
self.search(i, 0, lowestEval(), highestEval())
|
result = self.pvMoves[0][0]
|
||||||
# Since we always search the best move from the
|
if self.shouldStop():
|
||||||
# previous iteration, we can use partial search
|
self.log(i - 1)
|
||||||
# results: the engine will either not have changed
|
break
|
||||||
# its mind, or it will have found an even better move
|
self.log(i)
|
||||||
# in the meantime, which we should obviously use!
|
|
||||||
result = self.bestMoveRoot
|
|
||||||
# Soft time management: don't start a new search iteration
|
# Soft time management: don't start a new search iteration
|
||||||
# if the soft limit has expired, as it is unlikely to complete
|
# if the soft limit has expired, as it is unlikely to complete
|
||||||
# anyway
|
# anyway
|
||||||
if self.shouldStop() or getMonoTime() >= self.softLimit:
|
if getMonoTime() >= self.softLimit:
|
||||||
self.log(i - 1)
|
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
self.log(i)
|
|
||||||
self.searchFlag[].store(false)
|
self.searchFlag[].store(false)
|
||||||
self.stopFlag[].store(false)
|
self.stopFlag[].store(false)
|
||||||
|
|
|
@ -388,6 +388,7 @@ proc commandLoop*: int =
|
||||||
of "skip":
|
of "skip":
|
||||||
board.position.sideToMove = board.position.sideToMove.opposite()
|
board.position.sideToMove = board.position.sideToMove.opposite()
|
||||||
board.position.updateChecksAndPins()
|
board.position.updateChecksAndPins()
|
||||||
|
board.position.hash()
|
||||||
of "go":
|
of "go":
|
||||||
handleGoCommand(board, cmd)
|
handleGoCommand(board, cmd)
|
||||||
of "position", "pos":
|
of "position", "pos":
|
||||||
|
|
|
@ -351,6 +351,10 @@ proc startUCISession* =
|
||||||
session.transpositionTable[] = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
session.transpositionTable[] = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
||||||
session.historyTable = cast[ptr HistoryTable](alloc0(sizeof(HistoryTable)))
|
session.historyTable = cast[ptr HistoryTable](alloc0(sizeof(HistoryTable)))
|
||||||
session.killerMoves = cast[ptr KillersTable](alloc0(sizeof(KillersTable)))
|
session.killerMoves = cast[ptr KillersTable](alloc0(sizeof(KillersTable)))
|
||||||
|
# Initialize killer move array
|
||||||
|
for i in 0..<MAX_DEPTH:
|
||||||
|
for j in 0..<NUM_KILLERS:
|
||||||
|
session.killerMoves[i][j] = nullMove()
|
||||||
# Fun fact, nim doesn't collect the memory of thread vars. Another stupid fucking design pitfall
|
# 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
|
# 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
|
# my fucking words. (for legal purposes THAT IS A JOKE). See https://github.com/nim-lang/Nim/issues/23165
|
||||||
|
@ -384,16 +388,20 @@ proc startUCISession* =
|
||||||
session.position = startpos()
|
session.position = startpos()
|
||||||
session.history = @[]
|
session.history = @[]
|
||||||
of Go:
|
of Go:
|
||||||
when not defined(noScaleHistory):
|
# when not defined(noScaleHistory):
|
||||||
# Scale our history coefficients
|
# Scale our history coefficients
|
||||||
for color in PieceColor.White..PieceColor.Black:
|
for color in PieceColor.White..PieceColor.Black:
|
||||||
for source in Square(0)..Square(63):
|
for source in Square(0)..Square(63):
|
||||||
for target in Square(0)..Square(63):
|
for target in Square(0)..Square(63):
|
||||||
session.historyTable[][color][source][target] = session.historyTable[][color][source][target] div 2
|
session.historyTable[][color][source][target] = session.historyTable[][color][source][target] div 2
|
||||||
|
if searchThread.running:
|
||||||
|
joinThread(searchThread)
|
||||||
createThread(searchThread, bestMove, (session, cmd))
|
createThread(searchThread, bestMove, (session, cmd))
|
||||||
if session.debug:
|
if session.debug:
|
||||||
echo "info string search started"
|
echo "info string search started"
|
||||||
of Stop:
|
of Stop:
|
||||||
|
if not session.searchFlag[].load():
|
||||||
|
continue
|
||||||
session.stopFlag[].store(true)
|
session.stopFlag[].store(true)
|
||||||
joinThread(searchThread)
|
joinThread(searchThread)
|
||||||
if session.debug:
|
if session.debug:
|
||||||
|
|
|
@ -55,7 +55,8 @@ def main(args: Namespace) -> int:
|
||||||
stop = timeit.default_timer()
|
stop = timeit.default_timer()
|
||||||
pool.shutdown(cancel_futures=True)
|
pool.shutdown(cancel_futures=True)
|
||||||
print(f"\r[S] Interrupted\033[K")
|
print(f"\r[S] Interrupted\033[K")
|
||||||
print(f"[S] Ran {i} tests at depth {args.ply} in {stop - start:.2f} seconds ({len(successful)} successful, {len(failed)} failed)")
|
total = len(successful) + len(failed)
|
||||||
|
print(f"[S] Ran {total} tests at depth {args.ply} in {stop - start:.2f} seconds ({len(successful)} successful, {len(failed)} failed)")
|
||||||
if failed and args.show_failures:
|
if failed and args.show_failures:
|
||||||
print("[S] The following FENs failed to pass the test:\n\t", end="")
|
print("[S] The following FENs failed to pass the test:\n\t", end="")
|
||||||
print("\n\t".join(failed))
|
print("\n\t".join(failed))
|
||||||
|
|
Loading…
Reference in New Issue