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
|
-d:danger
|
||||||
--passL:"-flto"
|
--passL:"-flto"
|
||||||
--passC:"-Ofast -flto -march=native -mtune=native"
|
--passC:"-Ofast -flto -march=native -mtune=native"
|
||||||
--threads:on
|
|
|
@ -14,6 +14,7 @@ bin = @["nimfish"]
|
||||||
|
|
||||||
requires "nim >= 2.0.4"
|
requires "nim >= 2.0.4"
|
||||||
requires "jsony >= 1.1.5"
|
requires "jsony >= 1.1.5"
|
||||||
|
requires "nint128 >= 0.3.3"
|
||||||
|
|
||||||
|
|
||||||
task test, "Runs the test suite":
|
task test, "Runs the test suite":
|
||||||
|
|
|
@ -369,7 +369,6 @@ else:
|
||||||
result = joinPath(result.parentDir(), "resources")
|
result = joinPath(result.parentDir(), "resources")
|
||||||
|
|
||||||
const path = buildPath()
|
const path = buildPath()
|
||||||
echo "Loading magic bitboards"
|
|
||||||
const
|
const
|
||||||
magicFile = staticRead(joinPath(path, "magics.json"))
|
magicFile = staticRead(joinPath(path, "magics.json"))
|
||||||
movesFile = staticRead(joinPath(path, "movesets.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) =
|
proc generateMoves*(self: Chessboard, moves: var MoveList) =
|
||||||
## Generates the list of all possible legal moves
|
## Generates the list of all possible legal moves
|
||||||
## in the current position
|
## 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
|
let sideToMove = self.position.sideToMove
|
||||||
self.generateKingMoves(moves)
|
self.generateKingMoves(moves)
|
||||||
if self.position.checkers.countSquares() > 1:
|
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.} =
|
proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} =
|
||||||
## Makes a move on the board
|
## Makes a move on the board
|
||||||
result = move
|
result = move
|
||||||
echo move
|
|
||||||
# Updates checks and pins for the side to move
|
# Updates checks and pins for the side to move
|
||||||
if not self.isLegal(move):
|
if not self.isLegal(move):
|
||||||
return nullMove()
|
return nullMove()
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
import board
|
import board
|
||||||
import movegen
|
import movegen
|
||||||
import eval
|
import eval
|
||||||
|
import transpositions
|
||||||
|
|
||||||
|
|
||||||
import std/times
|
import std/times
|
||||||
|
@ -38,12 +39,14 @@ type
|
||||||
maxNodes: uint64
|
maxNodes: uint64
|
||||||
searchMoves: seq[Move]
|
searchMoves: seq[Move]
|
||||||
previousBestMove: Move
|
previousBestMove: Move
|
||||||
|
transpositionTable: TTable
|
||||||
|
|
||||||
|
|
||||||
proc newSearchManager*(board: Chessboard): SearchManager =
|
proc newSearchManager*(board: Chessboard, transpositions: TTable): SearchManager =
|
||||||
new(result)
|
new(result)
|
||||||
result.board = board
|
result.board = board
|
||||||
result.bestMoveRoot = nullMove()
|
result.bestMoveRoot = nullMove()
|
||||||
|
result.transpositionTable = transpositions
|
||||||
|
|
||||||
|
|
||||||
proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
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
|
## Simple negamax search with alpha-beta pruning
|
||||||
if self.shouldStop():
|
if self.shouldStop():
|
||||||
return
|
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:
|
if depth == 0:
|
||||||
return self.board.evaluate()
|
return self.board.evaluate()
|
||||||
var moves = MoveList()
|
var moves = MoveList()
|
||||||
|
var depth = depth
|
||||||
self.board.generateMoves(moves)
|
self.board.generateMoves(moves)
|
||||||
self.reorderMoves(moves)
|
self.reorderMoves(moves)
|
||||||
if moves.len() == 0:
|
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:
|
if ply == 0 and self.searchMoves.len() > 0 and move notin self.searchMoves:
|
||||||
continue
|
continue
|
||||||
self.board.doMove(move)
|
self.board.doMove(move)
|
||||||
|
let zobrist = self.board.position.zobristKey
|
||||||
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)
|
||||||
|
@ -141,8 +157,21 @@ proc search*(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.
|
||||||
return
|
return
|
||||||
bestScore = max(score, bestScore)
|
bestScore = max(score, bestScore)
|
||||||
if score >= beta:
|
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
|
# This move was too good for us, opponent will not search it
|
||||||
break
|
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:
|
if score > alpha:
|
||||||
alpha = score
|
alpha = score
|
||||||
if ply == 0:
|
if ply == 0:
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
## Implementation of a transposition table
|
## Implementation of a transposition table
|
||||||
|
|
||||||
import zobrist
|
import zobrist
|
||||||
import moves
|
|
||||||
import eval
|
import eval
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +22,7 @@ import nint128
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
TTentryFlag = enum
|
TTentryFlag* = enum
|
||||||
## A flag for an entry in the
|
## A flag for an entry in the
|
||||||
## transposition table
|
## transposition table
|
||||||
Exact = 0'i8
|
Exact = 0'i8
|
||||||
|
@ -38,18 +37,18 @@ type
|
||||||
# fit into an int16
|
# fit into an int16
|
||||||
score*: int16
|
score*: int16
|
||||||
hash*: ZobristKey
|
hash*: ZobristKey
|
||||||
bestMove*: Move
|
depth: uint8
|
||||||
|
|
||||||
TTable = ref object
|
TTable* = ref object
|
||||||
data: ptr UncheckedArray[TTEntry]
|
data: seq[TTEntry]
|
||||||
size: uint64
|
size: uint64
|
||||||
|
|
||||||
|
|
||||||
proc newTranspositionTable(size: uint64): TTable =
|
proc newTranspositionTable*(size: uint64): TTable =
|
||||||
## Initializes a new transposition table of
|
## Initializes a new transposition table of
|
||||||
## size bytes
|
## size bytes
|
||||||
let size = size div sizeof(TTEntry).uint64
|
new(result)
|
||||||
result.data = cast[ptr UncheckedArray[TTEntry]](alloc(size))
|
result.data = newSeq[TTEntry](size)
|
||||||
|
|
||||||
|
|
||||||
func getIndex(self: TTable, key: ZobristKey): uint64 =
|
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
|
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
|
## 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
|
## Attempts to get the entry with the given
|
||||||
## zobrist key in the transposition table.
|
## zobrist key at the given depth in the table.
|
||||||
## The success parameter is set to false upon
|
## The success parameter is set to false upon detection
|
||||||
## detection of a hash collision and the result
|
## of a hash collision or if the provided depth is greater
|
||||||
## should be considered invalid unless it's true
|
## 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.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 board
|
||||||
import movegen
|
import movegen
|
||||||
import search
|
import search
|
||||||
|
import transpositions
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
@ -30,6 +31,7 @@ type
|
||||||
searching: bool
|
searching: bool
|
||||||
currentSearch: SearchManager
|
currentSearch: SearchManager
|
||||||
hashTableSize: uint64
|
hashTableSize: uint64
|
||||||
|
transpositionTable: TTable
|
||||||
|
|
||||||
UCICommandType = enum
|
UCICommandType = enum
|
||||||
Unknown,
|
Unknown,
|
||||||
|
@ -293,9 +295,13 @@ proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.}
|
||||||
setControlCHook(proc () {.noconv.} = quit(0))
|
setControlCHook(proc () {.noconv.} = quit(0))
|
||||||
# Yes yes nim sure this isn't gcsafe. Now stfu and spawn a thread
|
# Yes yes nim sure this isn't gcsafe. Now stfu and spawn a thread
|
||||||
var session = args.session
|
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.searching = true
|
||||||
session.currentSearch = newSearchManager(session.board)
|
session.currentSearch = newSearchManager(session.board, session.transpositionTable)
|
||||||
session.currentSearch.stopFlag.store(false)
|
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)
|
||||||
|
@ -327,7 +333,7 @@ proc startUCISession* =
|
||||||
var
|
var
|
||||||
cmd: UCICommand
|
cmd: UCICommand
|
||||||
cmdStr: string
|
cmdStr: string
|
||||||
session = UCISession()
|
session = UCISession(hashTableSize: 64)
|
||||||
while true:
|
while true:
|
||||||
try:
|
try:
|
||||||
cmdStr = readLine(stdin).strip(leading=true, trailing=true, chars={'\t', ' '})
|
cmdStr = readLine(stdin).strip(leading=true, trailing=true, chars={'\t', ' '})
|
||||||
|
|
|
@ -44,7 +44,7 @@ proc computeZobristKeys: array[781, ZobristKey] =
|
||||||
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..781:
|
for i in 774..780:
|
||||||
result[i] = ZobristKey(prng.next())
|
result[i] = ZobristKey(prng.next())
|
||||||
|
|
||||||
|
|
||||||
|
@ -81,4 +81,4 @@ proc getKingSideCastlingKey*(color: PieceColor): ZobristKey =
|
||||||
discard
|
discard
|
||||||
|
|
||||||
|
|
||||||
proc getEnPassantKey*(file: SomeInteger): ZobristKey = ZOBRIST_KEYS[774 + file]
|
proc getEnPassantKey*(file: SomeInteger): ZobristKey = ZOBRIST_KEYS[773 + file]
|
Loading…
Reference in New Issue