Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file not shown.
94 changes: 63 additions & 31 deletions python_easy_chess_gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,8 @@ class RunEngine(threading.Thread):
def __init__(self, eng_queue, engine_config_file, engine_path_and_file,
engine_id_name, max_depth=MAX_DEPTH,
base_ms=300000, inc_ms=1000, tc_type='fischer',
period_moves=0, is_stream_search_info=True):
period_moves=0, is_stream_search_info=True,
existing_engine=None):
"""
Run engine as opponent or as adviser.

Expand All @@ -383,6 +384,8 @@ def __init__(self, eng_queue, engine_config_file, engine_path_and_file,
:param engine_path_and_file:
:param engine_id_name:
:param max_depth:
:param existing_engine: An existing chess.engine.SimpleEngine instance
to reuse instead of spawning a new process.
"""
threading.Thread.__init__(self)
self._kill = threading.Event()
Expand All @@ -398,7 +401,7 @@ def __init__(self, eng_queue, engine_config_file, engine_path_and_file,
self.nps = 0
self.max_depth = max_depth
self.eng_queue = eng_queue
self.engine = None
self.engine = existing_engine
self.board = None
self.analysis = is_stream_search_info
self.is_nomove_number_in_variation = True
Expand Down Expand Up @@ -468,32 +471,34 @@ def run(self):

If there is error we still send bestmove None.
"""
folder = Path(self.engine_path_and_file)
folder = folder.parents[0]
# Reuse existing engine if provided
if self.engine is None:
folder = Path(self.engine_path_and_file)
folder = folder.parents[0]

try:
if sys_os == 'Windows':
self.engine = chess.engine.SimpleEngine.popen_uci(
self.engine_path_and_file, cwd=folder,
creationflags=subprocess.CREATE_NO_WINDOW)
else:
self.engine = chess.engine.SimpleEngine.popen_uci(
self.engine_path_and_file, cwd=folder)
except chess.engine.EngineTerminatedError:
logging.warning('Failed to start {}.'.format(self.engine_path_and_file))
self.eng_queue.put('bestmove {}'.format(self.bm))
return
except Exception:
logging.exception('Failed to start {}.'.format(
self.engine_path_and_file))
self.eng_queue.put('bestmove {}'.format(self.bm))
return
try:
if sys_os == 'Windows':
self.engine = chess.engine.SimpleEngine.popen_uci(
self.engine_path_and_file, cwd=folder,
creationflags=subprocess.CREATE_NO_WINDOW)
else:
self.engine = chess.engine.SimpleEngine.popen_uci(
self.engine_path_and_file, cwd=folder)
except chess.engine.EngineTerminatedError:
logging.warning('Failed to start {}.'.format(self.engine_path_and_file))
self.eng_queue.put('bestmove {}'.format(self.bm))
return
except Exception:
logging.exception('Failed to start {}.'.format(
self.engine_path_and_file))
self.eng_queue.put('bestmove {}'.format(self.bm))
return

# Set engine option values
try:
self.configure_engine()
except Exception:
logging.exception('Failed to configure engine.')
# Set engine option values
try:
self.configure_engine()
except Exception:
logging.exception('Failed to configure engine.')

# Set search limits
if self.tc_type == 'delay':
Expand Down Expand Up @@ -629,14 +634,25 @@ def run(self):
logging.info(f'bestmove {self.bm}')

def quit_engine(self):
"""Quit engine."""
"""Quit engine.

Safe to call multiple times; subsequent calls are no-ops.
"""
if self.engine is None:
return
logging.info('quit engine')
try:
self.engine.quit()
except AttributeError:
logging.info('AttributeError, self.engine is already None')
except Exception:
logging.exception('Failed to quit engine.')
self.engine = None

def get_engine(self):
"""Return the engine instance without quitting it.

This allows the engine process to be reused across moves.
"""
return self.engine

def short_variation_san(self):
"""Returns variation in san but without move numbers."""
Expand Down Expand Up @@ -1695,6 +1711,9 @@ def play_game(self, window: sg.Window, board: chess.Board):
is_search_stop_for_resign = False
is_search_stop_for_user_wins = False
is_search_stop_for_user_draws = False

# Engine instance that persists across moves
persistent_engine = None
is_hide_book1 = True
is_hide_book2 = True
is_hide_search_info = True
Expand Down Expand Up @@ -2179,7 +2198,8 @@ def play_game(self, window: sg.Window, board: chess.Board):
self.queue, self.engine_config_file, self.opp_path_and_file,
self.opp_id_name, self.max_depth, engine_timer.base,
engine_timer.inc, tc_type=engine_timer.tc_type,
period_moves=board.fullmove_number
period_moves=board.fullmove_number,
existing_engine=persistent_engine
)
search.get_board(board)
search.daemon = True
Expand Down Expand Up @@ -2287,7 +2307,8 @@ def play_game(self, window: sg.Window, board: chess.Board):
break

search.join()
search.quit_engine()
# Keep engine alive for reuse; retrieve instance
persistent_engine = search.get_engine()
is_book_from_gui = False

# If engine failed to send a legal move
Expand Down Expand Up @@ -2371,6 +2392,17 @@ def play_game(self, window: sg.Window, board: chess.Board):

# Auto-save game
logging.info('Saving game automatically')

# Quit the persistent engine now that the game is over or
# the user is exiting play mode (e.g. neutral, new game, resign).
if persistent_engine is not None:
logging.info('Quitting persistent engine at end of game')
try:
persistent_engine.quit()
except Exception:
logging.exception('Failed to quit persistent engine.')
finally:
persistent_engine = None
if is_user_resigns:
self.game.headers['Result'] = '0-1' if self.is_user_white else '1-0'
self.game.headers['Termination'] = '{} resigns'.format(
Expand Down