2024-04-24 10:41:01 +02:00
|
|
|
# Copyright 2024 Mattia Giambirtone & All Contributors
|
|
|
|
#
|
|
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
# you may not use this file except in compliance with the License.
|
|
|
|
# You may obtain a copy of the License at
|
|
|
|
#
|
|
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
#
|
|
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
# See the License for the specific language governing permissions and
|
|
|
|
# limitations under the License.
|
|
|
|
|
|
|
|
## Implementation of a UCI compatible server
|
|
|
|
import std/strutils
|
|
|
|
import std/strformat
|
2024-05-04 20:30:26 +02:00
|
|
|
|
|
|
|
|
2024-04-24 10:41:01 +02:00
|
|
|
import board
|
|
|
|
import movegen
|
2024-04-24 19:38:54 +02:00
|
|
|
import search
|
2024-05-12 01:02:47 +02:00
|
|
|
import eval
|
2024-04-26 11:41:59 +02:00
|
|
|
import transpositions
|
2024-04-24 10:41:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
type
|
2024-05-04 20:30:26 +02:00
|
|
|
UCISession = object
|
|
|
|
## A UCI session
|
2024-04-24 10:41:01 +02:00
|
|
|
debug: bool
|
2024-05-05 16:07:37 +02:00
|
|
|
# Previously reached positions
|
2024-05-04 20:30:26 +02:00
|
|
|
history: seq[Position]
|
2024-05-05 16:07:37 +02:00
|
|
|
# The current position
|
2024-05-04 20:30:26 +02:00
|
|
|
position: Position
|
2024-05-13 14:51:21 +02:00
|
|
|
## Information about the current search. We use a
|
|
|
|
## raw pointer because Nim's memory management strategy
|
|
|
|
## doesn't like sharing references across thread (despite
|
|
|
|
## the fact that it should be safe to do so)
|
|
|
|
searchState: ptr SearchManager
|
2024-05-13 17:11:24 +02:00
|
|
|
printMove: ptr bool
|
2024-05-05 16:07:37 +02:00
|
|
|
# Size of the transposition table (in megabytes)
|
2024-04-25 23:41:25 +02:00
|
|
|
hashTableSize: uint64
|
2024-05-13 14:51:21 +02:00
|
|
|
# # Atomic boolean flag to interrupt the search
|
|
|
|
# stopFlag: ptr Atomic[bool]
|
|
|
|
# # Atomic search flag used to know whether a search
|
|
|
|
# # is in progress
|
|
|
|
# searchFlag: ptr Atomic[bool]
|
|
|
|
# # The transposition table
|
|
|
|
# transpositionTable: ptr TTable
|
|
|
|
# # Storage for our history heuristic
|
|
|
|
# historyTable: ptr HistoryTable
|
|
|
|
# # Storage for our killer move heuristic
|
|
|
|
# killerMoves: ptr KillersTable
|
2024-04-24 10:41:01 +02:00
|
|
|
|
|
|
|
UCICommandType = enum
|
2024-05-04 20:30:26 +02:00
|
|
|
## A UCI command type enumeration
|
2024-04-24 19:38:54 +02:00
|
|
|
Unknown,
|
|
|
|
IsReady,
|
|
|
|
NewGame,
|
2024-04-24 10:41:01 +02:00
|
|
|
Quit,
|
|
|
|
Debug,
|
2024-04-24 12:38:03 +02:00
|
|
|
Position,
|
2024-04-25 18:53:51 +02:00
|
|
|
SetOption,
|
2024-04-24 19:38:54 +02:00
|
|
|
Go,
|
2024-05-12 15:30:10 +02:00
|
|
|
Stop,
|
|
|
|
PonderHit
|
2024-04-24 10:41:01 +02:00
|
|
|
|
|
|
|
UCICommand = object
|
2024-05-04 20:30:26 +02:00
|
|
|
## A UCI command
|
2024-04-24 10:41:01 +02:00
|
|
|
case kind: UCICommandType
|
|
|
|
of Debug:
|
2024-04-25 23:41:25 +02:00
|
|
|
on: bool
|
2024-04-24 10:41:01 +02:00
|
|
|
of Position:
|
|
|
|
fen: string
|
|
|
|
moves: seq[string]
|
2024-04-25 23:41:25 +02:00
|
|
|
of SetOption:
|
|
|
|
name: string
|
|
|
|
value: string
|
2024-04-24 10:41:01 +02:00
|
|
|
of Unknown:
|
|
|
|
reason: string
|
2024-04-24 12:38:03 +02:00
|
|
|
of Go:
|
|
|
|
wtime: int
|
|
|
|
btime: int
|
|
|
|
winc: int
|
|
|
|
binc: int
|
|
|
|
movesToGo: int
|
|
|
|
depth: int
|
|
|
|
moveTime: int
|
2024-04-25 15:20:55 +02:00
|
|
|
nodes: uint64
|
|
|
|
searchmoves: seq[Move]
|
2024-05-12 15:30:10 +02:00
|
|
|
ponder: bool
|
2024-04-24 10:41:01 +02:00
|
|
|
else:
|
|
|
|
discard
|
|
|
|
|
|
|
|
|
2024-05-04 20:30:26 +02:00
|
|
|
proc parseUCIMove(position: Position, move: string): tuple[move: Move, command: UCICommand] =
|
2024-04-24 10:41:01 +02:00
|
|
|
var
|
|
|
|
startSquare: Square
|
|
|
|
targetSquare: Square
|
|
|
|
flags: seq[MoveFlag]
|
|
|
|
if len(move) notin 4..5:
|
2024-04-24 12:38:03 +02:00
|
|
|
return (nullMove(), UCICommand(kind: Unknown, reason: "invalid move syntax"))
|
2024-04-24 10:41:01 +02:00
|
|
|
try:
|
|
|
|
startSquare = move[0..1].toSquare()
|
|
|
|
except ValueError:
|
|
|
|
return (nullMove(), UCICommand(kind: Unknown, reason: &"invalid start square {move[0..1]}"))
|
|
|
|
try:
|
|
|
|
targetSquare = move[2..3].toSquare()
|
|
|
|
except ValueError:
|
|
|
|
return (nullMove(), UCICommand(kind: Unknown, reason: &"invalid target square {move[2..3]}"))
|
|
|
|
|
|
|
|
# Since the client tells us just the source and target square of the move,
|
|
|
|
# we have to figure out all the flags by ourselves (whether it's a double
|
|
|
|
# push, a capture, a promotion, etc.)
|
2024-05-04 20:30:26 +02:00
|
|
|
if position.getPiece(targetSquare).kind != Empty:
|
2024-04-24 10:41:01 +02:00
|
|
|
flags.add(Capture)
|
|
|
|
|
2024-05-04 20:30:26 +02:00
|
|
|
if position.getPiece(startSquare).kind == Pawn and abs(rankFromSquare(startSquare) - rankFromSquare(targetSquare)) == 2:
|
2024-04-24 10:41:01 +02:00
|
|
|
flags.add(DoublePush)
|
|
|
|
|
|
|
|
if len(move) == 5:
|
|
|
|
# Promotion
|
|
|
|
case move[4]:
|
|
|
|
of 'b':
|
|
|
|
flags.add(PromoteToBishop)
|
|
|
|
of 'n':
|
|
|
|
flags.add(PromoteToKnight)
|
|
|
|
of 'q':
|
|
|
|
flags.add(PromoteToQueen)
|
|
|
|
of 'r':
|
|
|
|
flags.add(PromoteToRook)
|
|
|
|
else:
|
|
|
|
return
|
2024-05-04 20:30:26 +02:00
|
|
|
let piece = position.getPiece(startSquare)
|
|
|
|
if piece.kind == King and startSquare == position.sideToMove.getKingStartingSquare():
|
2024-04-24 10:41:01 +02:00
|
|
|
if targetSquare in [piece.kingSideCastling(), piece.queenSideCastling()]:
|
|
|
|
flags.add(Castle)
|
2024-05-04 20:30:26 +02:00
|
|
|
elif piece.kind == Pawn and targetSquare == position.enPassantSquare:
|
2024-04-25 23:41:25 +02:00
|
|
|
# I hate en passant I hate en passant I hate en passant I hate en passant I hate en passant I hate en passant
|
2024-04-24 10:41:01 +02:00
|
|
|
flags.add(EnPassant)
|
2024-04-25 15:20:55 +02:00
|
|
|
result.move = createMove(startSquare, targetSquare, flags)
|
|
|
|
|
|
|
|
|
2024-05-04 20:30:26 +02:00
|
|
|
proc handleUCIMove(session: var UCISession, board: var Chessboard, move: string): tuple[move: Move, cmd: UCICommand] {.discardable.} =
|
2024-04-24 10:41:01 +02:00
|
|
|
if session.debug:
|
|
|
|
echo &"info string making move {move}"
|
2024-04-25 15:20:55 +02:00
|
|
|
let
|
2024-05-04 20:30:26 +02:00
|
|
|
r = board.position.parseUCIMove(move)
|
2024-04-25 15:20:55 +02:00
|
|
|
move = r.move
|
|
|
|
command = r.command
|
|
|
|
if move == nullMove():
|
|
|
|
return (move, command)
|
|
|
|
else:
|
2024-05-04 20:30:26 +02:00
|
|
|
result.move = board.makeMove(move)
|
2024-04-25 15:20:55 +02:00
|
|
|
|
|
|
|
|
|
|
|
proc handleUCIGoCommand(session: UCISession, command: seq[string]): UCICommand =
|
|
|
|
result = UCICommand(kind: Go)
|
|
|
|
result.wtime = 0
|
|
|
|
result.btime = 0
|
|
|
|
result.winc = 0
|
|
|
|
result.binc = 0
|
|
|
|
result.movesToGo = 0
|
|
|
|
result.depth = -1
|
|
|
|
result.moveTime = -1
|
|
|
|
result.nodes = 0
|
|
|
|
var
|
|
|
|
current = 1 # Skip the "go"
|
|
|
|
while current < command.len():
|
|
|
|
let flag = command[current]
|
|
|
|
inc(current)
|
|
|
|
case flag:
|
|
|
|
of "infinite":
|
|
|
|
result.wtime = int32.high()
|
|
|
|
result.btime = int32.high()
|
2024-05-13 17:11:24 +02:00
|
|
|
of "ponder":
|
|
|
|
result.ponder = true
|
2024-04-25 15:20:55 +02:00
|
|
|
of "wtime":
|
|
|
|
result.wtime = command[current].parseInt()
|
|
|
|
of "btime":
|
|
|
|
result.btime = command[current].parseInt()
|
|
|
|
of "winc":
|
|
|
|
result.winc = command[current].parseInt()
|
|
|
|
of "binc":
|
|
|
|
result.binc = command[current].parseInt()
|
|
|
|
of "movestogo":
|
|
|
|
result.movesToGo = command[current].parseInt()
|
|
|
|
of "depth":
|
|
|
|
result.depth = command[current].parseInt()
|
|
|
|
of "movetime":
|
|
|
|
result.moveTime = command[current].parseInt()
|
|
|
|
of "nodes":
|
|
|
|
result.nodes = command[current].parseBiggestUInt()
|
|
|
|
of "searchmoves":
|
|
|
|
while current < command.len():
|
|
|
|
inc(current)
|
|
|
|
if command[current] == "":
|
|
|
|
break
|
2024-05-04 20:30:26 +02:00
|
|
|
let move = session.position.parseUCIMove(command[current]).move
|
2024-04-25 15:20:55 +02:00
|
|
|
if move == nullMove():
|
|
|
|
return UCICommand(kind: Unknown, reason: &"invalid move '{command[current]}' for searchmoves")
|
|
|
|
result.searchmoves.add(move)
|
|
|
|
else:
|
|
|
|
discard
|
2024-04-24 10:41:01 +02:00
|
|
|
|
|
|
|
|
2024-05-04 20:30:26 +02:00
|
|
|
proc handleUCIPositionCommand(session: var UCISession, command: seq[string]): UCICommand =
|
2024-04-24 10:41:01 +02:00
|
|
|
# Makes sure we don't leave the board in an invalid state if
|
|
|
|
# some error occurs
|
|
|
|
result = UCICommand(kind: Position)
|
2024-05-04 20:30:26 +02:00
|
|
|
var chessboard = newChessboard()
|
2024-04-24 10:41:01 +02:00
|
|
|
case command[1]:
|
|
|
|
of "startpos":
|
|
|
|
result.fen = "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"
|
2024-05-04 20:30:26 +02:00
|
|
|
chessboard.position = startpos()
|
2024-04-24 10:41:01 +02:00
|
|
|
if command.len() > 2:
|
|
|
|
let args = command[2..^1]
|
|
|
|
if args.len() > 0:
|
|
|
|
var i = 0
|
|
|
|
while i < args.len():
|
|
|
|
case args[i]:
|
|
|
|
of "moves":
|
|
|
|
var j = i + 1
|
|
|
|
while j < args.len():
|
2024-05-04 20:30:26 +02:00
|
|
|
let r = handleUCIMove(session, chessboard, args[j])
|
2024-04-24 10:41:01 +02:00
|
|
|
if r.move == nullMove():
|
|
|
|
if r.cmd.reason.len() > 0:
|
|
|
|
return UCICommand(kind: Unknown, reason: &"move {args[j]} is illegal or invalid ({r.cmd.reason})")
|
|
|
|
else:
|
|
|
|
return UCICommand(kind: Unknown, reason: &"move {args[j]} is illegal or invalid")
|
|
|
|
result.moves.add(args[j])
|
|
|
|
inc(j)
|
|
|
|
inc(i)
|
|
|
|
of "fen":
|
|
|
|
var
|
|
|
|
args = command[2..^1]
|
|
|
|
fenString = ""
|
|
|
|
stop = 0
|
|
|
|
for i, arg in args:
|
|
|
|
if arg in ["moves", ]:
|
|
|
|
break
|
|
|
|
if i > 0:
|
|
|
|
fenString &= " "
|
|
|
|
fenString &= arg
|
|
|
|
inc(stop)
|
|
|
|
result.fen = fenString
|
|
|
|
args = args[stop..^1]
|
2024-05-04 20:30:26 +02:00
|
|
|
chessboard.position = loadFEN(result.fen)
|
2024-04-24 10:41:01 +02:00
|
|
|
if args.len() > 0:
|
|
|
|
var i = 0
|
|
|
|
while i < args.len():
|
|
|
|
case args[i]:
|
|
|
|
of "moves":
|
|
|
|
var j = i + 1
|
|
|
|
while j < args.len():
|
2024-05-04 20:30:26 +02:00
|
|
|
let r = handleUCIMove(session, chessboard, args[j])
|
2024-04-25 23:41:25 +02:00
|
|
|
if r.move == nullMove():
|
|
|
|
if r.cmd.reason.len() > 0:
|
|
|
|
return UCICommand(kind: Unknown, reason: &"move {args[j]} is illegal or invalid ({r.cmd.reason})")
|
|
|
|
else:
|
|
|
|
return UCICommand(kind: Unknown, reason: &"move {args[j]} is illegal or invalid")
|
|
|
|
result.moves.add(args[j])
|
|
|
|
inc(j)
|
2024-04-24 10:41:01 +02:00
|
|
|
inc(i)
|
|
|
|
else:
|
|
|
|
return UCICommand(kind: Unknown, reason: &"unknown subcomponent '{command[1]}'")
|
2024-05-04 20:30:26 +02:00
|
|
|
session.position = chessboard.position
|
|
|
|
session.history = chessboard.positions
|
2024-04-24 10:41:01 +02:00
|
|
|
|
|
|
|
|
2024-05-04 20:30:26 +02:00
|
|
|
proc parseUCICommand(session: var UCISession, command: string): UCICommand =
|
2024-04-24 10:41:01 +02:00
|
|
|
## Attempts to parse the given UCI command
|
|
|
|
var cmd = command.replace("\t", "").splitWhitespace()
|
|
|
|
result = UCICommand(kind: Unknown)
|
|
|
|
var current = 0
|
|
|
|
while current < cmd.len():
|
|
|
|
case cmd[current]:
|
|
|
|
of "isready":
|
|
|
|
return UCICommand(kind: IsReady)
|
2024-04-24 19:38:54 +02:00
|
|
|
of "stop":
|
|
|
|
return UCICommand(kind: Stop)
|
2024-04-24 10:41:01 +02:00
|
|
|
of "ucinewgame":
|
|
|
|
return UCICommand(kind: NewGame)
|
|
|
|
of "quit":
|
|
|
|
return UCICommand(kind: Quit)
|
2024-05-12 15:30:10 +02:00
|
|
|
of "ponderhit":
|
|
|
|
return UCICommand(kind: PonderHit)
|
2024-04-24 10:41:01 +02:00
|
|
|
of "debug":
|
|
|
|
if current == cmd.high():
|
|
|
|
return
|
|
|
|
case cmd[current + 1]:
|
|
|
|
of "on":
|
2024-04-25 23:41:25 +02:00
|
|
|
return UCICommand(kind: Debug, on: true)
|
2024-04-24 10:41:01 +02:00
|
|
|
of "off":
|
2024-04-25 23:41:25 +02:00
|
|
|
return UCICommand(kind: Debug, on: false)
|
2024-04-24 10:41:01 +02:00
|
|
|
else:
|
|
|
|
return
|
|
|
|
of "position":
|
|
|
|
return session.handleUCIPositionCommand(cmd)
|
2024-04-24 12:38:03 +02:00
|
|
|
of "go":
|
|
|
|
return session.handleUCIGoCommand(cmd)
|
2024-04-25 23:41:25 +02:00
|
|
|
of "setoption":
|
|
|
|
result = UCICommand(kind: SetOption)
|
2024-04-27 15:00:03 +02:00
|
|
|
inc(current)
|
2024-04-25 23:41:25 +02:00
|
|
|
while current < cmd.len():
|
|
|
|
case cmd[current]:
|
|
|
|
of "name":
|
|
|
|
inc(current)
|
|
|
|
result.name = cmd[current]
|
|
|
|
of "value":
|
|
|
|
inc(current)
|
|
|
|
result.value = cmd[current]
|
|
|
|
else:
|
|
|
|
discard
|
2024-04-27 15:00:03 +02:00
|
|
|
inc(current)
|
2024-04-25 23:41:25 +02:00
|
|
|
|
2024-04-24 10:41:01 +02:00
|
|
|
else:
|
|
|
|
# Unknown UCI commands should be ignored. Attempt
|
|
|
|
# to make sense of the input regardless
|
|
|
|
inc(current)
|
|
|
|
|
|
|
|
|
2024-04-25 15:20:55 +02:00
|
|
|
proc bestMove(args: tuple[session: UCISession, command: UCICommand]) {.thread.} =
|
2024-04-24 19:38:54 +02:00
|
|
|
## Finds the best move in the current position
|
2024-05-01 16:30:21 +02:00
|
|
|
setControlCHook(proc () {.noconv.} = quit(0))
|
|
|
|
|
2024-05-05 16:07:37 +02:00
|
|
|
# Yes yes nim sure this isn't gcsafe. Now stfu and spawn a thread
|
2024-04-25 15:20:55 +02:00
|
|
|
{.cast(gcsafe).}:
|
|
|
|
var session = args.session
|
2024-05-04 20:30:26 +02:00
|
|
|
var board = newChessboard()
|
|
|
|
board.position = session.position
|
|
|
|
board.positions = session.history
|
|
|
|
let command = args.command
|
2024-04-25 15:20:55 +02:00
|
|
|
var
|
2024-05-04 20:30:26 +02:00
|
|
|
timeRemaining = (if session.position.sideToMove == White: command.wtime else: command.btime)
|
|
|
|
increment = (if session.position.sideToMove == White: command.winc else: command.binc)
|
2024-05-07 13:40:48 +02:00
|
|
|
timePerMove = command.moveTime != -1
|
|
|
|
if timePerMove:
|
|
|
|
timeRemaining = command.moveTime
|
|
|
|
increment = 0
|
2024-05-06 00:34:06 +02:00
|
|
|
elif timeRemaining == 0:
|
|
|
|
timeRemaining = int32.high()
|
2024-05-13 17:11:24 +02:00
|
|
|
var line = session.searchState[].findBestLine(timeRemaining, increment, command.depth, command.nodes, command.searchmoves, timePerMove, command.ponder)
|
|
|
|
if session.printMove[]:
|
|
|
|
if line.len() == 1:
|
2024-05-12 23:18:53 +02:00
|
|
|
echo &"bestmove {line[0].toAlgebraic()}"
|
2024-05-13 17:11:24 +02:00
|
|
|
else:
|
|
|
|
echo &"bestmove {line[0].toAlgebraic()} ponder {line[1].toAlgebraic()}"
|
2024-04-25 15:20:55 +02:00
|
|
|
|
|
|
|
|
2024-04-24 10:41:01 +02:00
|
|
|
proc startUCISession* =
|
|
|
|
## Begins listening for UCI commands
|
|
|
|
echo "id name Nimfish 0.1"
|
|
|
|
echo "id author Nocturn9x & Contributors (see LICENSE)"
|
2024-04-25 23:41:25 +02:00
|
|
|
echo "option name Hash type spin default 64 min 1 max 33554432"
|
2024-05-13 18:31:23 +02:00
|
|
|
echo "option name Threads type spin default 1 min 1 max 1"
|
2024-04-24 10:41:01 +02:00
|
|
|
echo "uciok"
|
|
|
|
var
|
|
|
|
cmd: UCICommand
|
|
|
|
cmdStr: string
|
2024-05-04 20:30:26 +02:00
|
|
|
session = UCISession(hashTableSize: 64, position: startpos())
|
2024-05-06 00:34:06 +02:00
|
|
|
# God forbid we try to use atomic ARC like it was intended. Raw pointers
|
|
|
|
# it is then... sigh
|
2024-05-13 14:51:21 +02:00
|
|
|
var
|
|
|
|
transpositionTable = cast[ptr TTable](alloc0(sizeof(TTable)))
|
|
|
|
historyTable = cast[ptr HistoryTable](alloc0(sizeof(HistoryTable)))
|
|
|
|
killerMoves = cast[ptr KillersTable](alloc0(sizeof(KillersTable)))
|
|
|
|
transpositionTable[] = newTranspositionTable(session.hashTableSize * 1024 * 1024)
|
|
|
|
session.searchState = cast[ptr SearchManager](alloc0(sizeof(SearchManager)))
|
2024-05-13 17:11:24 +02:00
|
|
|
session.searchState[] = newSearchManager(session.position, session.history, transpositionTable, historyTable, killerMoves)
|
|
|
|
session.printMove = cast[ptr bool](alloc0(sizeof(bool)))
|
2024-05-13 14:51:21 +02:00
|
|
|
# Initialize history table
|
2024-05-12 14:35:52 +02:00
|
|
|
for color in PieceColor.White..PieceColor.Black:
|
|
|
|
for i in Square(0)..Square(63):
|
|
|
|
for j in Square(0)..Square(63):
|
2024-05-13 14:51:21 +02:00
|
|
|
historyTable[color][i][j] = Score(0)
|
|
|
|
# Initialize killer move table
|
2024-05-12 14:35:52 +02:00
|
|
|
for i in 0..<MAX_DEPTH:
|
|
|
|
for j in 0..<NUM_KILLERS:
|
2024-05-13 14:51:21 +02:00
|
|
|
killerMoves[i][j] = nullMove()
|
2024-05-12 01:02:47 +02:00
|
|
|
session.position = startpos()
|
|
|
|
session.history = @[]
|
2024-05-04 20:30:26 +02:00
|
|
|
# Fun fact, nim doesn't collect the memory of thread vars. Another stupid fucking design pitfall
|
|
|
|
# of nim's AWESOME threading model. Someone is getting a pipebomb in their mailbox about this, mark
|
|
|
|
# my fucking words. (for legal purposes THAT IS A JOKE). See https://github.com/nim-lang/Nim/issues/23165
|
|
|
|
# The solution? Just reuse the same thread object so the leak is isolated to a single thread.
|
|
|
|
# Also the nim allocator has internal races, so we gotta lose performance by using -d:useMalloc instead.
|
|
|
|
# At least mimalloc exists.
|
|
|
|
# THANKS ARAQ
|
|
|
|
var searchThread: Thread[tuple[session: UCISession, command: UCICommand]]
|
2024-04-24 10:41:01 +02:00
|
|
|
while true:
|
|
|
|
try:
|
|
|
|
cmdStr = readLine(stdin).strip(leading=true, trailing=true, chars={'\t', ' '})
|
|
|
|
if cmdStr.len() == 0:
|
|
|
|
if session.debug:
|
|
|
|
echo "info string received empty input, ignoring it"
|
|
|
|
continue
|
|
|
|
cmd = session.parseUCICommand(cmdStr)
|
|
|
|
if cmd.kind == Unknown:
|
|
|
|
if session.debug:
|
|
|
|
echo &"info string received unknown or invalid command '{cmdStr}' -> {cmd.reason}"
|
|
|
|
continue
|
|
|
|
if session.debug:
|
|
|
|
echo &"info string received command '{cmdStr}' -> {cmd}"
|
|
|
|
case cmd.kind:
|
|
|
|
of Quit:
|
|
|
|
quit(0)
|
|
|
|
of IsReady:
|
|
|
|
echo "readyok"
|
|
|
|
of Debug:
|
2024-04-25 23:41:25 +02:00
|
|
|
session.debug = cmd.on
|
2024-04-24 10:41:01 +02:00
|
|
|
of NewGame:
|
2024-05-13 17:11:24 +02:00
|
|
|
if session.debug:
|
|
|
|
echo &"info string clearing out TT of size {session.hashTableSize} MiB"
|
|
|
|
transpositionTable[].clear()
|
2024-05-13 14:51:21 +02:00
|
|
|
# Re-Initialize history table
|
2024-05-06 23:48:40 +02:00
|
|
|
for color in PieceColor.White..PieceColor.Black:
|
2024-05-12 01:02:47 +02:00
|
|
|
for i in Square(0)..Square(63):
|
|
|
|
for j in Square(0)..Square(63):
|
2024-05-13 14:51:21 +02:00
|
|
|
historyTable[color][i][j] = Score(0)
|
|
|
|
# Re-nitialize killer move table
|
2024-05-12 01:02:47 +02:00
|
|
|
for i in 0..<MAX_DEPTH:
|
|
|
|
for j in 0..<NUM_KILLERS:
|
2024-05-13 14:51:21 +02:00
|
|
|
killerMoves[i][j] = nullMove()
|
2024-05-12 15:30:10 +02:00
|
|
|
of PonderHit:
|
|
|
|
if session.debug:
|
2024-05-13 14:51:21 +02:00
|
|
|
echo "info string ponder move has ben hit"
|
|
|
|
if not session.searchState[].isSearching():
|
2024-05-12 15:30:10 +02:00
|
|
|
continue
|
2024-05-13 17:11:24 +02:00
|
|
|
session.searchState[].stopPondering()
|
2024-05-12 15:30:10 +02:00
|
|
|
if session.debug:
|
2024-05-13 17:11:24 +02:00
|
|
|
echo "info string switched to normal search"
|
2024-05-12 01:02:47 +02:00
|
|
|
of Go:
|
2024-05-13 17:11:24 +02:00
|
|
|
session.printMove[] = true
|
|
|
|
if not cmd.ponder and session.searchState[].isPondering():
|
|
|
|
session.searchState[].stopPondering()
|
|
|
|
else:
|
|
|
|
when not defined(historyPenalty):
|
|
|
|
# Scale our history coefficients
|
|
|
|
for color in PieceColor.White..PieceColor.Black:
|
|
|
|
for source in Square(0)..Square(63):
|
|
|
|
for target in Square(0)..Square(63):
|
|
|
|
historyTable[color][source][target] = historyTable[color][source][target] div 2
|
|
|
|
if searchThread.running:
|
|
|
|
joinThread(searchThread)
|
|
|
|
createThread(searchThread, bestMove, (session, cmd))
|
|
|
|
if session.debug:
|
|
|
|
echo "info string search started"
|
2024-04-25 15:20:55 +02:00
|
|
|
of Stop:
|
2024-05-13 14:51:21 +02:00
|
|
|
if not session.searchState[].isSearching():
|
2024-05-06 23:48:40 +02:00
|
|
|
continue
|
2024-05-13 14:51:21 +02:00
|
|
|
session.searchState[].stop()
|
2024-05-04 20:30:26 +02:00
|
|
|
joinThread(searchThread)
|
2024-05-02 14:39:46 +02:00
|
|
|
if session.debug:
|
|
|
|
echo "info string search stopped"
|
2024-04-25 23:41:25 +02:00
|
|
|
of SetOption:
|
2024-05-13 14:51:21 +02:00
|
|
|
if session.searchState[].isSearching():
|
2024-05-04 20:30:26 +02:00
|
|
|
# Cannot set options during search
|
|
|
|
continue
|
2024-04-25 23:41:25 +02:00
|
|
|
case cmd.name:
|
|
|
|
of "Hash":
|
2024-05-13 14:51:21 +02:00
|
|
|
let newSize = cmd.value.parseBiggestUInt()
|
|
|
|
if newSize < 1:
|
|
|
|
continue
|
|
|
|
if transpositionTable[].size() > 0:
|
2024-05-12 01:02:47 +02:00
|
|
|
if session.debug:
|
2024-05-13 14:51:21 +02:00
|
|
|
echo &"info string resizing TT from {session.hashTableSize} MiB To {newSize} MiB"
|
|
|
|
transpositionTable[].resize(newSize * 1024 * 1024)
|
|
|
|
session.hashTableSize = newSize
|
2024-04-25 23:41:25 +02:00
|
|
|
if session.debug:
|
|
|
|
echo &"info string set TT hash table size to {session.hashTableSize} MiB"
|
|
|
|
else:
|
|
|
|
discard
|
2024-04-24 10:41:01 +02:00
|
|
|
of Position:
|
2024-05-13 17:11:24 +02:00
|
|
|
if session.searchState[].isPondering():
|
|
|
|
session.printMove[] = false
|
|
|
|
session.searchState[].stopPondering()
|
|
|
|
session.searchState[].stop()
|
|
|
|
joinThread(searchThread)
|
|
|
|
session.searchState[].board.position = session.position
|
|
|
|
session.searchState[].board.positions = session.history
|
2024-04-24 10:41:01 +02:00
|
|
|
else:
|
|
|
|
discard
|
|
|
|
except IOError:
|
|
|
|
if session.debug:
|
|
|
|
echo "info string I/O error while reading from stdin, exiting"
|
|
|
|
echo ""
|
|
|
|
quit(0)
|
|
|
|
except EOFError:
|
|
|
|
if session.debug:
|
|
|
|
echo "info string EOF received while reading from stdin, exiting"
|
|
|
|
echo ""
|
|
|
|
quit(0)
|