Fix some bugs in movegen. Untested TT

Signed-off-by: Mattia Giambirtone <nocturn9x@nocturn9x.space>
This commit is contained in:
Mattia Giambirtone 2024-04-26 11:41:59 +02:00
parent 95780b3236
commit 3f283932d8
8 changed files with 59 additions and 34 deletions

View File

@ -3,4 +3,3 @@
-d:danger
--passL:"-flto"
--passC:"-Ofast -flto -march=native -mtune=native"
--threads:on

View File

@ -14,6 +14,7 @@ bin = @["nimfish"]
requires "nim >= 2.0.4"
requires "jsony >= 1.1.5"
requires "nint128 >= 0.3.3"
task test, "Runs the test suite":

View File

@ -369,7 +369,6 @@ else:
result = joinPath(result.parentDir(), "resources")
const path = buildPath()
echo "Loading magic bitboards"
const
magicFile = staticRead(joinPath(path, "magics.json"))
movesFile = staticRead(joinPath(path, "movesets.json"))

View File

@ -274,12 +274,7 @@ proc generateCastling(self: Chessboard, moves: var MoveList) =
proc generateMoves*(self: Chessboard, moves: var MoveList) =
## Generates the list of all possible legal moves
## in the current position
if self.position.halfMoveClock >= 100:
# Draw by 50-move rule
return
# TODO: Check for draw by insufficient material
if self.position.repetitionDraw:
return
let sideToMove = self.position.sideToMove
self.generateKingMoves(moves)
if self.position.checkers.countSquares() > 1:
@ -440,7 +435,6 @@ proc isLegal*(self: Chessboard, move: Move): bool {.inline.} =
proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} =
## Makes a move on the board
result = move
echo move
# Updates checks and pins for the side to move
if not self.isLegal(move):
return nullMove()

View File

