Build PyClient turn generator

bram/deployment
Bram van den Heuvel 2026-06-07 13:28:13 +02:00
parent 17e12a12ab
commit 10e808751a
2 changed files with 52 additions and 23 deletions

View File

@ -1,6 +1,6 @@
"""Public client entry points.""" """Public client entry points."""
from typing import List, Optional from typing import Any, Generator, List, Optional
import requests import requests
@ -39,12 +39,28 @@ class PyClient:
except (requests.exceptions.RequestException, ValueError): except (requests.exceptions.RequestException, ValueError):
return None return None
def play_game(self, name : str, game, timeout : float = 1.0): 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 a given game. Ask the registered agents to participate. Play game. Return the results as they've been calculated.
: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.
""" """
agents = [ agent for agent in self.agents if name in agent.games ] agents = [
states = [] ServerAgent(url=url, name="", games={}, debug=self.debug)
for url in urls
]
current_state = game.empty() current_state = game.empty()
@ -52,25 +68,38 @@ class PyClient:
player : int = current_state.player_to_move() player : int = current_state.player_to_move()
if len(agents) < player: if len(agents) < player:
raise KeyError( if not move_default_if_nonexistent:
f"{player} players for game {name}, found only {len(agents)}" 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
else:
agent = agents[player-1]
payload = agent.poll(
game=(name + "/" + current_state.action_name()).strip("/"),
payload=current_state.as_seen_by(player),
timeout=timeout,
) )
# Ask for move # Calculate move
agent = agents[player-1] current_state = current_state.move(payload)
payload = agent.poll(
game=current_state.action_name(),
payload=current_state.as_seen_by(player),
timeout=timeout,
)
# Calculate move yield player, payload or {}, current_state
current_state = current_state.move(payload)
# Save the results def play_game(self, name : str, game, timeout : float = 1.0) -> list[tuple[int, dict[str, Any], Any]]:
states.append((player, payload, current_state)) """
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: def verify_host(self, url: str) -> bool:
""" """

View File

@ -169,8 +169,8 @@ class TicTacToe:
f"Index to write to should be 1-9, not {index}" f"Index to write to should be 1-9, not {index}"
) )
def action_name(self): def action_name(self) -> str:
return "/tic-tac-toe" return ""
def as_seen_by(self, player : int) -> Dict[str, Any]: def as_seen_by(self, player : int) -> Dict[str, Any]:
""" """