Bot-Man-Toe/pyclient/client.py

106 lines
3.3 KiB
Python

"""
This module hosts the PyClient class. You can use this class to simulate
games among mulltiple agents.
"""
from __future__ import annotations
from agents import Agent
from .games import Game
from .replay import GameReplay, Turn
from dataclasses import dataclass
from typing import Any, Generator
@dataclass(frozen=True)
class PyClient:
"""
Host games among multiple agents.
"""
debug : bool
def gen_game(
self,
players : list[Agent],
start : Game,
max_turns : int | None = None,
) -> Generator[Turn, None, None]:
"""
Generate a game by polling the players.
:param players: All players that wish to participate.
:type players: list[Agent]
:param start: The start state of the game.
:type start: Game
:param max_turns: The maximum number of turns a game may take
:type max_turns: int
:return: A generator that yields turns.
:rtype: Generator[Turn, None, None]
:raises ValueError: The game is taking too long.
"""
current_state = start
turns_taken = 0
while current_state.winner() is None:
if max_turns is not None and turns_taken >= max_turns:
raise ValueError(
f"Turn limit exceeded: played for {turns_taken} turns and still needed more."
)
turns_taken += 1
player = current_state.player_to_move()
if len(players) < player:
# Player not found! Make a default move.
current_state = current_state.move_default()
yield Turn(action={}, player=player, state=current_state)
else:
agent = players[player - 1]
on_move = agent.get_action(
game=current_state.game_name(),
action=current_state.action_name(),
)
# Calculate move
if on_move is None:
payload = {}
else:
try:
payload = on_move(current_state.as_seen_by(player=player))
except Exception:
payload = {}
current_state = current_state.move(payload=payload)
yield Turn(action=payload, player=player, state=current_state)
def play_game(
self,
players : list[Agent],
start : Game,
max_turns : int | None = None,
) -> GameReplay:
"""
Generate a game by polling the players. Collect all moves in a
summary.
:param players: All players that wish to participate.
:type players: list[Agent]
:param start: The start state of the game.
:type start: Game
:param max_turns: The maximum number of turns a game may take
:type max_turns: int
:return: Summary describing how the game went.
:rtype: GameReplay
:raises ValueError: The game is taking too long.
"""
return GameReplay(
game_name=start.game_name(),
start=start,
turns=list(self.gen_game(
players=players, start=start, max_turns=max_turns
)),
)