Fix searches with time per move, preliminary work on eval improvements, minor changes and fixes

This commit is contained in:
Mattia Giambirtone 2024-05-07 13:40:48 +02:00
parent d9f16c5ceb
commit 9ffa7f6ea6
4 changed files with 86 additions and 30 deletions

View File

@ -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]

View 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

View File

@ -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

View File

@ -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()}"