Fix some bugs in movegen. Untested TT
Signed-off-by: Mattia Giambirtone <nocturn9x@nocturn9x.space>
This commit is contained in:
parent
95780b3236
commit
3f283932d8
|
@ -3,4 +3,3 @@
|
|||
-d:danger
|
||||
--passL:"-flto"
|
||||
--passC:"-Ofast -flto -march=native -mtune=native"
|
||||
--threads:on
|
|
@ -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":
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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', ' '})
|
||||
|
|
|
@ -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]
|
Loading…
Reference in New Issue