diff --git a/elo.py b/elo.py index 347a4f2..973cb0b 100644 --- a/elo.py +++ b/elo.py @@ -3,7 +3,7 @@ """ from elo_tracker import EloTracker -from pyclient.games import TicTacToe +from pyclient.games import RoyalGameOfUr, TicTacToe GAME_FILE = "games.jsonl" PLAYER_FILE = "known_players.json" @@ -21,6 +21,12 @@ def main() -> int: player_count=2, ) + tracker.start_periodic_matches( + game=RoyalGameOfUr.empty(), + interval_seconds=9 * 60, + player_count=2, + ) + try: tracker.start_server(import_name=__name__) except KeyboardInterrupt: diff --git a/elo_tracker/app.py b/elo_tracker/app.py index 92efb57..82f436e 100644 --- a/elo_tracker/app.py +++ b/elo_tracker/app.py @@ -16,6 +16,7 @@ from typing import Any, Sequence import agents import pyclient +import time from pyclient.games import FinishState, Game @@ -280,7 +281,7 @@ class EloTracker: # Threading variables self.__lock = threading.RLock() self.__scheduler_stop = threading.Event() - self.__scheduler_thread: threading.Thread | None = None + self.__scheduler_threads: list[threading.Thread] = [] # Immutable values self.debug: bool = debug @@ -551,7 +552,11 @@ class EloTracker: :rtype: bool """ with self.__lock: - return self.__scheduler_thread is not None and self.__scheduler_thread.is_alive() + for thread in self.__scheduler_threads: + if thread.is_alive(): + return True + else: + return False def load_players(self) -> None: """ @@ -665,33 +670,40 @@ class EloTracker: raise ValueError("player_count must be greater than one.") with self.__lock: - if self.is_running_periodic_matches(): - self.stop_periodic_matches() - self.__scheduler_stop.clear() - self.__scheduler_thread = threading.Thread( + thread = threading.Thread( target=self.__scheduler_loop, args=(game, interval_seconds, player_count), daemon=True, ) - self.__scheduler_thread.start() + self.__scheduler_threads.append(thread) + thread.start() def stop_periodic_matches(self) -> None: """ Stop the periodic match scheduler and wait briefly for it to exit. """ - thread: threading.Thread | None + max_wait_time_s = 5 + now = time.time() + time_left = lambda : max(0.1, now + max_wait_time_s - time.time()) + to_remove : list[threading.Thread] = [] + # Announce that all threads need to be stopped with self.__lock: self.__scheduler_stop.set() - thread = self.__scheduler_thread + to_remove = [ thread for thread in self.__scheduler_threads ] - if thread is not None: - thread.join(timeout=5) + # Then, quietly remove all existing threads + for thread in self.__scheduler_threads: + if thread is not None: + thread.join(timeout=time_left()) + # Remove all stopped threads + # Keep in mind that another threads might've added new threads in the + # meantime. with self.__lock: - if self.__scheduler_thread is thread: - self.__scheduler_thread = None + for thread in to_remove: + self.__scheduler_threads.remove(thread) def get_json_players(self, game_name : str) -> list[dict[str, Any]]: """