Initial work on killer moves. Add soft tm (gains 41.8 +/- 22.0)
This commit is contained in:
parent
651acd26ed
commit
245a5d75e8
|
@ -1,6 +1,6 @@
|
|||
--cc:clang
|
||||
-o:"bin/nimfish"
|
||||
-d:danger
|
||||
-d:debug
|
||||
--passL:"-flto -lmimalloc"
|
||||
--passC:"-flto -march=native -mtune=native"
|
||||
-d:useMalloc
|
||||
|
|
|
@ -30,6 +30,7 @@ import threading/smartptrs
|
|||
|
||||
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
|
||||
## A simple state storage
|
||||
## for our search
|
||||
|
@ -46,15 +47,15 @@ type
|
|||
searchMoves: seq[Move]
|
||||
previousBestMove: Move
|
||||
transpositionTable: SharedPtr[TTable]
|
||||
# Storage for our history euristic
|
||||
history: SharedPtr[HistoryTable]
|
||||
killers: SharedPtr[KillersTable]
|
||||
|
||||
|
||||
proc newSearchManager*(board: Chessboard, transpositions: SharedPtr[TTable], stopFlag, searchFlag: SharedPtr[Atomic[bool]],
|
||||
history: SharedPtr[HistoryTable]): SearchManager =
|
||||
history: SharedPtr[HistoryTable], killers: SharedPtr[KillersTable]): SearchManager =
|
||||
new(result)
|
||||
result = SearchManager(board: board, bestMoveRoot: nullMove(), transpositionTable: transpositions, stopFlag: stopFlag,
|
||||
searchFlag: searchFlag, history: history)
|
||||
searchFlag: searchFlag, history: history, killers: killers)
|
||||
|
||||
|
||||
proc isSearching*(self: SearchManager): bool =
|
||||
|
@ -71,19 +72,24 @@ proc stop*(self: SearchManager) =
|
|||
|
||||
|
||||
proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
||||
## Returns an estimated static score for the move
|
||||
## Returns an estimated static score for the move used
|
||||
## during move ordering
|
||||
result = Score(0)
|
||||
let
|
||||
sideToMove = self.board.position.sideToMove
|
||||
nonSideToMove = sideToMove.opposite()
|
||||
|
||||
if self.previousBestMove != nullMove() and move == self.previousBestMove:
|
||||
return highestEval() + 1
|
||||
|
||||
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
|
||||
|
||||
if not move.isCapture():
|
||||
# History euristic bonus
|
||||
result += self.history[][sideToMove][move.startSquare][move.targetSquare]
|
||||
|
||||
if move.isCapture():
|
||||
# Implementation of MVVLVA: Most Valuable Victim Least Valuable Attacker
|
||||
# We prioritize moves that capture the most valuable pieces, and as a
|
||||
|
@ -91,6 +97,7 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
|||
# is why we multiply the score of the captured piece by 10, to give
|
||||
# it priority)
|
||||
result += 10 * self.board.position.getPieceScore(move.targetSquare) - self.board.position.getPieceScore(move.startSquare)
|
||||
|
||||
if move.isPromotion():
|
||||
# Promotions are a good idea to search first
|
||||
var piece: Piece
|
||||
|
@ -106,6 +113,7 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
|||
else:
|
||||
discard # Unreachable
|
||||
result += self.board.position.getPieceScore(piece, move.targetSquare)
|
||||
|
||||
if self.board.position.getPawnAttacks(move.targetSquare, nonSideToMove) != 0:
|
||||
# Moving on a square attacked by an enemy pawn is _usually_ a very bad
|
||||
# idea. Assume the piece is lost and give a malus based on the fact that
|
||||
|
@ -342,8 +350,8 @@ proc findBestMove*(self: SearchManager, timeRemaining, increment: int64, maxDept
|
|||
|
||||
# Apparently negative remaining time is a thing. Welp
|
||||
let
|
||||
maxSearchTime = max(1, (timeRemaining div 20) + (increment div 2))
|
||||
softLimit = max(1, (timeRemaining div 30) + (increment div 2))
|
||||
maxSearchTime = max(1, (timeRemaining div 10) + (increment div 2))
|
||||
softLimit = max(1, maxSearchTime div 3)
|
||||
self.bestMoveRoot = nullMove()
|
||||
result = self.bestMoveRoot
|
||||
self.maxNodes = maxNodes
|
||||
|
@ -357,6 +365,8 @@ 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())
|
||||
|
@ -369,17 +379,10 @@ proc findBestMove*(self: SearchManager, timeRemaining, increment: int64, maxDept
|
|||
# Soft time management: don't start a new search iteration
|
||||
# if the soft limit has expired, as it is unlikely to complete
|
||||
# anyway
|
||||
when defined(softTM):
|
||||
if self.shouldStop() or getMonoTime() >= self.softLimit:
|
||||
self.log(i - 1)
|
||||
break
|
||||
else:
|
||||
self.log(i)
|
||||
if self.shouldStop() or getMonoTime() >= self.softLimit:
|
||||
self.log(i - 1)
|
||||
break
|
||||
else:
|
||||
if self.shouldStop():
|
||||
self.log(i - 1)
|
||||
break
|
||||
else:
|
||||
self.log(i)
|
||||
self.log(i)
|
||||
self.searchFlag[].store(false)
|
||||
self.stopFlag[].store(false)
|
||||
|
|
|
@ -31,13 +31,23 @@ type
|
|||
UCISession = object
|
||||
## A UCI session
|
||||
debug: bool
|
||||
# Previously reached positions
|
||||
history: seq[Position]
|
||||
# The current position
|
||||
position: Position
|
||||
# Atomic boolean flag to interrupt the search
|
||||
stopFlag: SharedPtr[Atomic[bool]]
|
||||
# Atomic search flag used to know whether a search
|
||||
# is in progress
|
||||
searchFlag: SharedPtr[Atomic[bool]]
|
||||
# Size of the transposition table (in megabytes)
|
||||
hashTableSize: uint64
|
||||
# The transposition table
|
||||
transpositionTable: SharedPtr[TTable]
|
||||
# Storage for our history heuristic
|
||||
historyTable: SharedPtr[HistoryTable]
|
||||
# Storage for our killer move heuristic
|
||||
killerMoves: SharedPtr[KillersTable]
|
||||
|
||||
UCICommandType = enum
|
||||
## A UCI command type enumeration
|
||||
|
@ -305,14 +315,15 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
|
|||
## Finds the best move in the current position
|
||||
setControlCHook(proc () {.noconv.} = quit(0))
|
||||
|
||||
# Yes yes nim sure this isn't gcsafe. Now stfu and spawn a thread
|
||||
{.cast(gcsafe).}:
|
||||
# Yes yes nim sure this isn't gcsafe. Now stfu and spawn a thread
|
||||
var session = args.session
|
||||
var board = newChessboard()
|
||||
board.position = session.position
|
||||
board.positions = session.history
|
||||
let command = args.command
|
||||
var searcher = newSearchManager(board, session.transpositionTable, session.stopFlag, session.searchFlag, session.historyTable)
|
||||
var searcher = newSearchManager(board, session.transpositionTable, session.stopFlag,
|
||||
session.searchFlag, session.historyTable, session.killerMoves)
|
||||
var
|
||||
timeRemaining = (if session.position.sideToMove == White: command.wtime else: command.btime)
|
||||
increment = (if session.position.sideToMove == White: command.winc else: command.binc)
|
||||
|
@ -339,6 +350,7 @@ proc startUCISession* =
|
|||
session.searchFlag = newSharedPtr(Atomic[bool])
|
||||
session.transpositionTable[] = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
||||
session.historyTable = newSharedPtr(HistoryTable)
|
||||
session.killerMoves = newSharedPtr(KillersTable)
|
||||
session.stopFlag[].store(false)
|
||||
# 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
|
||||
|
|
Loading…
Reference in New Issue