Massive eval improvement. Perform search in a separate thread. Implement more UCI commands
This commit is contained in:
parent
a94d9ae1f0
commit
40b47053c1
|
@ -3,3 +3,4 @@
|
||||||
-d:danger
|
-d:danger
|
||||||
--passL:"-flto"
|
--passL:"-flto"
|
||||||
--passC:"-Ofast -flto -march=native -mtune=native"
|
--passC:"-Ofast -flto -march=native -mtune=native"
|
||||||
|
--threads:on
|
|
@ -222,7 +222,7 @@ proc removePiece*(self: Chessboard, square: Square) =
|
||||||
## Removes a piece from the board, updating necessary
|
## Removes a piece from the board, updating necessary
|
||||||
## metadata
|
## metadata
|
||||||
when not defined(danger):
|
when not defined(danger):
|
||||||
let Piece = self.getPiece(square)
|
let piece = self.getPiece(square)
|
||||||
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
doAssert piece.kind != Empty and piece.color != None, self.toFEN()
|
||||||
self.removePieceFromBitboard(square)
|
self.removePieceFromBitboard(square)
|
||||||
self.grid[square] = nullPiece()
|
self.grid[square] = nullPiece()
|
||||||
|
|
|
@ -16,7 +16,161 @@
|
||||||
import board
|
import board
|
||||||
|
|
||||||
type
|
type
|
||||||
Score* = int16
|
Score* = int32
|
||||||
|
|
||||||
|
|
||||||
|
# Stolen from https://www.chessprogramming.org/PeSTO's_Evaluation_Function
|
||||||
|
const
|
||||||
|
PAWN_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
98, 134, 61, 95, 68, 126, 34, -11,
|
||||||
|
-6, 7, 26, 31, 65, 56, 25, -20,
|
||||||
|
-14, 13, 6, 21, 23, 12, 17, -23,
|
||||||
|
-27, -2, -5, 12, 17, 6, 10, -25,
|
||||||
|
-26, -4, -4, -10, 3, 3, 33, -12,
|
||||||
|
-35, -1, -20, -23, -15, 24, 38, -22,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
]
|
||||||
|
|
||||||
|
PAWN_ENDGAME_SCORES: array[64, Score] = [
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
178, 173, 158, 134, 147, 132, 165, 187,
|
||||||
|
94, 100, 85, 67, 56, 53, 82, 84,
|
||||||
|
32, 24, 13, 5, -2, 4, 17, 17,
|
||||||
|
13, 9, -3, -7, -7, -8, 3, -1,
|
||||||
|
4, 7, -6, 1, 0, -5, -1, -8,
|
||||||
|
13, 8, 8, 10, 13, 0, 2, -7,
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
|
]
|
||||||
|
|
||||||
|
KNIGHT_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||||
|
-167, -89, -34, -49, 61, -97, -15, -107,
|
||||||
|
-73, -41, 72, 36, 23, 62, 7, -17,
|
||||||
|
-47, 60, 37, 65, 84, 129, 73, 44,
|
||||||
|
-9, 17, 19, 53, 37, 69, 18, 22,
|
||||||
|
-13, 4, 16, 13, 28, 19, 21, -8,
|
||||||
|
-23, -9, 12, 10, 19, 17, 25, -16,
|
||||||
|
-29, -53, -12, -3, -1, 18, -14, -19,
|
||||||
|
-105, -21, -58, -33, -17, -28, -19, -23,
|
||||||
|
]
|
||||||
|
|
||||||
|
KNIGHT_ENDGAME_SCORES: array[64, Score] = [
|
||||||
|
-58, -38, -13, -28, -31, -27, -63, -99,
|
||||||
|
-25, -8, -25, -2, -9, -25, -24, -52,
|
||||||
|
-24, -20, 10, 9, -1, -9, -19, -41,
|
||||||
|
-17, 3, 22, 22, 22, 11, 8, -18,
|
||||||
|
-18, -6, 16, 25, 16, 17, 4, -18,
|
||||||
|
-23, -3, -1, 15, 10, -3, -20, -22,
|
||||||
|
-42, -20, -10, -5, -2, -20, -23, -44,
|
||||||
|
-29, -51, -23, -15, -22, -18, -50, -64,
|
||||||
|
]
|
||||||
|
|
||||||
|
BISHOP_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||||
|
-29, 4, -82, -37, -25, -42, 7, -8,
|
||||||
|
-26, 16, -18, -13, 30, 59, 18, -47,
|
||||||
|
-16, 37, 43, 40, 35, 50, 37, -2,
|
||||||
|
-4, 5, 19, 50, 37, 37, 7, -2,
|
||||||
|
-6, 13, 13, 26, 34, 12, 10, 4,
|
||||||
|
0, 15, 15, 15, 14, 27, 18, 10,
|
||||||
|
4, 15, 16, 0, 7, 21, 33, 1,
|
||||||
|
-33, -3, -14, -21, -13, -12, -39, -21,
|
||||||
|
]
|
||||||
|
|
||||||
|
BISHOP_ENDGAME_SCORES: array[64, Score] = [
|
||||||
|
-14, -21, -11, -8, -7, -9, -17, -24,
|
||||||
|
-8, -4, 7, -12, -3, -13, -4, -14,
|
||||||
|
2, -8, 0, -1, -2, 6, 0, 4,
|
||||||
|
-3, 9, 12, 9, 14, 10, 3, 2,
|
||||||
|
-6, 3, 13, 19, 7, 10, -3, -9,
|
||||||
|
-12, -3, 8, 10, 13, 3, -7, -15,
|
||||||
|
-14, -18, -7, -1, 4, -9, -15, -27,
|
||||||
|
-23, -9, -23, -5, -9, -16, -5, -17,
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOK_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||||
|
32, 42, 32, 51, 63, 9, 31, 43,
|
||||||
|
27, 32, 58, 62, 80, 67, 26, 44,
|
||||||
|
-5, 19, 26, 36, 17, 45, 61, 16,
|
||||||
|
-24, -11, 7, 26, 24, 35, -8, -20,
|
||||||
|
-36, -26, -12, -1, 9, -7, 6, -23,
|
||||||
|
-45, -25, -16, -17, 3, 0, -5, -33,
|
||||||
|
-44, -16, -20, -9, -1, 11, -6, -71,
|
||||||
|
-19, -13, 1, 17, 16, 7, -37, -26,
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOK_ENDGAME_SCORES: array[64, Score] = [
|
||||||
|
13, 10, 18, 15, 12, 12, 8, 5,
|
||||||
|
11, 13, 13, 11, -3, 3, 8, 3,
|
||||||
|
7, 7, 7, 5, 4, -3, -5, -3,
|
||||||
|
4, 3, 13, 1, 2, 1, -1, 2,
|
||||||
|
3, 5, 8, 4, -5, -6, -8, -11,
|
||||||
|
-4, 0, -5, -1, -7, -12, -8, -16,
|
||||||
|
-6, -6, 0, 2, -9, -9, -11, -3,
|
||||||
|
-9, 2, 3, -1, -5, -13, 4, -20,
|
||||||
|
]
|
||||||
|
|
||||||
|
QUEEN_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||||
|
-28, 0, 29, 12, 59, 44, 43, 45,
|
||||||
|
-24, -39, -5, 1, -16, 57, 28, 54,
|
||||||
|
-13, -17, 7, 8, 29, 56, 47, 57,
|
||||||
|
-27, -27, -16, -16, -1, 17, -2, 1,
|
||||||
|
-9, -26, -9, -10, -2, -4, 3, -3,
|
||||||
|
-14, 2, -11, -2, -5, 2, 14, 5,
|
||||||
|
-35, -8, 11, 2, 8, 15, -3, 1,
|
||||||
|
-1, -18, -9, 10, -15, -25, -31, -50,
|
||||||
|
]
|
||||||
|
|
||||||
|
QUEEN_ENDGAME_SCORES: array[64, Score] = [
|
||||||
|
-9, 22, 22, 27, 27, 19, 10, 20,
|
||||||
|
-17, 20, 32, 41, 58, 25, 30, 0,
|
||||||
|
-20, 6, 9, 49, 47, 35, 19, 9,
|
||||||
|
3, 22, 24, 45, 57, 40, 57, 36,
|
||||||
|
-18, 28, 19, 47, 31, 34, 39, 23,
|
||||||
|
-16, -27, 15, 6, 9, 17, 10, 5,
|
||||||
|
-22, -23, -30, -16, -16, -23, -36, -32,
|
||||||
|
-33, -28, -22, -43, -5, -32, -20, -41,
|
||||||
|
]
|
||||||
|
|
||||||
|
KING_MIDDLEGAME_SCORES: array[64, Score] = [
|
||||||
|
-65, 23, 16, -15, -56, -34, 2, 13,
|
||||||
|
29, -1, -20, -7, -8, -4, -38, -29,
|
||||||
|
-9, 24, 2, -16, -20, 6, 22, -22,
|
||||||
|
-17, -20, -12, -27, -30, -25, -14, -36,
|
||||||
|
-49, -1, -27, -39, -46, -44, -33, -51,
|
||||||
|
-14, -14, -22, -46, -44, -30, -15, -27,
|
||||||
|
1, 7, -8, -64, -43, -16, 9, 8,
|
||||||
|
-15, 36, 12, -54, 8, -28, 24, 14,
|
||||||
|
]
|
||||||
|
|
||||||
|
KING_ENDGAME_SCORES: array[64, Score] = [
|
||||||
|
-74, -35, -18, -18, -11, 15, 4, -17,
|
||||||
|
-12, 17, 14, 17, 17, 38, 23, 11,
|
||||||
|
10, 17, 23, 15, 20, 45, 44, 13,
|
||||||
|
-8, 22, 24, 27, 26, 33, 26, 3,
|
||||||
|
-18, -4, 21, 24, 27, 23, 9, -11,
|
||||||
|
-19, -3, 11, 21, 23, 16, 7, -9,
|
||||||
|
-27, -11, 4, 13, 14, 4, -5, -17,
|
||||||
|
-53, -34, -21, -11, -28, -14, -24, -43
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEGAME_TABLES: array[6, array[64, Score]] = [
|
||||||
|
BISHOP_MIDDLEGAME_SCORES,
|
||||||
|
KING_MIDDLEGAME_SCORES,
|
||||||
|
KNIGHT_MIDDLEGAME_SCORES,
|
||||||
|
PAWN_MIDDLEGAME_SCORES,
|
||||||
|
QUEEN_MIDDLEGAME_SCORES,
|
||||||
|
ROOK_MIDDLEGAME_SCORES
|
||||||
|
]
|
||||||
|
|
||||||
|
ENDGAME_TABLES: array[6, array[64, Score]] = [
|
||||||
|
BISHOP_ENDGAME_SCORES,
|
||||||
|
KING_ENDGAME_SCORES,
|
||||||
|
KNIGHT_ENDGAME_SCORES,
|
||||||
|
PAWN_ENDGAME_SCORES,
|
||||||
|
QUEEN_ENDGAME_SCORES,
|
||||||
|
ROOK_ENDGAME_SCORES
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc getPieceValue(kind: PieceKind): Score =
|
proc getPieceValue(kind: PieceKind): Score =
|
||||||
|
@ -36,12 +190,68 @@ proc getPieceValue(kind: PieceKind): Score =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
proc getPieceScore(board: Chessboard, square: Square): Score =
|
proc getPieceScore*(board: Chessboard, square: Square): Score =
|
||||||
## Returns the value of the piece located at
|
## Returns the value of the piece located at
|
||||||
## the given square
|
## the given square
|
||||||
return board.getPiece(square).kind.getPieceValue()
|
return board.getPiece(square).kind.getPieceValue()
|
||||||
|
|
||||||
|
|
||||||
|
proc getEstimatedMoveScore*(board: Chessboard, move: Move): Score =
|
||||||
|
## Returns an estimated static score for the move
|
||||||
|
result = Score(0)
|
||||||
|
if move.isCapture():
|
||||||
|
# Implementation of MVVLVA: Most Valuable Victim Least Valuable Attacker
|
||||||
|
# We prioritize moves that capture the most valuable pieces, and as a
|
||||||
|
# second goal we want to use our least valuable pieces to do so (this
|
||||||
|
# is why we multiply the piece of the captured score by 100, to give
|
||||||
|
# it priority)
|
||||||
|
result = 100 * board.getPieceScore(move.targetSquare) -
|
||||||
|
board.getPieceScore(move.startSquare)
|
||||||
|
|
||||||
|
|
||||||
|
proc getGamePhase(board: Chessboard): int =
|
||||||
|
## Computes the game phase according to
|
||||||
|
## how many pieces are left on the board
|
||||||
|
result = 0
|
||||||
|
for sq in board.getOccupancy():
|
||||||
|
case board.getPiece(sq).kind:
|
||||||
|
of Bishop, Knight:
|
||||||
|
inc(result)
|
||||||
|
of Queen:
|
||||||
|
inc(result, 4)
|
||||||
|
of Rook:
|
||||||
|
inc(result, 2)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
# Caps the value in case of early
|
||||||
|
# promotions
|
||||||
|
result = min(24, result)
|
||||||
|
|
||||||
|
|
||||||
|
proc evaluatePiecePositions(board: ChessBoard): Score =
|
||||||
|
## Returns the evaluation of the current
|
||||||
|
## material's position relative to white
|
||||||
|
let
|
||||||
|
middleGamePhase = board.getGamePhase()
|
||||||
|
endGamePhase = 24 - middleGamePhase
|
||||||
|
var
|
||||||
|
# White, Black
|
||||||
|
middleGameScores: array[2, Score] = [0, 0]
|
||||||
|
endGameScores: array[2, Score] = [0, 0]
|
||||||
|
|
||||||
|
for sq in board.getOccupancy():
|
||||||
|
let piece = board.getPiece(sq)
|
||||||
|
middleGameScores[piece.color.int] += MIDDLEGAME_TABLES[piece.kind.int][sq.int]
|
||||||
|
endGameScores[piece.color.int] += ENDGAME_TABLES[piece.kind.int][sq.int]
|
||||||
|
|
||||||
|
let
|
||||||
|
sideToMove = board.position.sideToMove
|
||||||
|
nonSideToMove = sideToMove.opposite()
|
||||||
|
middleGameScore = middleGameScores[sideToMove.int] - middleGameScores[nonSideToMove.int]
|
||||||
|
endGameScore = endGameScores[sideToMove.int] - endGameScores[nonSideToMove.int]
|
||||||
|
|
||||||
|
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
||||||
|
|
||||||
|
|
||||||
proc evaluateMaterial(board: ChessBoard): Score =
|
proc evaluateMaterial(board: ChessBoard): Score =
|
||||||
## Returns the material evaluation of the
|
## Returns the material evaluation of the
|
||||||
|
@ -65,5 +275,5 @@ proc evaluateMaterial(board: ChessBoard): Score =
|
||||||
proc evaluate*(board: Chessboard): Score =
|
proc evaluate*(board: Chessboard): Score =
|
||||||
## Evaluates the current position
|
## Evaluates the current position
|
||||||
|
|
||||||
|
|
||||||
result = board.evaluateMaterial()
|
result = board.evaluateMaterial()
|
||||||
|
result += board.evaluatePiecePositions()
|
|
@ -40,11 +40,12 @@ type
|
||||||
targetSquare*: Square
|
targetSquare*: Square
|
||||||
flags*: uint16
|
flags*: uint16
|
||||||
|
|
||||||
MoveList* = object
|
MoveList* = ref object
|
||||||
## A list of moves
|
## A list of moves
|
||||||
data: array[218, Move]
|
data*: array[218, Move]
|
||||||
len: int8
|
len: int8
|
||||||
|
|
||||||
|
|
||||||
func `[]`*(self: MoveList, i: SomeInteger): Move =
|
func `[]`*(self: MoveList, i: SomeInteger): Move =
|
||||||
when not defined(danger):
|
when not defined(danger):
|
||||||
if i >= self.len:
|
if i >= self.len:
|
||||||
|
|
|
@ -18,12 +18,16 @@ import movegen
|
||||||
import eval
|
import eval
|
||||||
|
|
||||||
|
|
||||||
|
import std/times
|
||||||
import std/atomics
|
import std/atomics
|
||||||
|
import std/algorithm
|
||||||
|
import std/monotimes
|
||||||
|
import std/strformat
|
||||||
|
|
||||||
|
|
||||||
func lowestEval*: Score {.inline.} = Score(-32000'i16)
|
func lowestEval*: Score {.inline.} = Score(int32.low() + 1_000_000)
|
||||||
func highestEval*: Score {.inline.} = Score(32000'i16)
|
func highestEval*: Score {.inline.} = Score(int32.high() - 1_000_000)
|
||||||
func mateScore*: Score {.inline.} = lowestEval() - Score(1)
|
func mateScore*: Score {.inline.} = lowestEval()
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -31,20 +35,72 @@ type
|
||||||
## A simple state storage
|
## A simple state storage
|
||||||
## for our search
|
## for our search
|
||||||
stopFlag*: Atomic[bool] # Can be used to cancel the search from another thread
|
stopFlag*: Atomic[bool] # Can be used to cancel the search from another thread
|
||||||
bestMove*: Move
|
board: Chessboard
|
||||||
|
bestMoveRoot: Move
|
||||||
|
searchStart: MonoTime
|
||||||
|
searchDeadline: MonoTime
|
||||||
|
nodeCount: uint64
|
||||||
|
maxNodes: uint64
|
||||||
|
searchMoves: seq[Move]
|
||||||
|
|
||||||
|
|
||||||
proc search*(self: SearchManager, board: Chessboard, depth, ply: int, alpha, beta: Score): Score {.discardable.} =
|
proc newSearchManager*(board: Chessboard): SearchManager =
|
||||||
## Simple negamax search with alpha-beta pruning
|
new(result)
|
||||||
if self.stopFlag.load():
|
result.board = board
|
||||||
|
result.bestMoveRoot = nullMove()
|
||||||
|
result.searchMoves = @[]
|
||||||
|
|
||||||
|
|
||||||
|
proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
||||||
|
## Reorders the list of moves in-place, trying
|
||||||
|
## to place the best ones first
|
||||||
|
|
||||||
|
proc orderer(a, b: Move): int {.closure.} =
|
||||||
|
return cmp(self.board.getEstimatedMoveScore(a), self.board.getEstimatedMoveScore(b))
|
||||||
|
|
||||||
|
moves.data.sort(orderer, SortOrder.Descending)
|
||||||
|
|
||||||
|
|
||||||
|
proc timedOut(self: SearchManager): bool = getMonoTime() >= self.searchDeadline
|
||||||
|
proc cancelled(self: SearchManager): bool = self.stopFlag.load()
|
||||||
|
|
||||||
|
|
||||||
|
proc log(self: SearchManager, depth: int) =
|
||||||
|
let
|
||||||
|
elapsed = getMonoTime() - self.searchStart
|
||||||
|
elapsedMsec = elapsed.inMilliseconds.uint64
|
||||||
|
nps = 1000 * (self.nodeCount div max(elapsedMsec, 1))
|
||||||
|
var logMsg = &"info depth {depth} time {elapsedMsec} nodes {self.nodeCount} nps {nps}"
|
||||||
|
if self.bestMoveRoot != nullMove():
|
||||||
|
logMsg &= &" pv {self.bestMoveRoot.toAlgebraic()}"
|
||||||
|
echo logMsg
|
||||||
|
|
||||||
|
|
||||||
|
proc shouldStop(self: SearchManager): bool =
|
||||||
|
## Returns whether searching should
|
||||||
|
## stop
|
||||||
|
if self.cancelled():
|
||||||
# Search has been cancelled!
|
# Search has been cancelled!
|
||||||
|
return true
|
||||||
|
if self.timedOut():
|
||||||
|
# We ran out of time!
|
||||||
|
return true
|
||||||
|
if self.maxNodes > 0 and self.nodeCount >= self.maxNodes:
|
||||||
|
# Ran out of nodes
|
||||||
|
return true
|
||||||
|
|
||||||
|
|
||||||
|
proc search*(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.discardable.} =
|
||||||
|
## Simple negamax search with alpha-beta pruning
|
||||||
|
if self.shouldStop():
|
||||||
return
|
return
|
||||||
if depth == 0:
|
if depth == 0:
|
||||||
return board.evaluate()
|
return self.board.evaluate()
|
||||||
var moves = MoveList()
|
var moves = MoveList()
|
||||||
board.generateMoves(moves)
|
self.board.generateMoves(moves)
|
||||||
|
self.reorderMoves(moves)
|
||||||
if moves.len() == 0:
|
if moves.len() == 0:
|
||||||
if board.inCheck():
|
if self.board.inCheck():
|
||||||
# Checkmate! We add the current ply
|
# Checkmate! We add the current ply
|
||||||
# because mating in 3 is better than
|
# because mating in 3 is better than
|
||||||
# mating in 5 (and conversely being
|
# mating in 5 (and conversely being
|
||||||
|
@ -55,13 +111,20 @@ proc search*(self: SearchManager, board: Chessboard, depth, ply: int, alpha, bet
|
||||||
return Score(0)
|
return Score(0)
|
||||||
var bestScore = lowestEval()
|
var bestScore = lowestEval()
|
||||||
var alpha = alpha
|
var alpha = alpha
|
||||||
for move in moves:
|
for i, move in moves:
|
||||||
board.makeMove(move)
|
if ply == 0 and self.searchMoves.len() > 0 and move notin self.searchMoves:
|
||||||
|
continue
|
||||||
|
self.board.makeMove(move)
|
||||||
|
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)
|
||||||
let eval = -self.search(board, depth - 1, ply + 1, -beta, -alpha)
|
let eval = -self.search(depth - 1, ply + 1, -beta, -alpha)
|
||||||
board.unmakeMove()
|
self.board.unmakeMove()
|
||||||
|
# When a search is cancelled or times out, we need
|
||||||
|
# to make sure the entire call stack unwindss back
|
||||||
|
# to the root move. This is why the check is duplicated
|
||||||
|
if self.shouldStop():
|
||||||
|
return
|
||||||
bestScore = max(eval, bestScore)
|
bestScore = max(eval, bestScore)
|
||||||
if eval >= beta:
|
if eval >= beta:
|
||||||
# This move was too good for us, opponent will not search it
|
# This move was too good for us, opponent will not search it
|
||||||
|
@ -69,6 +132,32 @@ proc search*(self: SearchManager, board: Chessboard, depth, ply: int, alpha, bet
|
||||||
if eval > alpha:
|
if eval > alpha:
|
||||||
alpha = eval
|
alpha = eval
|
||||||
if ply == 0:
|
if ply == 0:
|
||||||
self.bestMove = move
|
self.bestMoveRoot = move
|
||||||
|
|
||||||
return bestScore
|
return bestScore
|
||||||
|
|
||||||
|
|
||||||
|
proc findBestMove*(self: SearchManager, maxSearchTime, maxDepth: int, maxNodes: uint64, searchMoves: seq[Move]): Move =
|
||||||
|
## Finds the best move in the current position
|
||||||
|
## and returns it, limiting search time to
|
||||||
|
## maxSearchTime milliseconds and to maxDepth
|
||||||
|
## ply (if maxDepth is -1, a reasonable limit
|
||||||
|
## is picked). If maxNodes is supplied and is nonzero,
|
||||||
|
## search will stop once it has analyzed the given number
|
||||||
|
## of nodes. If searchMoves is provided and is not empty,
|
||||||
|
## search will be restricted to the moves in the list
|
||||||
|
self.bestMoveRoot = nullMove()
|
||||||
|
result = self.bestMoveRoot
|
||||||
|
self.maxNodes = maxNodes
|
||||||
|
self.searchMoves = searchMoves
|
||||||
|
self.searchStart = getMonoTime()
|
||||||
|
self.searchDeadline = self.searchStart + initDuration(milliseconds=maxSearchTime)
|
||||||
|
var maxDepth = maxDepth
|
||||||
|
if maxDepth == -1:
|
||||||
|
maxDepth = 30
|
||||||
|
# Iterative deepening loop
|
||||||
|
for i in 1..maxDepth:
|
||||||
|
self.search(i, 0, lowestEval(), highestEval())
|
||||||
|
self.log(i)
|
||||||
|
if self.shouldStop():
|
||||||
|
break
|
||||||
|
result = self.bestMoveRoot
|
||||||
|
|
|
@ -15,10 +15,7 @@
|
||||||
## Implementation of a UCI compatible server
|
## Implementation of a UCI compatible server
|
||||||
import std/strutils
|
import std/strutils
|
||||||
import std/strformat
|
import std/strformat
|
||||||
import std/random
|
import std/atomics
|
||||||
|
|
||||||
|
|
||||||
randomize()
|
|
||||||
|
|
||||||
|
|
||||||
import board
|
import board
|
||||||
|
@ -60,44 +57,13 @@ type
|
||||||
movesToGo: int
|
movesToGo: int
|
||||||
depth: int
|
depth: int
|
||||||
moveTime: int
|
moveTime: int
|
||||||
|
nodes: uint64
|
||||||
|
searchmoves: seq[Move]
|
||||||
else:
|
else:
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
proc handleUCIGoCommand(session: UCISession, command: seq[string]): UCICommand =
|
proc parseUCIMove(session: UCISession, move: string): tuple[move: Move, command: UCICommand] =
|
||||||
result = UCICommand(kind: Go)
|
|
||||||
result.wtime = -1
|
|
||||||
result.btime = -1
|
|
||||||
result.winc = 0
|
|
||||||
result.binc = 0
|
|
||||||
result.movesToGo = 0
|
|
||||||
result.depth = 0
|
|
||||||
result.moveTime = -1
|
|
||||||
var
|
|
||||||
current = 0
|
|
||||||
while current < command.len():
|
|
||||||
case command[current]:
|
|
||||||
of "infinite":
|
|
||||||
discard
|
|
||||||
of "wtime":
|
|
||||||
result.wtime = command[current + 1].parseInt()
|
|
||||||
of "btime":
|
|
||||||
result.btime = command[current + 1].parseInt()
|
|
||||||
of "winc":
|
|
||||||
result.winc = command[current + 1].parseInt()
|
|
||||||
of "binc":
|
|
||||||
result.binc = command[current + 1].parseInt()
|
|
||||||
of "result.movestogo":
|
|
||||||
result.movesToGo = command[current + 1].parseInt()
|
|
||||||
of "depth":
|
|
||||||
result.depth = command[current + 1].parseInt()
|
|
||||||
of "movetime":
|
|
||||||
result.moveTime = command[current + 1].parseInt()
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
proc handleUCIMove(session: UCISession, move: string): tuple[move: Move, cmd: UCICommand] {.discardable.} =
|
|
||||||
var
|
var
|
||||||
startSquare: Square
|
startSquare: Square
|
||||||
targetSquare: Square
|
targetSquare: Square
|
||||||
|
@ -141,10 +107,68 @@ proc handleUCIMove(session: UCISession, move: string): tuple[move: Move, cmd: UC
|
||||||
flags.add(Castle)
|
flags.add(Castle)
|
||||||
elif targetSquare == session.board.position.enPassantSquare:
|
elif targetSquare == session.board.position.enPassantSquare:
|
||||||
flags.add(EnPassant)
|
flags.add(EnPassant)
|
||||||
let move = createMove(startSquare, targetSquare, flags)
|
result.move = createMove(startSquare, targetSquare, flags)
|
||||||
|
|
||||||
|
|
||||||
|
proc handleUCIMove(session: UCISession, move: string): tuple[move: Move, cmd: UCICommand] {.discardable.} =
|
||||||
if session.debug:
|
if session.debug:
|
||||||
echo &"info string making move {move}"
|
echo &"info string making move {move}"
|
||||||
result.move = session.board.makeMove(move)
|
let
|
||||||
|
r = session.parseUCIMove(move)
|
||||||
|
move = r.move
|
||||||
|
command = r.command
|
||||||
|
if move == nullMove():
|
||||||
|
return (move, command)
|
||||||
|
else:
|
||||||
|
result.move = session.board.makeMove(move)
|
||||||
|
|
||||||
|
|
||||||
|
proc handleUCIGoCommand(session: UCISession, command: seq[string]): UCICommand =
|
||||||
|
result = UCICommand(kind: Go)
|
||||||
|
result.wtime = 0
|
||||||
|
result.btime = 0
|
||||||
|
result.winc = 0
|
||||||
|
result.binc = 0
|
||||||
|
result.movesToGo = 0
|
||||||
|
result.depth = -1
|
||||||
|
result.moveTime = -1
|
||||||
|
result.nodes = 0
|
||||||
|
var
|
||||||
|
current = 1 # Skip the "go"
|
||||||
|
while current < command.len():
|
||||||
|
let flag = command[current]
|
||||||
|
inc(current)
|
||||||
|
case flag:
|
||||||
|
of "infinite":
|
||||||
|
result.wtime = int32.high()
|
||||||
|
result.btime = int32.high()
|
||||||
|
of "wtime":
|
||||||
|
result.wtime = command[current].parseInt()
|
||||||
|
of "btime":
|
||||||
|
result.btime = command[current].parseInt()
|
||||||
|
of "winc":
|
||||||
|
result.winc = command[current].parseInt()
|
||||||
|
of "binc":
|
||||||
|
result.binc = command[current].parseInt()
|
||||||
|
of "movestogo":
|
||||||
|
result.movesToGo = command[current].parseInt()
|
||||||
|
of "depth":
|
||||||
|
result.depth = command[current].parseInt()
|
||||||
|
of "movetime":
|
||||||
|
result.moveTime = command[current].parseInt()
|
||||||
|
of "nodes":
|
||||||
|
result.nodes = command[current].parseBiggestUInt()
|
||||||
|
of "searchmoves":
|
||||||
|
while current < command.len():
|
||||||
|
inc(current)
|
||||||
|
if command[current] == "":
|
||||||
|
break
|
||||||
|
let move = session.parseUCIMove(command[current]).move
|
||||||
|
if move == nullMove():
|
||||||
|
return UCICommand(kind: Unknown, reason: &"invalid move '{command[current]}' for searchmoves")
|
||||||
|
result.searchmoves.add(move)
|
||||||
|
else:
|
||||||
|
discard
|
||||||
|
|
||||||
|
|
||||||
proc handleUCIPositionCommand(session: UCISession, command: seq[string]): UCICommand =
|
proc handleUCIPositionCommand(session: UCISession, command: seq[string]): UCICommand =
|
||||||
|
@ -244,14 +268,35 @@ proc parseUCICommand(session: UCISession, command: string): UCICommand =
|
||||||
inc(current)
|
inc(current)
|
||||||
|
|
||||||
|
|
||||||
proc bestMove(session: UCISession, command: UCICommand) =
|
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
|
||||||
session.searching = true
|
{.cast(gcsafe).}:
|
||||||
session.currentSearch = SearchManager()
|
setControlCHook(proc () {.noconv.} = quit(0))
|
||||||
session.currentSearch.search(session.board, 6, 0, lowestEval(), highestEval())
|
# Yes yes nim sure this isn't gcsafe. Now stfu and spawn a thread
|
||||||
session.searching = false
|
var session = args.session
|
||||||
let move = session.currentSearch.bestMove
|
var command = args.command
|
||||||
echo &"bestmove {move.toAlgebraic()}"
|
session.searching = true
|
||||||
|
session.currentSearch = newSearchManager(session.board)
|
||||||
|
session.currentSearch.stopFlag.store(false)
|
||||||
|
var
|
||||||
|
timeRemaining = (if session.board.position.sideToMove == White: command.wtime else: command.btime)
|
||||||
|
increment = (if session.board.position.sideToMove == White: command.winc else: command.binc)
|
||||||
|
maxTime = (timeRemaining div 20) + (increment div 2)
|
||||||
|
# Buffer to avoid loosing on time
|
||||||
|
if maxTime == 0:
|
||||||
|
maxTime = int32.high()
|
||||||
|
else:
|
||||||
|
maxTime -= 100
|
||||||
|
if command.moveTime != -1:
|
||||||
|
maxTime = command.moveTime
|
||||||
|
if session.debug:
|
||||||
|
echo &"info string starting search to depth {command.depth} for at most {maxTime} ms and {command.nodes} nodes"
|
||||||
|
if session.debug and command.searchmoves.len() > 0:
|
||||||
|
echo &"""info string restricting search to: {command.searchmoves.join(" ")}"""
|
||||||
|
var move = session.currentSearch.findBestMove(maxTime, command.depth, command.nodes, command.searchmoves)
|
||||||
|
session.searching = false
|
||||||
|
echo &"bestmove {move.toAlgebraic()}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc startUCISession* =
|
proc startUCISession* =
|
||||||
|
@ -287,7 +332,11 @@ proc startUCISession* =
|
||||||
of NewGame:
|
of NewGame:
|
||||||
session.board = newDefaultChessboard()
|
session.board = newDefaultChessboard()
|
||||||
of Go:
|
of Go:
|
||||||
session.bestMove(cmd)
|
var thread: Thread[tuple[session: UCISession, command: UCICommand]]
|
||||||
|
createThread(thread, bestMove, (session, cmd))
|
||||||
|
of Stop:
|
||||||
|
if session.searching:
|
||||||
|
session.currentSearch.stopFlag.store(true)
|
||||||
of Position:
|
of Position:
|
||||||
discard
|
discard
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue