diff --git a/pyclient/__init__.py b/pyclient/__init__.py index 0bed8f7..6d92dd9 100644 --- a/pyclient/__init__.py +++ b/pyclient/__init__.py @@ -1,6 +1,6 @@ """Public client entry points.""" -from typing import List, Optional +from typing import Any, Generator, List, Optional import requests @@ -38,13 +38,29 @@ class PyClient: return ServerAgent.from_url(url, debug=self.debug) except (requests.exceptions.RequestException, ValueError): return None + + def gen_game(self, name : str, game : Any, urls : list[str], timeout : float = 1.0, move_default_if_nonexistent : bool = False) -> Generator[tuple[int, dict[str, Any], Any], None, None]: + """ + Play game. Return the results as they've been calculated. - def play_game(self, name : str, game, timeout : float = 1.0): + :param name: Unique identifier string name for the game + :type name: str + :param game: Game to be played + :type game: Any + :param urls: List of URLs where players are located + :type urls: list[str] + :param timeout: Maximum time in seconds to wait for a player to respond + :type timeout: float + :param move_default_if_nonexistent: If a required player doesn't exist, raise an error. When this is set to true, however, the program will instead assume a player that always takes the default option. + :type move_default_if_nonexistent: bool + :return: Generator of each turn consisting of a player, what action it took and what that resulted in. + :rtype: Generator[tuple[int, dict[str, Any], Any], None, None] + :raises KeyError: The number of URLs provided is too low, the game requires more players. """ - Play a given game. Ask the registered agents to participate. - """ - agents = [ agent for agent in self.agents if name in agent.games ] - states = [] + agents = [ + ServerAgent(url=url, name="", games={}, debug=self.debug) + for url in urls + ] current_state = game.empty() @@ -52,25 +68,38 @@ class PyClient: player : int = current_state.player_to_move() if len(agents) < player: - raise KeyError( - f"{player} players for game {name}, found only {len(agents)}" - ) + if not move_default_if_nonexistent: + raise KeyError( + f"Game requires at least {player} players to exist, found only {len(agents)}" + ) + + current_state = current_state.move_default() + + yield player, {}, current_state - # Ask for move - agent = agents[player-1] - payload = agent.poll( - game=current_state.action_name(), - payload=current_state.as_seen_by(player), - timeout=timeout, - ) + else: + agent = agents[player-1] + payload = agent.poll( + game=(name + "/" + current_state.action_name()).strip("/"), + payload=current_state.as_seen_by(player), + timeout=timeout, + ) - # Calculate move - current_state = current_state.move(payload) + # Calculate move + current_state = current_state.move(payload) - # Save the results - states.append((player, payload, current_state)) + yield player, payload or {}, current_state + + def play_game(self, name : str, game, timeout : float = 1.0) -> list[tuple[int, dict[str, Any], Any]]: + """ + Play a given game. Ask the registered agents to participate. + """ + urls = [ agent.url for agent in self.agents if name in agent.games ] - return states + return list(self.gen_game( + name=name, game=game, urls=urls, timeout=timeout, + move_default_if_nonexistent=False, + )) def verify_host(self, url: str) -> bool: """ diff --git a/pyclient/games/tic_tac_toe.py b/pyclient/games/tic_tac_toe.py index a720287..8d268ad 100644 --- a/pyclient/games/tic_tac_toe.py +++ b/pyclient/games/tic_tac_toe.py @@ -169,8 +169,8 @@ class TicTacToe: f"Index to write to should be 1-9, not {index}" ) - def action_name(self): - return "/tic-tac-toe" + def action_name(self) -> str: + return "" def as_seen_by(self, player : int) -> Dict[str, Any]: """