Hide untested options behind when defined(), increase size of some position counters, tweak MVVLVA multiplier and more fixes
This commit is contained in:
parent
4efde83934
commit
c7ad3aa1b5
|
@ -9,4 +9,5 @@ nimfish/nimfishpkg/resources/*.pgn
|
|||
__pycache__
|
||||
fast-chess
|
||||
log.txt
|
||||
config.json
|
||||
config.json
|
||||
*.log
|
||||
|
|
|
@ -3,4 +3,3 @@
|
|||
-d:danger
|
||||
--passL:"-flto"
|
||||
--passC:"-Ofast -flto -march=native -mtune=native"
|
||||
--maxLoopIterationsVM:100000000
|
|
@ -168,7 +168,7 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
|||
while index <= fen.high():
|
||||
s.add(fen[index])
|
||||
inc(index)
|
||||
result.position.fullMoveCount = parseInt(s).int8
|
||||
result.position.fullMoveCount = parseInt(s).uint16
|
||||
else:
|
||||
raise newException(ValueError, "invalid FEN: too many fields in FEN string")
|
||||
inc(index)
|
||||
|
|
|
@ -245,6 +245,7 @@ proc getPieceScore*(board: Chessboard, piece: Piece, square: Square): Score =
|
|||
|
||||
result = Score((middleGameScore * middleGamePhase + endGameScore * endGamePhase) div 24)
|
||||
|
||||
|
||||
proc evaluateMaterial(board: ChessBoard): Score =
|
||||
## Returns a material and position evaluation
|
||||
## for the current side to move
|
||||
|
@ -270,7 +271,7 @@ proc evaluateMaterial(board: ChessBoard): Score =
|
|||
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
|
||||
## position for the side to move
|
||||
let
|
||||
|
@ -294,11 +295,20 @@ proc evaluatePawnStructure(board: Chessboard): Score =
|
|||
fileMask = fileMask or getFileMask(file + 1)
|
||||
if (friendlyPawns and fileMask) == 0:
|
||||
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 =
|
||||
## Evaluates the current position
|
||||
|
||||
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 benchFens = staticRead("../../tests/all.txt").splitLines()
|
||||
|
||||
|
||||
proc basicTests* =
|
||||
|
||||
for fen in testFens:
|
||||
doAssert fen == newChessboardFromFEN(fen).toFEN()
|
||||
|
||||
for fen in benchFens:
|
||||
for fen in testFens:
|
||||
var
|
||||
board = newChessboardFromFEN(fen)
|
||||
hashes = newTable[ZobristKey, Move]()
|
||||
|
@ -497,11 +495,10 @@ proc basicTests* =
|
|||
for move in moves:
|
||||
board.makeMove(move)
|
||||
let
|
||||
currentFEN = board.toFEN()
|
||||
pos = board.position
|
||||
key = pos.zobristKey
|
||||
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
|
||||
|
||||
|
||||
|
|
|
@ -30,14 +30,14 @@ type
|
|||
# Number of half-moves that were performed
|
||||
# to reach this position starting from the
|
||||
# root of the tree
|
||||
plyFromRoot*: uint8
|
||||
plyFromRoot*: uint16
|
||||
# Number of half moves since
|
||||
# last piece capture or pawn movement.
|
||||
# Used for the 50-move rule
|
||||
halfMoveClock*: int8
|
||||
# Full move counter. Increments
|
||||
# every 2 ply (half-moves)
|
||||
fullMoveCount*: int8
|
||||
fullMoveCount*: uint16
|
||||
# En passant target square (see https://en.wikipedia.org/wiki/En_passant)
|
||||
enPassantSquare*: Square
|
||||
|
||||
|
|
|
@ -63,9 +63,9 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
|||
# 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 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)
|
||||
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():
|
||||
# Promotions are a good idea to search first
|
||||
var piece: Piece
|
||||
|
@ -81,10 +81,11 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
|||
else:
|
||||
discard # Unreachable
|
||||
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
|
||||
# idea. Assume the piece is lost
|
||||
result -= self.board.getPieceScore(move.startSquare)
|
||||
# idea. Assume the piece is lost and give a malus based on the fact that
|
||||
# losing a piece this way is a very poor move
|
||||
result -= self.board.getPieceScore(move.startSquare) * 2
|
||||
|
||||
|
||||
proc reorderMoves(self: SearchManager, moves: var MoveList) =
|
||||
|
@ -127,7 +128,7 @@ proc shouldStop(self: SearchManager): bool =
|
|||
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
|
||||
## when exploring the given move
|
||||
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.reorderMoves(moves)
|
||||
var bestScore = score
|
||||
var alpha = max(alpha, score)
|
||||
var alpha = max(alpha, score)
|
||||
for move in moves:
|
||||
self.board.doMove(move)
|
||||
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:
|
||||
continue
|
||||
self.board.doMove(move)
|
||||
# var extension = self.getSearchExtension(move)
|
||||
var extension = 0
|
||||
when defined(searchExtensions):
|
||||
extension = self.getSearchExtension(move)
|
||||
inc(self.nodeCount)
|
||||
# Find the best move for us (worst move
|
||||
# for our opponent, hence the negative sign)
|
||||
var score: Score
|
||||
#[var fullDepth = true
|
||||
if extension == 0 and i >= 3 and not move.isCapture():
|
||||
# Late Move Reduction: assume our move orderer did a good job,
|
||||
# so it is not worth to look at all moves at the same depth equally.
|
||||
# If this move turns out to be better than we expected, we'll re-search
|
||||
# it at full depth
|
||||
const reduction = 1
|
||||
score = -self.search(depth - 1 - reduction, ply + 1, -beta, -alpha)
|
||||
fullDepth = score > alpha]#
|
||||
var fullDepth = true
|
||||
when defined(searchLMR):
|
||||
if extension == 0 and i >= 3 and not move.isCapture():
|
||||
# Late Move Reduction: assume our move orderer did a good job,
|
||||
# so it is not worth to look at all moves at the same depth equally.
|
||||
# If this move turns out to be better than we expected, we'll re-search
|
||||
# it at full depth
|
||||
const reduction = 1
|
||||
score = -self.search(depth - 1 - reduction, ply + 1, -beta, -alpha)
|
||||
fullDepth = score > alpha
|
||||
if self.board.position.halfMoveClock >= 100 or self.board.position.repetitionDraw:
|
||||
# Drawing by repetition is *bad*
|
||||
score = Score(0)
|
||||
#if fullDepth:
|
||||
score = -self.search(depth - 1 #[+ extension]#, ply + 1, -beta, -alpha)
|
||||
elif fullDepth:
|
||||
score = -self.search(depth - 1 + extension, ply + 1, -beta, -alpha)
|
||||
self.board.unmakeMove()
|
||||
# When a search is cancelled or times out, we need
|
||||
# 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
|
||||
# in the meantime, which we should obviously use!
|
||||
result = self.bestMoveRoot
|
||||
let shouldStop = self.shouldStop()
|
||||
if shouldStop:
|
||||
if self.shouldStop():
|
||||
self.log(i - 1)
|
||||
break
|
||||
else:
|
||||
self.log(i)
|
||||
if shouldStop:
|
||||
break
|
||||
self.log(i)
|
|
@ -303,15 +303,14 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
|
|||
var command = args.command
|
||||
session.searching = true
|
||||
session.currentSearch = newSearchManager(session.board, session.transpositionTable)
|
||||
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:
|
||||
# Buffer to avoid losing on time
|
||||
maxTime -= 100
|
||||
if command.moveTime != -1:
|
||||
maxTime = command.moveTime
|
||||
|
|
|
@ -40,11 +40,11 @@ proc computeZobristKeys: array[781, ZobristKey] =
|
|||
# to move
|
||||
result[768] = ZobristKey(prng.next())
|
||||
# Four numbers to indicate castling rights
|
||||
for i in 769..773:
|
||||
for i in 769..772:
|
||||
result[i] = ZobristKey(prng.next())
|
||||
# Eight numbers to indicate the file of a valid
|
||||
# En passant square, if any
|
||||
for i in 774..780:
|
||||
for i in 773..780:
|
||||
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 =
|
||||
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]
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue