Pregenerate pawn attacks (currently unused). Attempts at fixing random crashing
This commit is contained in:
parent
d3170ab03a
commit
b865ece113
|
@ -2,4 +2,8 @@
|
||||||
-o:"bin/nimfish"
|
-o:"bin/nimfish"
|
||||||
-d:danger
|
-d:danger
|
||||||
--passL:"-flto"
|
--passL:"-flto"
|
||||||
--passC:"-Ofast -flto -march=native -mtune=native"
|
--passC:"-flto -march=native -mtune=native"
|
||||||
|
--mm:boehm
|
||||||
|
--stackTrace
|
||||||
|
--lineTrace
|
||||||
|
--debugger:native
|
|
@ -302,10 +302,28 @@ func computeKnightBitboards: array[64, Bitboard] {.compileTime.} =
|
||||||
result[i] = movements
|
result[i] = movements
|
||||||
|
|
||||||
|
|
||||||
|
func computePawnAttacks(color: PieceColor): array[64, Bitboard] {.compileTime.} =
|
||||||
|
## Precomputes all the attack bitboards for pawns
|
||||||
|
## of the given color
|
||||||
|
for i in 0'u64..63:
|
||||||
|
let
|
||||||
|
pawn = i.toBitboard()
|
||||||
|
square = Square(i)
|
||||||
|
file = fileFromSquare(square)
|
||||||
|
var movements = Bitboard(0)
|
||||||
|
if file in 1..7:
|
||||||
|
movements = movements or pawn.forwardLeftRelativeTo(color)
|
||||||
|
if file in 0..6:
|
||||||
|
movements = movements or pawn.forwardRightRelativeTo(color)
|
||||||
|
movements = movements and not pawn
|
||||||
|
result[i] = movements
|
||||||
|
|
||||||
const
|
const
|
||||||
KING_BITBOARDS = computeKingBitboards()
|
KING_BITBOARDS = computeKingBitboards()
|
||||||
KNIGHT_BITBOARDS = computeKnightBitboards()
|
KNIGHT_BITBOARDS = computeKnightBitboards()
|
||||||
|
PAWN_ATTACKS = [computePawnAttacks(White), computePawnAttacks(Black)]
|
||||||
|
|
||||||
|
|
||||||
func getKingAttacks*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square.int]
|
func getKingAttacks*(square: Square): Bitboard {.inline.} = KING_BITBOARDS[square.int]
|
||||||
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square.int]
|
func getKnightAttacks*(square: Square): Bitboard {.inline.} = KNIGHT_BITBOARDS[square.int]
|
||||||
|
func getPawnAttacks*(color: PieceColor, square: Square): Bitboard {.inline.} = PAWN_ATTACKS[color.int][square.int]
|
||||||
|
|
|
@ -161,7 +161,7 @@ proc newChessboardFromFEN*(fen: string): Chessboard =
|
||||||
# Backtrack so the space is seen by the
|
# Backtrack so the space is seen by the
|
||||||
# next iteration of the loop
|
# next iteration of the loop
|
||||||
dec(index)
|
dec(index)
|
||||||
result.position.halfMoveClock = parseInt(s).int8
|
result.position.halfMoveClock = parseInt(s).uint16
|
||||||
of 5:
|
of 5:
|
||||||
# Fullmove number
|
# Fullmove number
|
||||||
var s = ""
|
var s = ""
|
||||||
|
@ -603,7 +603,10 @@ proc drawByRepetition*(self: Chessboard): bool =
|
||||||
|
|
||||||
proc hash*(self: Chessboard) =
|
proc hash*(self: Chessboard) =
|
||||||
## Computes the zobrist hash of the current
|
## Computes the zobrist hash of the current
|
||||||
## position
|
## position. This only needs to be called when
|
||||||
|
## a position is loaded the first time, as all
|
||||||
|
## subsequent hashes are updated incrementally
|
||||||
|
## at every call to doMove()
|
||||||
self.position.zobristKey = ZobristKey(0)
|
self.position.zobristKey = ZobristKey(0)
|
||||||
|
|
||||||
if self.position.sideToMove == Black:
|
if self.position.sideToMove == Black:
|
||||||
|
@ -622,4 +625,4 @@ proc hash*(self: Chessboard) =
|
||||||
self.position.zobristKey = self.position.zobristKey xor getQueenSideCastlingKey(Black)
|
self.position.zobristKey = self.position.zobristKey xor getQueenSideCastlingKey(Black)
|
||||||
|
|
||||||
if self.position.enPassantSquare != nullSquare():
|
if self.position.enPassantSquare != nullSquare():
|
||||||
self.position.zobristKey = self.position.zobristKey xor getEnPassantKey(fileFromSquare(self.position.enPassantSquare))
|
self.position.zobristKey = self.position.zobristKey xor getEnPassantKey(fileFromSquare(self.position.enPassantSquare))
|
||||||
|
|
|
@ -349,12 +349,16 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
fullMoveCount = self.position.fullMoveCount
|
fullMoveCount = self.position.fullMoveCount
|
||||||
enPassantTarget = nullSquare()
|
enPassantTarget = nullSquare()
|
||||||
|
|
||||||
|
if self.position.enPassantSquare != nullSquare():
|
||||||
|
# Unset the previous en passant square in the zobrist key
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor getEnPassantKey(fileFromSquare(self.position.enPassantSquare))
|
||||||
# Needed to detect draw by the 50 move rule
|
# Needed to detect draw by the 50 move rule
|
||||||
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
|
if piece.kind == Pawn or move.isCapture() or move.isEnPassant():
|
||||||
# Number of half-moves since the last reversible half-move
|
# Number of half-moves since the last reversible half-move
|
||||||
halfMoveClock = 0
|
halfMoveClock = 0
|
||||||
else:
|
else:
|
||||||
inc(halfMoveClock)
|
inc(halfMoveClock)
|
||||||
|
|
||||||
if piece.color == Black:
|
if piece.color == Black:
|
||||||
inc(fullMoveCount)
|
inc(fullMoveCount)
|
||||||
|
|
||||||
|
@ -368,13 +372,18 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
sideToMove: self.position.sideToMove.opposite(),
|
sideToMove: self.position.sideToMove.opposite(),
|
||||||
enPassantSquare: enPassantTarget,
|
enPassantSquare: enPassantTarget,
|
||||||
pieces: self.position.pieces,
|
pieces: self.position.pieces,
|
||||||
castlingAvailability: self.position.castlingAvailability
|
castlingAvailability: self.position.castlingAvailability,
|
||||||
|
zobristKey: self.position.zobristKey
|
||||||
)
|
)
|
||||||
|
if self.position.enPassantSquare != nullSquare():
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor getEnPassantKey(fileFromSquare(move.targetSquare))
|
||||||
# Update position metadata
|
# Update position metadata
|
||||||
|
|
||||||
if move.isEnPassant():
|
if move.isEnPassant():
|
||||||
# Make the en passant pawn disappear
|
# Make the en passant pawn disappear
|
||||||
self.removePiece(move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare())
|
let epPawnSquare = move.targetSquare.toBitboard().backwardRelativeTo(piece.color).toSquare()
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor self.getPiece(epPawnSquare).getKey(epPawnSquare)
|
||||||
|
self.removePiece(epPawnSquare)
|
||||||
|
|
||||||
if move.isCastling() or piece.kind == King:
|
if move.isCastling() or piece.kind == King:
|
||||||
# If the king has moved, all castling rights for the side to
|
# If the king has moved, all castling rights for the side to
|
||||||
|
@ -382,12 +391,24 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
self.position.castlingAvailability[piece.color.int] = (false, false)
|
self.position.castlingAvailability[piece.color.int] = (false, false)
|
||||||
if move.isCastling():
|
if move.isCastling():
|
||||||
# Move the rook where it belongs
|
# Move the rook where it belongs
|
||||||
|
var
|
||||||
|
source: Square
|
||||||
|
rook: Piece
|
||||||
|
target: Square
|
||||||
|
|
||||||
if move.targetSquare == piece.kingSideCastling():
|
if move.targetSquare == piece.kingSideCastling():
|
||||||
let rook = self.getPiece(piece.color.kingSideRook())
|
source = piece.color.kingSideRook()
|
||||||
self.movePiece(piece.color.kingSideRook(), rook.kingSideCastling())
|
rook = self.getPiece(source)
|
||||||
if move.targetSquare == piece.queenSideCastling():
|
target = rook.kingSideCastling()
|
||||||
let rook = self.getPiece(piece.color.queenSideRook())
|
|
||||||
self.movePiece(piece.color.queenSideRook(), rook.queenSideCastling())
|
elif move.targetSquare == piece.queenSideCastling():
|
||||||
|
source = piece.color.queenSideRook()
|
||||||
|
rook = self.getPiece(source)
|
||||||
|
target = rook.queenSideCastling()
|
||||||
|
|
||||||
|
self.movePiece(source, target)
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor piece.getKey(source)
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor piece.getKey(target)
|
||||||
|
|
||||||
if piece.kind == Rook:
|
if piece.kind == Rook:
|
||||||
# If a rook on either side moves, castling rights are permanently revoked
|
# If a rook on either side moves, castling rights are permanently revoked
|
||||||
|
@ -400,6 +421,7 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
if move.isCapture():
|
if move.isCapture():
|
||||||
# Get rid of captured pieces
|
# Get rid of captured pieces
|
||||||
let captured = self.getPiece(move.targetSquare)
|
let captured = self.getPiece(move.targetSquare)
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor captured.getKey(move.targetSquare)
|
||||||
self.removePiece(move.targetSquare)
|
self.removePiece(move.targetSquare)
|
||||||
# If a rook has been captured, castling on that side is prohibited
|
# If a rook has been captured, castling on that side is prohibited
|
||||||
if captured.kind == Rook:
|
if captured.kind == Rook:
|
||||||
|
@ -409,27 +431,36 @@ proc doMove*(self: Chessboard, move: Move) =
|
||||||
self.position.castlingAvailability[captured.color.int].queen = false
|
self.position.castlingAvailability[captured.color.int].queen = false
|
||||||
|
|
||||||
# Move the piece to its target square
|
# Move the piece to its target square
|
||||||
self.movePiece(move)
|
self.movePiece(move)
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor piece.getKey(move.startSquare)
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor piece.getKey(move.targetSquare)
|
||||||
if move.isPromotion():
|
if move.isPromotion():
|
||||||
# Move is a pawn promotion: get rid of the pawn
|
# Move is a pawn promotion: get rid of the pawn
|
||||||
# and spawn a new piece
|
# and spawn a new piece
|
||||||
self.removePiece(move.targetSquare)
|
self.removePiece(move.targetSquare)
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor piece.getKey(move.targetSquare)
|
||||||
|
var spawnedPiece: Piece
|
||||||
case move.getPromotionType():
|
case move.getPromotionType():
|
||||||
of PromoteToBishop:
|
of PromoteToBishop:
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Bishop, color: piece.color))
|
spawnedPiece = Piece(kind: Bishop, color: piece.color)
|
||||||
of PromoteToKnight:
|
of PromoteToKnight:
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Knight, color: piece.color))
|
spawnedPiece = Piece(kind: Knight, color: piece.color)
|
||||||
of PromoteToRook:
|
of PromoteToRook:
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Rook, color: piece.color))
|
spawnedPiece = Piece(kind: Rook, color: piece.color)
|
||||||
of PromoteToQueen:
|
of PromoteToQueen:
|
||||||
self.spawnPiece(move.targetSquare, Piece(kind: Queen, color: piece.color))
|
spawnedPiece = Piece(kind: Queen, color: piece.color)
|
||||||
else:
|
else:
|
||||||
# Unreachable
|
# Unreachable
|
||||||
discard
|
discard
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor spawnedPiece.getKey(move.targetSquare)
|
||||||
|
self.spawnPiece(move.targetSquare, spawnedPiece)
|
||||||
# Updates checks and pins for the (new) side to move
|
# Updates checks and pins for the (new) side to move
|
||||||
self.updateChecksAndPins()
|
self.updateChecksAndPins()
|
||||||
# Update zobrist key
|
# Last updates to zobrist key
|
||||||
self.hash()
|
if self.position.castlingAvailability[piece.color.int].king:
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor getKingSideCastlingKey(piece.color)
|
||||||
|
if self.position.castlingAvailability[piece.color.int].queen:
|
||||||
|
self.position.zobristKey = self.position.zobristKey xor getQueenSideCastlingKey(piece.color)
|
||||||
discard self.drawByRepetition()
|
discard self.drawByRepetition()
|
||||||
|
|
||||||
|
|
||||||
|
@ -450,13 +481,11 @@ proc makeMove*(self: Chessboard, move: Move): Move {.discardable.} =
|
||||||
|
|
||||||
|
|
||||||
proc unmakeMove*(self: Chessboard) =
|
proc unmakeMove*(self: Chessboard) =
|
||||||
## Reverts to the previous board position,
|
## Reverts to the previous board position
|
||||||
## if one exists
|
if self.positions.len() == 0:
|
||||||
|
return
|
||||||
self.position = self.positions.pop()
|
self.position = self.positions.pop()
|
||||||
self.update()
|
self.update()
|
||||||
self.hash()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## Testing stuff
|
## Testing stuff
|
||||||
|
@ -477,7 +506,7 @@ proc testPieceBitboard(bitboard: Bitboard, squares: seq[Square]) =
|
||||||
if i != squares.len():
|
if i != squares.len():
|
||||||
doAssert false, &"bitboard.len() ({i}) != squares.len() ({squares.len()})"
|
doAssert false, &"bitboard.len() ({i}) != squares.len() ({squares.len()})"
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
const testFens = staticRead("../../tests/all.txt").splitLines()
|
const testFens = staticRead("../../tests/all.txt").splitLines()
|
||||||
|
|
||||||
|
|
|
@ -100,6 +100,7 @@ func createMove*(startSquare, targetSquare: Square, flags: varargs[MoveFlag]): M
|
||||||
for flag in flags:
|
for flag in flags:
|
||||||
result.flags = result.flags or flag.uint16
|
result.flags = result.flags or flag.uint16
|
||||||
|
|
||||||
|
|
||||||
proc createMove*(startSquare, targetSquare: string, flags: varargs[MoveFlag]): Move =
|
proc createMove*(startSquare, targetSquare: string, flags: varargs[MoveFlag]): Move =
|
||||||
result = createMove(startSquare.toSquare(), targetSquare.toSquare(), flags)
|
result = createMove(startSquare.toSquare(), targetSquare.toSquare(), flags)
|
||||||
|
|
||||||
|
@ -113,6 +114,7 @@ func createMove*(startSquare: Square, targetSquare: SomeInteger, flags: varargs[
|
||||||
|
|
||||||
func nullMove*: Move {.inline.} = createMove(nullSquare(), nullSquare())
|
func nullMove*: Move {.inline.} = createMove(nullSquare(), nullSquare())
|
||||||
|
|
||||||
|
|
||||||
func isPromotion*(move: Move): bool {.inline.} =
|
func isPromotion*(move: Move): bool {.inline.} =
|
||||||
## Returns whether the given move is a
|
## Returns whether the given move is a
|
||||||
## pawn promotion
|
## pawn promotion
|
||||||
|
@ -168,6 +170,8 @@ func getFlags*(move: Move): seq[MoveFlag] =
|
||||||
func `$`*(self: Move): string =
|
func `$`*(self: Move): string =
|
||||||
## Returns a string representation
|
## Returns a string representation
|
||||||
## for the move
|
## for the move
|
||||||
|
if self == nullMove():
|
||||||
|
return "null"
|
||||||
result &= &"{self.startSquare}{self.targetSquare}"
|
result &= &"{self.startSquare}{self.targetSquare}"
|
||||||
let flags = self.getFlags()
|
let flags = self.getFlags()
|
||||||
if len(flags) > 0:
|
if len(flags) > 0:
|
||||||
|
@ -181,6 +185,8 @@ func `$`*(self: Move): string =
|
||||||
|
|
||||||
|
|
||||||
func toAlgebraic*(self: Move): string =
|
func toAlgebraic*(self: Move): string =
|
||||||
|
if self == nullMove():
|
||||||
|
return "null"
|
||||||
result &= &"{self.startSquare}{self.targetSquare}"
|
result &= &"{self.startSquare}{self.targetSquare}"
|
||||||
if self.isPromotion():
|
if self.isPromotion():
|
||||||
case self.getPromotionType():
|
case self.getPromotionType():
|
||||||
|
|
|
@ -18,8 +18,7 @@ import zobrist
|
||||||
|
|
||||||
|
|
||||||
type
|
type
|
||||||
|
Position* = object
|
||||||
Position* = ref object
|
|
||||||
## A chess position
|
## A chess position
|
||||||
|
|
||||||
# Castling availability. This just keeps track
|
# Castling availability. This just keeps track
|
||||||
|
@ -34,13 +33,12 @@ type
|
||||||
# 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*: uint16
|
||||||
# Full move counter. Increments
|
# Full move counter. Increments
|
||||||
# every 2 ply (half-moves)
|
# every 2 ply (half-moves)
|
||||||
fullMoveCount*: uint16
|
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
|
||||||
|
|
||||||
# The side to move
|
# The side to move
|
||||||
sideToMove*: PieceColor
|
sideToMove*: PieceColor
|
||||||
# Positional bitboards for all pieces
|
# Positional bitboards for all pieces
|
||||||
|
|
|
@ -30,7 +30,8 @@ type
|
||||||
SearchManager* = ref object
|
SearchManager* = ref object
|
||||||
## A simple state storage
|
## A simple state storage
|
||||||
## for our search
|
## for our search
|
||||||
stopFlag*: Atomic[bool] # Can be used to cancel the search from another thread
|
searching: Atomic[bool]
|
||||||
|
stopFlag: Atomic[bool] # Can be used to cancel the search from another thread
|
||||||
board: Chessboard
|
board: Chessboard
|
||||||
bestMoveRoot: Move
|
bestMoveRoot: Move
|
||||||
bestRootScore: Score
|
bestRootScore: Score
|
||||||
|
@ -51,6 +52,19 @@ proc newSearchManager*(board: Chessboard, transpositions: TTable): SearchManager
|
||||||
result.transpositionTable = transpositions
|
result.transpositionTable = transpositions
|
||||||
|
|
||||||
|
|
||||||
|
proc isSearching*(self: SearchManager): bool =
|
||||||
|
## Returns whether a search for the best
|
||||||
|
## move is in progress
|
||||||
|
result = self.searching.load()
|
||||||
|
|
||||||
|
|
||||||
|
proc stop*(self: SearchManager) =
|
||||||
|
## Stops the search if it is
|
||||||
|
## running
|
||||||
|
if self.isSearching():
|
||||||
|
self.stopFlag.store(true)
|
||||||
|
|
||||||
|
|
||||||
proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
||||||
## Returns an estimated static score for the move
|
## Returns an estimated static score for the move
|
||||||
result = Score(0)
|
result = Score(0)
|
||||||
|
@ -84,7 +98,7 @@ proc getEstimatedMoveScore(self: SearchManager, move: Move): Score =
|
||||||
if self.board.getPawnAttacks(move.targetSquare, 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 and give a malus based on the fact that
|
# 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
|
# losing a piece this way is dumb
|
||||||
result -= self.board.getPieceScore(move.startSquare) * 2
|
result -= self.board.getPieceScore(move.startSquare) * 2
|
||||||
|
|
||||||
|
|
||||||
|
@ -159,8 +173,9 @@ proc qsearch(self: SearchManager, ply: uint8, alpha, beta: Score): Score =
|
||||||
## or sacking pieces for apparently no reason: the reason is that it
|
## or sacking pieces for apparently no reason: the reason is that it
|
||||||
## did not look at the opponent's responses, because it stopped earlier.
|
## did not look at the opponent's responses, because it stopped earlier.
|
||||||
## That's the horizon). To address this, we look at all possible captures
|
## That's the horizon). To address this, we look at all possible captures
|
||||||
## in the current position and make sure that a position is not evaluated as
|
## in the current position and make sure that a position is evaluated as
|
||||||
## bad if only bad capture moves exist, if good non-capture moves do
|
## bad if only bad capture moves are possible, even if good non-capture moves
|
||||||
|
## exist
|
||||||
if self.shouldStop():
|
if self.shouldStop():
|
||||||
return
|
return
|
||||||
if ply == 127:
|
if ply == 127:
|
||||||
|
@ -202,7 +217,10 @@ proc qsearch(self: SearchManager, ply: uint8, alpha, beta: Score): Score =
|
||||||
|
|
||||||
proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.discardable.} =
|
proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.discardable.} =
|
||||||
## Simple negamax search with alpha-beta pruning
|
## Simple negamax search with alpha-beta pruning
|
||||||
if self.shouldStop():
|
if depth > 1 and self.shouldStop():
|
||||||
|
# We do not let ourselves get cancelled at depth
|
||||||
|
# one because then we wouldn't have a move to return.
|
||||||
|
# In practice this should not be a problem
|
||||||
return
|
return
|
||||||
when defined(useTT):
|
when defined(useTT):
|
||||||
let query = self.transpositionTable.get(self.board.position.zobristKey, depth.uint8)
|
let query = self.transpositionTable.get(self.board.position.zobristKey, depth.uint8)
|
||||||
|
@ -219,8 +237,10 @@ proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.d
|
||||||
if depth == 0:
|
if depth == 0:
|
||||||
# Quiescent search gain: 264.8 +/- 71.6
|
# Quiescent search gain: 264.8 +/- 71.6
|
||||||
return self.qsearch(0, alpha, beta)
|
return self.qsearch(0, alpha, beta)
|
||||||
var moves = newMoveList()
|
var
|
||||||
var depth = depth
|
moves = newMoveList()
|
||||||
|
depth = depth
|
||||||
|
bestMove = nullMove()
|
||||||
self.board.generateMoves(moves)
|
self.board.generateMoves(moves)
|
||||||
self.reorderMoves(moves)
|
self.reorderMoves(moves)
|
||||||
if moves.len() == 0:
|
if moves.len() == 0:
|
||||||
|
@ -265,7 +285,7 @@ proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.d
|
||||||
# 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
|
||||||
# to the root move. This is why the check is duplicated
|
# to the root move. This is why the check is duplicated
|
||||||
if self.shouldStop():
|
if depth > 1 and self.shouldStop():
|
||||||
return
|
return
|
||||||
bestScore = max(score, bestScore)
|
bestScore = max(score, bestScore)
|
||||||
let nodeType = if score >= beta: LowerBound elif score <= alpha: UpperBound else: Exact
|
let nodeType = if score >= beta: LowerBound elif score <= alpha: UpperBound else: Exact
|
||||||
|
@ -277,6 +297,7 @@ proc search(self: SearchManager, depth, ply: int, alpha, beta: Score): Score {.d
|
||||||
break
|
break
|
||||||
if score > alpha:
|
if score > alpha:
|
||||||
alpha = score
|
alpha = score
|
||||||
|
bestMove = move
|
||||||
if ply == 0:
|
if ply == 0:
|
||||||
self.bestMoveRoot = move
|
self.bestMoveRoot = move
|
||||||
self.bestRootScore = bestScore
|
self.bestRootScore = bestScore
|
||||||
|
@ -291,7 +312,11 @@ proc findBestMove*(self: SearchManager, maxSearchTime, maxDepth: int, maxNodes:
|
||||||
## is picked). If maxNodes is supplied and is nonzero,
|
## is picked). If maxNodes is supplied and is nonzero,
|
||||||
## search will stop once it has analyzed the given number
|
## search will stop once it has analyzed the given number
|
||||||
## of nodes. If searchMoves is provided and is not empty,
|
## of nodes. If searchMoves is provided and is not empty,
|
||||||
## search will be restricted to the moves in the list
|
## search will be restricted to the moves in the list. Note
|
||||||
|
## that regardless of any time limitations, the search will
|
||||||
|
## not be cancelled until it has at least clear depth one
|
||||||
|
## (this is to make sure that there is always a best move to
|
||||||
|
## return)
|
||||||
self.bestMoveRoot = nullMove()
|
self.bestMoveRoot = nullMove()
|
||||||
result = self.bestMoveRoot
|
result = self.bestMoveRoot
|
||||||
self.maxNodes = maxNodes
|
self.maxNodes = maxNodes
|
||||||
|
@ -301,6 +326,7 @@ proc findBestMove*(self: SearchManager, maxSearchTime, maxDepth: int, maxNodes:
|
||||||
var maxDepth = maxDepth
|
var maxDepth = maxDepth
|
||||||
if maxDepth == -1:
|
if maxDepth == -1:
|
||||||
maxDepth = 30
|
maxDepth = 30
|
||||||
|
self.searching.store(true)
|
||||||
# Iterative deepening loop
|
# Iterative deepening loop
|
||||||
for i in 1..maxDepth:
|
for i in 1..maxDepth:
|
||||||
# Search the previous best move first
|
# Search the previous best move first
|
||||||
|
@ -316,4 +342,5 @@ proc findBestMove*(self: SearchManager, maxSearchTime, maxDepth: int, maxNodes:
|
||||||
self.log(i - 1)
|
self.log(i - 1)
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
self.log(i)
|
self.log(i)
|
||||||
|
self.searching.store(false)
|
|
@ -18,6 +18,7 @@ import std/strformat
|
||||||
import std/atomics
|
import std/atomics
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
import board
|
import board
|
||||||
import movegen
|
import movegen
|
||||||
import search
|
import search
|
||||||
|
@ -28,8 +29,7 @@ type
|
||||||
UCISession = ref object
|
UCISession = ref object
|
||||||
debug: bool
|
debug: bool
|
||||||
board: Chessboard
|
board: Chessboard
|
||||||
searching: bool
|
currentSearch: Atomic[SearchManager]
|
||||||
currentSearch: SearchManager
|
|
||||||
hashTableSize: uint64
|
hashTableSize: uint64
|
||||||
transpositionTable: TTable
|
transpositionTable: TTable
|
||||||
|
|
||||||
|
@ -292,38 +292,37 @@ proc parseUCICommand(session: UCISession, command: string): UCICommand =
|
||||||
|
|
||||||
proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.} =
|
proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.} =
|
||||||
## Finds the best move in the current position
|
## Finds the best move in the current position
|
||||||
|
setControlCHook(proc () {.noconv.} = quit(0))
|
||||||
|
|
||||||
{.cast(gcsafe).}:
|
{.cast(gcsafe).}:
|
||||||
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
|
||||||
if session.transpositionTable.isNil():
|
when defined(useTT):
|
||||||
if session.debug:
|
if session.transpositionTable.isNil():
|
||||||
echo &"info string created {session.hashTableSize} MiB TT"
|
if session.debug:
|
||||||
session.transpositionTable = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
echo &"info string created {session.hashTableSize} MiB TT"
|
||||||
|
session.transpositionTable = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
||||||
var command = args.command
|
var command = args.command
|
||||||
session.searching = true
|
var searcher = newSearchManager(session.board.deepCopy(), session.transpositionTable)
|
||||||
session.currentSearch = newSearchManager(session.board, session.transpositionTable)
|
session.currentSearch.store(searcher)
|
||||||
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)
|
||||||
if maxTime == 0:
|
if maxTime == 0:
|
||||||
maxTime = int32.high()
|
maxTime = int32.high()
|
||||||
else:
|
|
||||||
# Buffer to avoid losing on time
|
|
||||||
maxTime -= 100
|
|
||||||
if command.moveTime != -1:
|
if command.moveTime != -1:
|
||||||
maxTime = command.moveTime
|
maxTime = command.moveTime
|
||||||
|
# Apparently negative remaining time is a thing. Welp
|
||||||
|
maxTime = max(1, maxTime)
|
||||||
if session.debug:
|
if session.debug:
|
||||||
echo &"info string starting search to depth {command.depth} for at most {maxTime} ms and {command.nodes} nodes"
|
echo &"info string starting search to depth {command.depth} for at most {maxTime} ms and {command.nodes} nodes"
|
||||||
if session.debug and command.searchmoves.len() > 0:
|
if session.debug and command.searchmoves.len() > 0:
|
||||||
echo &"""info string restricting search to: {command.searchmoves.join(" ")}"""
|
echo &"""info string restricting search to: {command.searchmoves.join(" ")}"""
|
||||||
var move = session.currentSearch.findBestMove(maxTime, command.depth, command.nodes, command.searchmoves)
|
var move = searcher.findBestMove(maxTime, command.depth, command.nodes, command.searchmoves)
|
||||||
session.searching = false
|
|
||||||
echo &"bestmove {move.toAlgebraic()}"
|
echo &"bestmove {move.toAlgebraic()}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
proc startUCISession* =
|
proc startUCISession* =
|
||||||
## Begins listening for UCI commands
|
## Begins listening for UCI commands
|
||||||
echo "id name Nimfish 0.1"
|
echo "id name Nimfish 0.1"
|
||||||
|
@ -361,8 +360,7 @@ proc startUCISession* =
|
||||||
var thread: Thread[tuple[session: UCISession, command: UCICommand]]
|
var thread: Thread[tuple[session: UCISession, command: UCICommand]]
|
||||||
createThread(thread, bestMove, (session, cmd))
|
createThread(thread, bestMove, (session, cmd))
|
||||||
of Stop:
|
of Stop:
|
||||||
if session.searching:
|
session.currentSearch.load().stop()
|
||||||
session.currentSearch.stopFlag.store(true)
|
|
||||||
of SetOption:
|
of SetOption:
|
||||||
case cmd.name:
|
case cmd.name:
|
||||||
of "Hash":
|
of "Hash":
|
||||||
|
|
Loading…
Reference in New Issue