@ -16,6 +16,7 @@
import board
import movegen
import eval
import transpositions
import std/times
@ -38,12 +39,14 @@ type
maxNodes: uint64
searchMoves: seq[Move]
previousBestMove: Move
transpositionTable: TTable
proc newSearchManager*(board: Chessboard): SearchManager =
proc newSearchManager*(board: Chessboard, transpositions: TTable): SearchManager =
new(result)
result.board = board
result.bestMoveRoot = nullMove()
result.transpositionTable = transpositions
proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
@ -105,9 +108,21 @@ proc search*(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.
## Simple negamax search with alpha-beta pruning
if self.shouldStop():
return
let query = self.transpositionTable.get(self.board.position.zobristKey, depth.uint8)
if query.success:
case query.entry.flag:
of Exact:
return query.entry.score
of LowerBound:
if query.entry.score >= beta:
return query.entry.score
of UpperBound:
if query.entry.score <= alpha:
return query.entry.score
if depth == 0:
return self.board.evaluate()
var moves = MoveList()
var depth = depth
self.board.generateMoves(moves)
self.reorderMoves(moves)
if moves.len() == 0:
@ -126,6 +141,7 @@ proc search*(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.
if ply == 0 and self.searchMoves.len() > 0 and move notin self.searchMoves:
continue
self.board.doMove(move)
let zobrist = self.board.position.zobristKey
inc(self.nodeCount)
# Find the best move for us (worst move
# for our opponent, hence the negative sign)
@ -141,8 +157,21 @@ proc search*(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.
return
bestScore = max(score, bestScore)
if score >= beta:
# If we meet this position again, mark the fact that this score is a
# lower bound for the actual true score of the node (i.e. its score
# will NOT be lower than this)
self.transpositionTable.store(depth.uint8, score, zobrist, LowerBound)
# This move was too good for us, opponent will not search it
break
if score <= alpha:
# If we meet this position again, mark the fact that this score is an
# upper bound for the actual true score of the node (i.e. its score
# will NOT be higher than this)
self.transpositionTable.store(depth.uint8, score, zobrist, UpperBound)
else:
# The position didn't cause any cutoffs, so the score stored here is
# the actual true score of the position
self.transpositionTable.store(depth.uint8, score, zobrist, Exact)
if score > alpha:
alpha = score
if ply == 0:

View File

@ -15,7 +15,6 @@
## Implementation of a transposition table
import zobrist
import moves
import eval
@ -23,7 +22,7 @@ import nint128
type
TTentryFlag = enum
TTentryFlag* = enum
## A flag for an entry in the
## transposition table
Exact = 0'i8
@ -38,18 +37,18 @@ type
# fit into an int16
score*: int16
hash*: ZobristKey
bestMove*: Move
depth: uint8
TTable = ref object
data: ptr UncheckedArray[TTEntry]
TTable* = ref object
data: seq[TTEntry]
size: uint64
proc newTranspositionTable(size: uint64): TTable =
proc newTranspositionTable*(size: uint64): TTable =
## Initializes a new transposition table of
## size bytes
let size = size div sizeof(TTEntry).uint64
result.data = cast[ptr UncheckedArray[TTEntry]](alloc(size))
new(result)
result.data = newSeq[TTEntry](size)
func getIndex(self: TTable, key: ZobristKey): uint64 =
@ -65,20 +64,18 @@ func getIndex(self: TTable, key: ZobristKey): uint64 =
result = (u128(key.uint64) * u128(self.size)).hi
func store(self: TTable, score: Score, hash: ZobristKey, bestMove: Move, flag: TTentryFlag) =
func store*(self: TTable, depth: uint8, score: Score, hash: ZobristKey, flag: TTentryFlag) =
## Stores an entry in the transposition table
self.data[self.getIndex(hash)] = TTEntry(flag: flag, score: int16(score), hash: hash, bestMove: bestMove)
self.data[self.getIndex(hash)] = TTEntry(flag: flag, score: int16(score), hash: hash, depth: depth)
func get(self: TTable, hash: ZobristKey): tuple[success: bool, entry: TTEntry] =
proc get*(self: TTable, hash: ZobristKey, depth: uint8): tuple[success: bool, entry: TTEntry] =
## Attempts to get the entry with the given
## zobrist key in the transposition table.
## The success parameter is set to false upon
## detection of a hash collision and the result
## should be considered invalid unless it's true
## zobrist key at the given depth in the table.
## The success parameter is set to false upon detection
## of a hash collision or if the provided depth is greater
## than the one stored in the table: the result should be
## considered invalid unless it's true
result.entry = self.data[self.getIndex(hash)]
result.success = result.entry.hash == hash
result.success = result.entry.hash == hash and result.entry.depth >= depth
proc `destroy=`(self: TTable) =
dealloc(self.data)

View File

@ -21,6 +21,7 @@ import std/atomics
import board
import movegen
import search
import transpositions
type
@ -30,6 +31,7 @@ type
searching: bool
currentSearch: SearchManager
hashTableSize: uint64
transpositionTable: TTable
UCICommandType = enum
Unknown,
@ -293,9 +295,13 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
setControlCHook(proc () {.noconv.} = quit(0))
# Yes yes nim sure this isn't gcsafe. Now stfu and spawn a thread
var session = args.session
var command = args.command
if session.transpositionTable.isNil():
if session.debug:
echo &"info string created {session.hashTableSize} MiB TT"
session.transpositionTable = newTranspositionTable(session.hashTableSize * 1024 * 1024)
var command = args.command
session.searching = true
session.currentSearch = newSearchManager(session.board)
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)
@ -327,7 +333,7 @@ proc startUCISession* =
var
cmd: UCICommand
cmdStr: string
session = UCISession()
session = UCISession(hashTableSize: 64)
while true:
try:
cmdStr = readLine(stdin).strip(leading=true, trailing=true, chars={'\t', ' '})

View File

@ -44,7 +44,7 @@ proc computeZobristKeys: array[781, ZobristKey] =
result[i] = ZobristKey(prng.next())
# Eight numbers to indicate the file of a valid
# En passant square, if any
for i in 774..781:
for i in 774..780:
result[i] = ZobristKey(prng.next())
@ -81,4 +81,4 @@ proc getKingSideCastlingKey*(color: PieceColor): ZobristKey =
discard
proc getEnPassantKey*(file: SomeInteger): ZobristKey = ZOBRIST_KEYS[774 + file]
proc getEnPassantKey*(file: SomeInteger): ZobristKey = ZOBRIST_KEYS[773 + file]