""" 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 )), )