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()
|
let pawn = i.toBitboard()
|
||||||
result[i] = pawn.backwardLeftRelativeTo(color) or pawn.backwardRightRelativeTo(color)
|
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
|
const
|
||||||
KING_BITBOARDS = computeKingBitboards()
|
KING_BITBOARDS = computeKingBitboards()
|
||||||
KNIGHT_BITBOARDS = computeKnightBitboards()
|
KNIGHT_BITBOARDS = computeKnightBitboards()
|
||||||
PAWN_ATTACKS: array[PieceColor.White..PieceColor.Black, array[Square(0)..Square(63), Bitboard]] = [computePawnAttacks(White), computePawnAttacks(Black)]
|
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 getKingAttacks*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square]
|
||||||
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square]
|
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square]
|
||||||
func getPawnAttacks*(color: PieceColor, square: Square): Bitboard {.inline.} = PAWN_ATTACKS[color][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]
|
||||||
|
|
|
@ -178,7 +178,8 @@ const
|
||||||
|
|
||||||
DOUBLED_PAWNS_MALUS: array[9, Score] = [0, -5, -10, -20, -30, -30, -30, -30, -30]
|
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]
|
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
|
var
|
||||||
MIDDLEGAME_VALUE_TABLES: array[PieceColor.White..PieceColor.Black, array[PieceKind.Bishop..PieceKind.Rook, array[Square(0)..Square(63), Score]]]
|
MIDDLEGAME_VALUE_TABLES: array[PieceColor.White..PieceColor.Black, array[PieceKind.Bishop..PieceKind.Rook, array[Square(0)..Square(63), Score]]]
|
||||||
|
@ -275,9 +276,12 @@ proc evaluateMaterial(position: Position): Score =
|
||||||
proc evaluatePawnStructure(position: Position): Score {.used.} =
|
proc evaluatePawnStructure(position: Position): Score {.used.} =
|
||||||
## Evaluates the pawn structure of the current
|
## Evaluates the pawn structure of the current
|
||||||
## position for the side to move
|
## position for the side to move
|
||||||
|
|
||||||
|
# TODO: Don't use this. Make it tapered and tune it
|
||||||
let
|
let
|
||||||
sideToMove = position.sideToMove
|
sideToMove = position.sideToMove
|
||||||
friendlyPawns = position.getOccupancyFor(sideToMove)
|
friendlyPawns = position.getBitboard(Pawn, sideToMove)
|
||||||
|
enemyPawns = position.getBitboard(Pawn, sideToMove.opposite())
|
||||||
|
|
||||||
# Doubled pawns are a bad idea
|
# Doubled pawns are a bad idea
|
||||||
var doubledPawns = 0
|
var doubledPawns = 0
|
||||||
|
@ -287,23 +291,29 @@ proc evaluatePawnStructure(position: Position): Score {.used.} =
|
||||||
|
|
||||||
# Isolated pawns are also a bad idea
|
# Isolated pawns are also a bad idea
|
||||||
var isolatedPawns = 0
|
var isolatedPawns = 0
|
||||||
|
# Passed pawns are good
|
||||||
|
var passedPawnBonus = Score(0)
|
||||||
for pawn in friendlyPawns:
|
for pawn in friendlyPawns:
|
||||||
let file = fileFromSquare(pawn)
|
let file = fileFromSquare(pawn)
|
||||||
var fileMask = getFileMask(file)
|
let rank = rankFromSquare(pawn)
|
||||||
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)
|
|
||||||
|
|
||||||
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 =
|
proc evaluate*(position: Position): Score =
|
||||||
|
@ -312,5 +322,7 @@ proc evaluate*(position: Position): Score =
|
||||||
result = position.evaluateMaterial()
|
result = position.evaluateMaterial()
|
||||||
when defined(evalPawns):
|
when defined(evalPawns):
|
||||||
result += position.evaluatePawnStructure()
|
result += position.evaluatePawnStructure()
|
||||||
|
when defined(evalThreats):
|
||||||
|
result += position.evaluateThreats()
|
||||||
# Tempo bonus: gains 19.5 +/- 13.7
|
# Tempo bonus: gains 19.5 +/- 13.7
|
||||||
result += TEMPO_BONUS
|
result += TEMPO_BONUS
|
|
@ -206,7 +206,6 @@ proc getSearchExtension(self: SearchManager, move: Move): int {.used.} =
|
||||||
|
|
||||||
proc getReduction(self: SearchManager, move: Move, depth, ply, moveNumber: int, isPV: bool): int =
|
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
|
# Move reduction gains: 54.1 +/- 25.5
|
||||||
if moveNumber > 2 and depth > 2:
|
if moveNumber > 2 and depth > 2:
|
||||||
result = LMR_TABLE[depth][moveNumber]
|
result = LMR_TABLE[depth][moveNumber]
|
||||||
|
@ -427,7 +426,7 @@ proc search(self: var SearchManager, depth, ply: int, alpha, beta: Score, isPV:
|
||||||
return bestScore
|
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
|
## Finds the best move in the current position
|
||||||
## and returns it, limiting search time according
|
## and returns it, limiting search time according
|
||||||
## to the remaining time and increment values provided
|
## to the remaining time and increment values provided
|
||||||
|
@ -438,12 +437,15 @@ proc findBestMove*(self: var SearchManager, timeRemaining, increment: int64, max
|
||||||
## be restricted to the moves in the list. Note that regardless of
|
## be restricted to the moves in the list. Note that regardless of
|
||||||
## any time limitations or explicit cancellations, the search will
|
## any time limitations or explicit cancellations, the search will
|
||||||
## not stop until it has at least cleared depth one. Search depth
|
## not stop until it has at least cleared depth one. Search depth
|
||||||
## is always constrained to at most MAX_DEPTH ply from the root
|
## 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
|
# Apparently negative remaining time is a thing. Welp
|
||||||
let
|
let
|
||||||
maxSearchTime = max(1, (timeRemaining div 10) + (increment div 2))
|
maxSearchTime = if not timePerMove: max(1, (timeRemaining div 10) + (increment div 2)) else: timeRemaining
|
||||||
softLimit = maxSearchTime div 3
|
softLimit = if not timePerMove: maxSearchTime div 3 else: maxSearchTime
|
||||||
result = nullMove()
|
result = nullMove()
|
||||||
self.maxNodes = maxNodes
|
self.maxNodes = maxNodes
|
||||||
self.searchMoves = searchMoves
|
self.searchMoves = searchMoves
|
||||||
|
|
|
@ -324,12 +324,13 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
|
||||||
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)
|
||||||
if command.moveTime != -1:
|
timePerMove = command.moveTime != -1
|
||||||
timeRemaining = 0
|
if timePerMove:
|
||||||
increment = command.moveTime
|
timeRemaining = command.moveTime
|
||||||
|
increment = 0
|
||||||
elif timeRemaining == 0:
|
elif timeRemaining == 0:
|
||||||
timeRemaining = int32.high()
|
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()}"
|
echo &"bestmove {move.toAlgebraic()}"
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue