Skip to content

Commit 0623d86

Browse files
Copilotfsmosca
andauthored
Keep engine alive between moves, quit only on game over or neutral mode (#56)
Agent-Logs-Url: https://github.com/fsmosca/Python-Easy-Chess-GUI/sessions/1825c5bc-472f-4fcf-95c4-323effd52f19 Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: fsmosca <22366935+fsmosca@users.noreply.github.com>
1 parent feb54f2 commit 0623d86

2 files changed

Lines changed: 63 additions & 31 deletions

File tree

135 KB
Binary file not shown.

python_easy_chess_gui.py

Lines changed: 63 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -374,7 +374,8 @@ class RunEngine(threading.Thread):
374374
def __init__(self, eng_queue, engine_config_file, engine_path_and_file,
375375
engine_id_name, max_depth=MAX_DEPTH,
376376
base_ms=300000, inc_ms=1000, tc_type='fischer',
377-
period_moves=0, is_stream_search_info=True):
377+
period_moves=0, is_stream_search_info=True,
378+
existing_engine=None):
378379
"""
379380
Run engine as opponent or as adviser.
380381
@@ -383,6 +384,8 @@ def __init__(self, eng_queue, engine_config_file, engine_path_and_file,
383384
:param engine_path_and_file:
384385
:param engine_id_name:
385386
:param max_depth:
387+
:param existing_engine: An existing chess.engine.SimpleEngine instance
388+
to reuse instead of spawning a new process.
386389
"""
387390
threading.Thread.__init__(self)
388391
self._kill = threading.Event()
@@ -398,7 +401,7 @@ def __init__(self, eng_queue, engine_config_file, engine_path_and_file,
398401
self.nps = 0
399402
self.max_depth = max_depth
400403
self.eng_queue = eng_queue
401-
self.engine = None
404+
self.engine = existing_engine
402405
self.board = None
403406
self.analysis = is_stream_search_info
404407
self.is_nomove_number_in_variation = True
@@ -468,32 +471,34 @@ def run(self):
468471
469472
If there is error we still send bestmove None.
470473
"""
471-
folder = Path(self.engine_path_and_file)
472-
folder = folder.parents[0]
474+
# Reuse existing engine if provided
475+
if self.engine is None:
476+
folder = Path(self.engine_path_and_file)
477+
folder = folder.parents[0]
473478

474-
try:
475-
if sys_os == 'Windows':
476-
self.engine = chess.engine.SimpleEngine.popen_uci(
477-
self.engine_path_and_file, cwd=folder,
478-
creationflags=subprocess.CREATE_NO_WINDOW)
479-
else:
480-
self.engine = chess.engine.SimpleEngine.popen_uci(
481-
self.engine_path_and_file, cwd=folder)
482-
except chess.engine.EngineTerminatedError:
483-
logging.warning('Failed to start {}.'.format(self.engine_path_and_file))
484-
self.eng_queue.put('bestmove {}'.format(self.bm))
485-
return
486-
except Exception:
487-
logging.exception('Failed to start {}.'.format(
488-
self.engine_path_and_file))
489-
self.eng_queue.put('bestmove {}'.format(self.bm))
490-
return
479+
try:
480+
if sys_os == 'Windows':
481+
self.engine = chess.engine.SimpleEngine.popen_uci(
482+
self.engine_path_and_file, cwd=folder,
483+
creationflags=subprocess.CREATE_NO_WINDOW)
484+
else:
485+
self.engine = chess.engine.SimpleEngine.popen_uci(
486+
self.engine_path_and_file, cwd=folder)
487+
except chess.engine.EngineTerminatedError:
488+
logging.warning('Failed to start {}.'.format(self.engine_path_and_file))
489+
self.eng_queue.put('bestmove {}'.format(self.bm))
490+
return
491+
except Exception:
492+
logging.exception('Failed to start {}.'.format(
493+
self.engine_path_and_file))
494+
self.eng_queue.put('bestmove {}'.format(self.bm))
495+
return
491496

492-
# Set engine option values
493-
try:
494-
self.configure_engine()
495-
except Exception:
496-
logging.exception('Failed to configure engine.')
497+
# Set engine option values
498+
try:
499+
self.configure_engine()
500+
except Exception:
501+
logging.exception('Failed to configure engine.')
497502

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

631636
def quit_engine(self):
632-
"""Quit engine."""
637+
"""Quit engine.
638+
639+
Safe to call multiple times; subsequent calls are no-ops.
640+
"""
641+
if self.engine is None:
642+
return
633643
logging.info('quit engine')
634644
try:
635645
self.engine.quit()
636-
except AttributeError:
637-
logging.info('AttributeError, self.engine is already None')
638646
except Exception:
639647
logging.exception('Failed to quit engine.')
648+
self.engine = None
649+
650+
def get_engine(self):
651+
"""Return the engine instance without quitting it.
652+
653+
This allows the engine process to be reused across moves.
654+
"""
655+
return self.engine
640656

641657
def short_variation_san(self):
642658
"""Returns variation in san but without move numbers."""
@@ -1695,6 +1711,9 @@ def play_game(self, window: sg.Window, board: chess.Board):
16951711
is_search_stop_for_resign = False
16961712
is_search_stop_for_user_wins = False
16971713
is_search_stop_for_user_draws = False
1714+
1715+
# Engine instance that persists across moves
1716+
persistent_engine = None
16981717
is_hide_book1 = True
16991718
is_hide_book2 = True
17001719
is_hide_search_info = True
@@ -2179,7 +2198,8 @@ def play_game(self, window: sg.Window, board: chess.Board):
21792198
self.queue, self.engine_config_file, self.opp_path_and_file,
21802199
self.opp_id_name, self.max_depth, engine_timer.base,
21812200
engine_timer.inc, tc_type=engine_timer.tc_type,
2182-
period_moves=board.fullmove_number
2201+
period_moves=board.fullmove_number,
2202+
existing_engine=persistent_engine
21832203
)
21842204
search.get_board(board)
21852205
search.daemon = True
@@ -2287,7 +2307,8 @@ def play_game(self, window: sg.Window, board: chess.Board):
22872307
break
22882308

22892309
search.join()
2290-
search.quit_engine()
2310+
# Keep engine alive for reuse; retrieve instance
2311+
persistent_engine = search.get_engine()
22912312
is_book_from_gui = False
22922313

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

23722393
# Auto-save game
23732394
logging.info('Saving game automatically')
2395+
2396+
# Quit the persistent engine now that the game is over or
2397+
# the user is exiting play mode (e.g. neutral, new game, resign).
2398+
if persistent_engine is not None:
2399+
logging.info('Quitting persistent engine at end of game')
2400+
try:
2401+
persistent_engine.quit()
2402+
except Exception:
2403+
logging.exception('Failed to quit persistent engine.')
2404+
finally:
2405+
persistent_engine = None
23742406
if is_user_resigns:
23752407
self.game.headers['Result'] = '0-1' if self.is_user_white else '1-0'
23762408
self.game.headers['Termination'] = '{} resigns'.format(

0 commit comments

Comments
 (0)