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