Hide untested options behind when defined(), increase size of some position counters, tweak MVVLVA multiplier and more fixes
This commit is contained in:
parent
679965c438
commit
d3170ab03a
|
@ -9,4 +9,5 @@ nimfish/nimfishpkg/resources/*.pgn
|
||||||
__pycache__
|
__pycache__
|
||||||
fast-chess
|
fast-chess
|
||||||
log.txt
|
log.txt
|
||||||
config.json
|
config.json
|
||||||
|
*.log
|
||||||
|
|
|
@ -3,4 +3,3 @@
|
||||||
-d:danger
|
-d:danger
|
||||||
--passL:"-flto"
|
--passL:"-flto"
|
||||||
--passC:"-Ofast -flto -march=native -mtune=native"
|
--passC:"-Ofast -flto -march=native -mtune=native"
|
||||||
--maxLoopIterationsVM:100000000
|
|
|
@ -168,7 +168,7 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||||
while index <= fen.high():
|
while index <= fen.high():
|
||||||
s.add(fen[index])
|
s.add(fen[index])
|
||||||
inc(index)
|
inc(index)
|
||||||
result.position.fullMoveCount = parseInt(s).int8
|
result.position.fullMoveCount = parseInt(s).uint16
|
||||||
else:
|
else:
|
||||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||||
inc(index)
|
inc(index)
|
||||||
|
|
|
@ -245,6 +245,7 @@ proc getPieceScore*(board: Chessboard, piece: Piece, square: Square): Score =
|
||||||
|
|
||||||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
||||||
|
|
||||||
|
|
||||||
proc evaluateMaterial(board: ChessBoard): Score =
|
proc evaluateMaterial(board: ChessBoard): Score =
|
||||||
## Returns a material and position evaluation
|
## Returns a material and position evaluation
|
||||||
## for the current side to move
|
## for the current side to move
|
||||||
|
@ -270,7 +271,7 @@ proc evaluateMaterial(board: ChessBoard): Score =
|
||||||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
||||||
|
|
||||||
|
|
||||||
proc evaluatePawnStructure(board: Chessboard): Score =
|
proc evaluatePawnStructure(board: Chessboard): 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
|
||||||
let
|
let
|
||||||
|
@ -294,11 +295,20 @@ proc evaluatePawnStructure(board: Chessboard): Score =
|
||||||
fileMask = fileMask or getFileMask(file + 1)
|
fileMask = fileMask or getFileMask(file + 1)
|
||||||
if (friendlyPawns and fileMask) == 0:
|
if (friendlyPawns and fileMask) == 0:
|
||||||
inc(isolatedPawns)
|
inc(isolatedPawns)
|
||||||
return DOUBLED_PAWNS_MALUS[doubledPawns] + ISOLATED_PAWN_MALUS[isolatedPawns]
|
# Pawns that are defended by another pawn are
|
||||||
|
# stronger
|
||||||
|
var
|
||||||
|
strongPawnIncrement = Score(0)
|
||||||
|
for pawn in board.getBitboard(Pawn, White):
|
||||||
|
if board.getPawnAttacks(pawn, White) != 0:
|
||||||
|
strongPawnIncrement += board.getPieceScore(pawn) div Score(4)
|
||||||
|
|
||||||
|
return DOUBLED_PAWNS_MALUS[doubledPawns] + ISOLATED_PAWN_MALUS[isolatedPawns] + strongPawnIncrement
|
||||||
|
|
||||||
|
|
||||||
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.evaluatePawnStructure()
|
when defined(evalPawns):
|
||||||
|
result += board.evaluatePawnStructure()
|
||||||
|
|
|
@ -480,15 +480,13 @@ proc testPieceBitboard(bitboard: Bitboard, squares: seq[Square]) =
|
||||||
|
|
||||||
|
|
||||||
const testFens = staticRead("../../tests/all.txt").splitLines()
|
const testFens = staticRead("../../tests/all.txt").splitLines()
|
||||||
const benchFens = staticRead("../../tests/all.txt").splitLines()
|
|
||||||
|
|
||||||
|
|
||||||
proc basicTests* =
|
proc basicTests* =
|
||||||
|
|
||||||
for fen in testFens:
|
for fen in testFens:
|
||||||
doAssert fen == newChessboardFromFEN(fen).toFEN()
|
doAssert fen == newChessboardFromFEN(fen).toFEN()
|
||||||
|
|
||||||
for fen in benchFens:
|
for fen in testFens:
|
||||||
var
|
var
|
||||||
board = newChessboardFromFEN(fen)
|
board = newChessboardFromFEN(fen)
|
||||||
hashes = newTable[ZobristKey, Move]()
|
hashes = newTable[ZobristKey, Move]()
|
||||||
|
@ -497,11 +495,10 @@ proc basicTests* =
|
||||||
for move in moves:
|
for move in moves:
|
||||||
board.makeMove(move)
|
board.makeMove(move)
|
||||||
let
|
let
|
||||||
currentFEN = board.toFEN()
|
|
||||||
pos = board.position
|
pos = board.position
|
||||||
key = pos.zobristKey
|
key = pos.zobristKey
|
||||||
board.unmakeMove()
|
board.unmakeMove()
|
||||||
doAssert not hashes.contains(key), &"{fen} has zobrist collisions {move} -> {hashes[key]}"
|
doAssert not hashes.contains(key), &"{fen} has zobrist collisions {move} -> {hashes[key]} (key is {key.uint64})"
|
||||||
hashes[key] = move
|
hashes[key] = move
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,14 +30,14 @@ type
|
||||||
# Number of half-moves that were performed
|
# Number of half-moves that were performed
|
||||||
# to reach this position starting from the
|
# to reach this position starting from the
|
||||||
# root of the tree
|
# root of the tree
|
||||||
plyFromRoot*: uint8
|
plyFromRoot*: uint16
|
||||||
# Number of half moves since
|
# Number of half moves since
|
||||||
# last piece capture or pawn movement.
|
# last piece capture or pawn movement.
|
||||||
# Used for the 50-move rule
|
# Used for the 50-move rule
|
||||||
halfMoveClock*: int8
|
halfMoveClock*: int8
|
||||||
# Full move counter. Increments
|
# Full move counter. Increments
|
||||||
# every 2 ply (half-moves)
|
# every 2 ply (half-moves)
|
||||||
fullMoveCount*: int8
|
fullMoveCount*: uint16
|
||||||
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
||||||
enPassantSquare*: Square
|
enPassantSquare*: Square
|
||||||
|
|
||||||
|
|
|
@ -63,9 +63,9 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
||||||
# 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
|
||||||
# second goal we want to use our least valuable pieces to do so (this
|
# second goal we want to use our least valuable pieces to do so (this
|
||||||
# is why we multiply the score of the captured piece by 100, to give
|
# is why we multiply the score of the captured piece by 10, to give
|
||||||
# it priority)
|
# it priority)
|
||||||
result += 100 * self.board.getPieceScore(move.targetSquare) - self.board.getPieceScore(move.startSquare)
|
result += 10 * self.board.getPieceScore(move.targetSquare) - self.board.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
|
||||||
|
@ -81,10 +81,11 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
||||||
else:
|
else:
|
||||||
discard # Unreachable
|
discard # Unreachable
|
||||||
result += self.board.getPieceScore(piece, move.targetSquare)
|
result += self.board.getPieceScore(piece, move.targetSquare)
|
||||||
if (self.board.getPawnAttacks(move.targetSquare, nonSideToMove) and self.board.getBitboard(Pawn, nonSideToMove)) != 0:
|
if self.board.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
|
# idea. Assume the piece is lost and give a malus based on the fact that
|
||||||
result -= self.board.getPieceScore(move.startSquare)
|
# losing a piece this way is a very poor move
|
||||||
|
result -= self.board.getPieceScore(move.startSquare) * 2
|
||||||
|
|
||||||
|
|
||||||
proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
||||||
|
@ -127,7 +128,7 @@ proc shouldStop(self: SearchManager): bool =
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
|
||||||
proc getSearchExtension(self: SearchManager, move: Move): int =
|
proc getSearchExtension(self: SearchManager, move: Move): int {.used.} =
|
||||||
## Returns the number of extensions that should be performed
|
## Returns the number of extensions that should be performed
|
||||||
## when exploring the given move
|
## when exploring the given move
|
||||||
if self.currentExtensionCount == 16:
|
if self.currentExtensionCount == 16:
|
||||||
|
@ -172,7 +173,7 @@ proc qsearch(self: SearchManager, ply: uint8, alpha, beta: Score): Score =
|
||||||
self.board.generateMoves(moves, capturesOnly=true)
|
self.board.generateMoves(moves, capturesOnly=true)
|
||||||
self.reorderMoves(moves)
|
self.reorderMoves(moves)
|
||||||
var bestScore = score
|
var bestScore = score
|
||||||
var alpha = max(alpha, score)
|
var alpha = max(alpha, score)
|
||||||
for move in moves:
|
for move in moves:
|
||||||
self.board.doMove(move)
|
self.board.doMove(move)
|
||||||
inc(self.nodeCount)
|
inc(self.nodeCount)
|
||||||
|
@ -238,25 +239,28 @@ proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.d
|
||||||
if ply == 0 and self.searchMoves.len() > 0 and move notin self.searchMoves:
|
if ply == 0 and self.searchMoves.len() > 0 and move notin self.searchMoves:
|
||||||
continue
|
continue
|
||||||
self.board.doMove(move)
|
self.board.doMove(move)
|
||||||
# var extension = self.getSearchExtension(move)
|
var extension = 0
|
||||||
|
when defined(searchExtensions):
|
||||||
|
extension = self.getSearchExtension(move)
|
||||||
inc(self.nodeCount)
|
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)
|
||||||
var score: Score
|
var score: Score
|
||||||
#[var fullDepth = true
|
var fullDepth = true
|
||||||
if extension == 0 and i >= 3 and not move.isCapture():
|
when defined(searchLMR):
|
||||||
# Late Move Reduction: assume our move orderer did a good job,
|
if extension == 0 and i >= 3 and not move.isCapture():
|
||||||
# so it is not worth to look at all moves at the same depth equally.
|
# Late Move Reduction: assume our move orderer did a good job,
|
||||||
# If this move turns out to be better than we expected, we'll re-search
|
# so it is not worth to look at all moves at the same depth equally.
|
||||||
# it at full depth
|
# If this move turns out to be better than we expected, we'll re-search
|
||||||
const reduction = 1
|
# it at full depth
|
||||||
score = -self.search(depth - 1 - reduction, ply + 1, -beta, -alpha)
|
const reduction = 1
|
||||||
fullDepth = score > alpha]#
|
score = -self.search(depth - 1 - reduction, ply + 1, -beta, -alpha)
|
||||||
|
fullDepth = score > alpha
|
||||||
if self.board.position.halfMoveClock >= 100 or self.board.position.repetitionDraw:
|
if self.board.position.halfMoveClock >= 100 or self.board.position.repetitionDraw:
|
||||||
# Drawing by repetition is *bad*
|
# Drawing by repetition is *bad*
|
||||||
score = Score(0)
|
score = Score(0)
|
||||||
#if fullDepth:
|
elif fullDepth:
|
||||||
score = -self.search(depth - 1 #[+ extension]#, ply + 1, -beta, -alpha)
|
score = -self.search(depth - 1 + extension, ply + 1, -beta, -alpha)
|
||||||
self.board.unmakeMove()
|
self.board.unmakeMove()
|
||||||
# When a search is cancelled or times out, we need
|
# When a search is cancelled or times out, we need
|
||||||
# to make sure the entire call stack unwinds back
|
# to make sure the entire call stack unwinds back
|
||||||
|
@ -308,10 +312,8 @@ proc findBestMove*(self: SearchManager, maxSearchTime, maxDepth: int, maxNodes:
|
||||||
# its mind, or it will have found an even better move
|
# its mind, or it will have found an even better move
|
||||||
# in the meantime, which we should obviously use!
|
# in the meantime, which we should obviously use!
|
||||||
result = self.bestMoveRoot
|
result = self.bestMoveRoot
|
||||||
let shouldStop = self.shouldStop()
|
if self.shouldStop():
|
||||||
if shouldStop:
|
|
||||||
self.log(i - 1)
|
self.log(i - 1)
|
||||||
|
break
|
||||||
else:
|
else:
|
||||||
self.log(i)
|
self.log(i)
|
||||||
if shouldStop:
|
|
||||||
break
|
|
|
@ -303,15 +303,14 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
|
||||||
var command = args.command
|
var command = args.command
|
||||||
session.searching = true
|
session.searching = true
|
||||||
session.currentSearch = newSearchManager(session.board, session.transpositionTable)
|
session.currentSearch = newSearchManager(session.board, session.transpositionTable)
|
||||||
session.currentSearch.stopFlag.store(false)
|
|
||||||
var
|
var
|
||||||
timeRemaining = (if session.board.position.sideToMove == White: command.wtime else: command.btime)
|
timeRemaining = (if session.board.position.sideToMove == White: command.wtime else: command.btime)
|
||||||
increment = (if session.board.position.sideToMove == White: command.winc else: command.binc)
|
increment = (if session.board.position.sideToMove == White: command.winc else: command.binc)
|
||||||
maxTime = (timeRemaining div 20) + (increment div 2)
|
maxTime = (timeRemaining div 20) + (increment div 2)
|
||||||
# Buffer to avoid loosing on time
|
|
||||||
if maxTime == 0:
|
if maxTime == 0:
|
||||||
maxTime = int32.high()
|
maxTime = int32.high()
|
||||||
else:
|
else:
|
||||||
|
# Buffer to avoid losing on time
|
||||||
maxTime -= 100
|
maxTime -= 100
|
||||||
if command.moveTime != -1:
|
if command.moveTime != -1:
|
||||||
maxTime = command.moveTime
|
maxTime = command.moveTime
|
||||||
|
|
|
@ -40,11 +40,11 @@ proc computeZobristKeys: array[781, ZobristKey] =
|
||||||
# to move
|
# to move
|
||||||
result[768] = ZobristKey(prng.next())
|
result[768] = ZobristKey(prng.next())
|
||||||
# Four numbers to indicate castling rights
|
# Four numbers to indicate castling rights
|
||||||
for i in 769..773:
|
for i in 769..772:
|
||||||
result[i] = ZobristKey(prng.next())
|
result[i] = ZobristKey(prng.next())
|
||||||
# Eight numbers to indicate the file of a valid
|
# Eight numbers to indicate the file of a valid
|
||||||
# En passant square, if any
|
# En passant square, if any
|
||||||
for i in 774..780:
|
for i in 773..780:
|
||||||
result[i] = ZobristKey(prng.next())
|
result[i] = ZobristKey(prng.next())
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ const PIECE_TO_INDEX = [[0, 1, 2, 3, 4, 5], [6, 7, 8, 9, 10, 11]]
|
||||||
|
|
||||||
|
|
||||||
proc getKey*(piece: Piece, square: Square): ZobristKey =
|
proc getKey*(piece: Piece, square: Square): ZobristKey =
|
||||||
let index = PIECE_TO_INDEX[piece.color.int][piece.kind.int] + square.int
|
let index = PIECE_TO_INDEX[piece.color.int][piece.kind.int] * 64 + square.int
|
||||||
return ZOBRIST_KEYS[index]
|
return ZOBRIST_KEYS[index]
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue