Fix searches with time per move, preliminary work on eval improvements, minor changes and fixes
This commit is contained in:
parent
d9f16c5ceb
commit
9ffa7f6ea6
|
@ -309,12 +309,53 @@ func computePawnAttacks(color: PieceColor): array[Square(0)..Square(63), Bitboar
|
|||
let pawn = i.toBitboard()
|
||||
result[i] = pawn.backwardLeftRelativeTo(color) or pawn.backwardRightRelativeTo(color)
|
||||
|
||||
|
||||
func computePassedPawnMasks(color: PieceColor): array[Square(0)..Square(63), Bitboard] =
|
||||
## Precomputes all the masks for passed pawns of the
|
||||
## given color
|
||||
for square in Square(0)..Square(63):
|
||||
let file = fileFromSquare(square)
|
||||
let rank = rankFromSquare(square)
|
||||
case color:
|
||||
of White:
|
||||
result[square] = (getFileMask(file) shr (8 * (rank)))
|
||||
if file + 1 in 0..7:
|
||||
result[square] = result[square] or (getFileMask(file + 1) shr (8 * (rank)))
|
||||
if file - 1 in 0..7:
|
||||
result[square] = result[square] or (getFileMask(file - 1) shr (8 * (rank)))
|
||||
result[square] = result[square] and not getRankMask(0)
|
||||
of Black:
|
||||
result[square] = (getFileMask(file) shl (8 * (rank)))
|
||||
if file + 1 in 0..7:
|
||||
result[square] = result[square] or (getFileMask(file + 1) shl (8 * (rank)))
|
||||
if file - 1 in 0..7:
|
||||
result[square] = result[square] or (getFileMask(file - 1) shl (8 * (rank)))
|
||||
# Remove last rank
|
||||
result[square] = result[square] and not getRankMask(7)
|
||||
else:
|
||||
discard
|
||||
|
||||
|
||||
func computeIsolatedPawnMasks: array[8, Bitboard] {.compileTime.} =
|
||||
## Computes all the masks for isolated pawns
|
||||
for file in 0..7:
|
||||
if file - 1 in 0..7:
|
||||
result[file] = result[file] or getFileMask(file - 1)
|
||||
if file + 1 in 0..7:
|
||||
result[file] = result[file] or getFileMask(file + 1)
|
||||
|
||||
|
||||
const
|
||||
KING_BITBOARDS = computeKingBitboards()
|
||||
KNIGHT_BITBOARDS = computeKnightBitboards()
|
||||
PAWN_ATTACKS: array[PieceColor.White..PieceColor.Black, array[Square(0)..Square(63), Bitboard]] = [computePawnAttacks(White), computePawnAttacks(Black)]
|
||||
ISOLATED_PAWNS = computeIsolatedPawnMasks()
|
||||
|
||||
let PASSED_PAWNS: array[PieceColor.White..PieceColor.Black, array[Square(0)..Square(63), Bitboard]] = [computePassedPawnMasks(White), computePassedPawnMasks(Black)]
|
||||
|
||||
|
||||
func getKingAttacks*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square]
|
||||
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square]
|
||||
func getPawnAttacks*(color: PieceColor, square: Square): Bitboard {.inline.} = PAWN_ATTACKS[color][square]
|
||||
proc getPassedPawnMask*(color: PieceColor, square: Square): Bitboard {.inline.} = PASSED_PAWNS[color][square]
|
||||
func getIsolatedPawnMask*(file: int): Bitboard {.inline.} = ISOLATED_PAWNS[file]
|
||||
|
|
|
@ -111,7 +111,7 @@ const
|
|||
]
|
||||
|
||||
QUEEN_MIDDLEGAME_SCORES: array[Square(0)..Square(63), Score] = [
|
||||
-28, 0, 29, 12, 59, 44, 43, 45,
|
||||
-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,
|
||||
|
@ -178,7 +178,8 @@ const
|
|||
|
||||
DOUBLED_PAWNS_MALUS: array[9, Score] = [0, -5, -10, -20, -30, -30, -30, -30, -30]
|
||||
ISOLATED_PAWN_MALUS: array[9, Score] = [0, -10, -25, -50, -75, -75, -75, -75, -75]
|
||||
|
||||
STRONG_PAWNS_BONUS: array[9, Score] = [0, 5, 10, 20, 30, 30, 30, 30, 30]
|
||||
PASSED_PAWN_BONUS: array[7, Score] = [0, 120, 80, 50, 30, 15, 15]
|
||||
|
||||
var
|
||||
MIDDLEGAME_VALUE_TABLES: array[PieceColor.White..PieceColor.Black, array[PieceKind.Bishop..PieceKind.Rook, array[Square(0)..Square(63), Score]]]
|
||||
|
@ -257,7 +258,7 @@ proc evaluateMaterial(position: Position): Score =
|
|||
# White, Black
|
||||
middleGameScores: array[PieceColor.White..PieceColor.Black, Score] = [0, 0]
|
||||
endGameScores: array[PieceColor.White..PieceColor.Black, Score] = [0, 0]
|
||||
|
||||
|
||||
for sq in position.getOccupancy():
|
||||
let piece = position.getPiece(sq)
|
||||
middleGameScores[piece.color] += MIDDLEGAME_VALUE_TABLES[piece.color][piece.kind][sq]
|
||||
|
@ -275,9 +276,12 @@ proc evaluateMaterial(position: Position): Score =
|
|||
proc evaluatePawnStructure(position: Position): Score {.used.} =
|
||||
## Evaluates the pawn structure of the current
|
||||
## position for the side to move
|
||||
|
||||
# TODO: Don't use this. Make it tapered and tune it
|
||||
let
|
||||
sideToMove = position.sideToMove
|
||||
friendlyPawns = position.getOccupancyFor(sideToMove)
|
||||
friendlyPawns = position.getBitboard(Pawn, sideToMove)
|
||||
enemyPawns = position.getBitboard(Pawn, sideToMove.opposite())
|
||||
|
||||
# Doubled pawns are a bad idea
|
||||
var doubledPawns = 0
|
||||
|
@ -287,23 +291,29 @@ proc evaluatePawnStructure(position: Position): Score {.used.} =
|
|||
|
||||
# Isolated pawns are also a bad idea
|
||||
var isolatedPawns = 0
|
||||
# Passed pawns are good
|
||||
var passedPawnBonus = Score(0)
|
||||
for pawn in friendlyPawns:
|
||||
let file = fileFromSquare(pawn)
|
||||
var fileMask = getFileMask(file)
|
||||
if file - 1 in 0..7:
|
||||
fileMask = fileMask or getFileMask(file - 1)
|
||||
if file + 1 in 0..7:
|
||||
fileMask = fileMask or getFileMask(file + 1)
|
||||
if (friendlyPawns and fileMask) == 0:
|
||||
inc(isolatedPawns)
|
||||
# Pawns that are defended by another pawn are
|
||||
# stronger
|
||||
var strongPawnIncrement = Score(0)
|
||||
for pawn in position.getBitboard(Pawn, White):
|
||||
if position.getPawnAttacks(pawn, White) != 0:
|
||||
strongPawnIncrement += position.getPieceScore(pawn) div Score(4)
|
||||
let rank = rankFromSquare(pawn)
|
||||
|
||||
return DOUBLED_PAWNS_MALUS[doubledPawns] + ISOLATED_PAWN_MALUS[isolatedPawns] + strongPawnIncrement
|
||||
if (friendlyPawns and getIsolatedPawnMask(file)) == 0:
|
||||
inc(isolatedPawns)
|
||||
|
||||
if (getPassedPawnMask(sideToMove, pawn) and enemyPawns) == 0:
|
||||
let distanceFromPromotion = if sideToMove == White: rank else: 7 - rank
|
||||
passedPawnBonus += PASSED_PAWN_BONUS[distanceFromPromotion]
|
||||
|
||||
# Nice (and cheap!) bitboard trick to count how many of our friendly pawns are defended
|
||||
# by other friendly pawns
|
||||
let strongPawns = ((friendlyPawns shl 7 or friendlyPawns shl 9) and friendlyPawns).countSquares()
|
||||
|
||||
return DOUBLED_PAWNS_MALUS[doubledPawns] + ISOLATED_PAWN_MALUS[isolatedPawns] + STRONG_PAWNS_BONUS[strongPawns] + passedPawnBonus
|
||||
|
||||
|
||||
proc evaluateThreats(position: Position): Score {.used.} =
|
||||
## Evaluates threats in the current position
|
||||
# TODO
|
||||
|
||||
|
||||
proc evaluate*(position: Position): Score =
|
||||
|
@ -312,5 +322,7 @@ proc evaluate*(position: Position): Score =
|
|||
result = position.evaluateMaterial()
|
||||
when defined(evalPawns):
|
||||
result += position.evaluatePawnStructure()
|
||||
when defined(evalThreats):
|
||||
result += position.evaluateThreats()
|
||||
# Tempo bonus: gains 19.5 +/- 13.7
|
||||
result += TEMPO_BONUS
|
||||
result += TEMPO_BONUS
|
|
@ -205,8 +205,7 @@ proc getSearchExtension(self: SearchManager, move: Move): int {.used.} =
|
|||
|
||||
|
||||
proc getReduction(self: SearchManager, move: Move, depth, ply, moveNumber: int, isPV: bool): int =
|
||||
## Returns the amount a search depth can be reduced to
|
||||
|
||||
## Returns the amount a search depth can be reduced to
|
||||
# Move reduction gains: 54.1 +/- 25.5
|
||||
if moveNumber > 2 and depth > 2:
|
||||
result = LMR_TABLE[depth][moveNumber]
|
||||
|
@ -427,7 +426,7 @@ proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score, isPV:
|
|||
return bestScore
|
||||
|
||||
|
||||
proc findBestMove*(self: var 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], timePerMove=false): Move =
|
||||
## Finds the best move in the current position
|
||||
## and returns it, limiting search time according
|
||||
## to the remaining time and increment values provided
|
||||
|
@ -437,13 +436,16 @@ proc findBestMove*(self: var SearchManager, timeRemaining, increment: int64, max
|
|||
## nodes. If searchMoves is provided and is not empty, search will
|
||||
## be restricted to the moves in the list. Note that regardless of
|
||||
## any time limitations or explicit cancellations, the search will
|
||||
## not stop until it has at least cleared depth one. Search depth
|
||||
## is always constrained to at most MAX_DEPTH ply from the root
|
||||
## not stop until it has at least cleared depth one. Search depth
|
||||
## is always constrained to at most MAX_DEPTH ply from the root. If
|
||||
## timePerMove is true, the increment is assumed to be zero and the
|
||||
## remaining time is considered the time limit for the entire search
|
||||
## (note that soft time management is disabled in that case)
|
||||
|
||||
# Apparently negative remaining time is a thing. Welp
|
||||
let
|
||||
maxSearchTime = max(1, (timeRemaining div 10) + (increment div 2))
|
||||
softLimit = maxSearchTime div 3
|
||||
maxSearchTime = if not timePerMove: max(1, (timeRemaining div 10) + (increment div 2)) else: timeRemaining
|
||||
softLimit = if not timePerMove: maxSearchTime div 3 else: maxSearchTime
|
||||
result = nullMove()
|
||||
self.maxNodes = maxNodes
|
||||
self.searchMoves = searchMoves
|
||||
|
|
|
@ -324,12 +324,13 @@ 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 command.moveTime != -1:
|
||||
timeRemaining = 0
|
||||
increment = command.moveTime
|
||||
timePerMove = command.moveTime != -1
|
||||
if timePerMove:
|
||||
timeRemaining = command.moveTime
|
||||
increment = 0
|
||||
elif timeRemaining == 0:
|
||||
timeRemaining = int32.high()
|
||||
var move = searcher.findBestMove(timeRemaining, increment, command.depth, command.nodes, command.searchmoves)
|
||||
var move = searcher.findBestMove(timeRemaining, increment, command.depth, command.nodes, command.searchmoves, timePerMove)
|
||||
echo &"bestmove {move.toAlgebraic()}"
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue