Source code for miniosl.usi_process
"""run external usi engine"""
import copy
import logging
import re
import subprocess
import weakref
from collections.abc import Sequence
info_string = re.compile(r'^info\s+string.*$')
info_regexp = re.compile(
r'^info.*\s+score\s+cp\s+([0-9-+]+).*\s+pv\s+((\s?[A-Za-z0-9+*]+)*)\s*$'
)
depth_regexp = re.compile(r'^info.*\s+depth\s+([0-9]+)')
nodes_regexp = re.compile(r'^info.*\s+nodes\s+([0-9]+)')
bestmove_regexp = re.compile(r'^bestmove\s+([A-Za-z0-9+*]+)\s*(ponder\s.*)*$')
idname_regexp = re.compile(r'^id\s+name\s+(.*)\s*$')
logger = logging.getLogger(__name__)
def close_session(proc):
if not proc:
return False
logger.debug('closing process')
proc.stdin.write('quit\n')
proc.stdin.close()
if proc.wait() != 0:
logger.error('close error')
return False
return True
[docs]
class UsiProcess:
"""Proxy for usi process
create process, initilaze its options and wait until ``readyok``
:param path_and_args: list of path and argments
"""
debug = False
def __init__(
self,
path_and_args: list[str],
setoptions: Sequence[str] = (
'setoption name Threads value 1',
'setoption name USI_Hash value 16',
),
cwd: str | None = None,
):
self.path_and_args = copy.copy(path_and_args)
self.name = None
self.usi = subprocess.Popen(
path_and_args,
bufsize=1,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
cwd=cwd,
close_fds=True,
universal_newlines=True,
)
self.writeline('usi')
line = self.readline()
options_avail = []
while line != 'usiok':
if line.startswith('option name'):
options_avail.append(line)
else:
match = re.match(idname_regexp, line)
if match:
self.name = match.group(1)
line = self.readline()
for line in setoptions:
self.writeline(line)
self.writeline('isready')
ready = self.readline()
while re.match(info_string, ready):
ready = self.readline()
if not ready.startswith('readyok'):
err = f'readyok != {ready}'
logger.critical(err)
raise ValueError(err)
# for graceful exit
# https://docs.python.org/3/library/weakref.html#finalizer-objects
self._finalizer = weakref.finalize(self, close_session, self.usi)
def readline(self) -> str:
assert self.usi.stdout
line = self.usi.stdout.readline().rstrip()
if self.debug:
logger.debug(f'< {line}')
return line
def writeline(self, line) -> None:
if self.debug:
logger.debug(f'> {line}')
assert self.usi.stdin
self.usi.stdin.write(line + '\n')
[docs]
def close(self) -> bool:
"""finish process"""
ret = self._finalizer.alive
self._finalizer()
return ret
[docs]
def search(self, position: str, limit: str) -> dict:
"""search: give position with go commands, and wait ``bestmove``
Returns hash contains "info"
:param position: e.g., ``'startpos'``
:param limit: e.g., ``'byoyomi 1000'``
"""
self.writeline(f'position {position}')
self.writeline(f'go {limit}')
result = {}
last_depth = None
last_node_count = None
best_move_identified = False
while not best_move_identified:
line = self.readline()
match_depth = re.match(depth_regexp, line)
if match_depth:
last_depth = match_depth.group(1)
match_nodes = re.match(nodes_regexp, line)
if match_nodes:
last_node_count = match_nodes.group(1)
match_info = False if 'rep' in line else re.match(info_regexp, line)
if match_info:
score = match_info.group(1)
pv = match_info.group(2)
result['cp'] = score
result['pv'] = pv
if last_depth:
result['d'] = last_depth
if last_node_count:
result['n'] = last_node_count
match_bestmove = re.match(bestmove_regexp, line)
if match_bestmove:
result['move'] = match_bestmove.group(1)
best_move_identified = True
return result