Create generalized Agent class
parent
bb3bb36665
commit
7b0f147a89
|
|
@ -0,0 +1,14 @@
|
|||
"""
|
||||
Agents are players that can participate in a game. THey contain a unified
|
||||
API that allows other systems to apply to them for communication.
|
||||
For example:
|
||||
|
||||
1. The PyClient can locally run a game with agents as participants.
|
||||
2. A PyServer can host a web server and asks a specific agent to respond.
|
||||
"""
|
||||
|
||||
from .agent import Agent
|
||||
|
||||
__all__ = [
|
||||
"Agent"
|
||||
]
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
"""
|
||||
The agent module hosts the base class of all agents. It dictates how
|
||||
agents are expected to behave.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable
|
||||
|
||||
# Communication type that the players communicate in
|
||||
Payload = dict[str, Any]
|
||||
|
||||
# Function that the agent is expected to communicate with.
|
||||
# The ActionFunction takes a Payload as input and returns a Payload of its own.
|
||||
ActionFunction = Callable[[Payload], Payload]
|
||||
|
||||
GameInfo = tuple[Payload, dict[str, ActionFunction]]
|
||||
|
||||
class Agent:
|
||||
"""
|
||||
Base class for all agents to communicate in.
|
||||
"""
|
||||
|
||||
# Local variables
|
||||
author : str
|
||||
name : str
|
||||
profile : Payload
|
||||
registered_games : dict[str, tuple[Payload, dict[str, ActionFunction]]]
|
||||
version : str | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name : str,
|
||||
author : str,
|
||||
version : str | None = None,
|
||||
profile : Payload = {},
|
||||
) -> None:
|
||||
"""
|
||||
Create an instance of the agent.
|
||||
|
||||
Note that the base class expects quite a lot of variables for the
|
||||
initialization, whereas the subclasses are expected to require
|
||||
no inputs for initialization. It is the subclasses' responsibility
|
||||
to override the current function by inserting its own values.
|
||||
|
||||
:param name: String value giving the player a name.
|
||||
:type name: str
|
||||
:param author: String value representing the player's designer or programmer.
|
||||
:type author: str
|
||||
:param version: Version of the player, in case an update changes its behavior.
|
||||
:type version: str | None
|
||||
:param profile: Custom profile pertaining to the player.
|
||||
:type profile: dict[str, Any]
|
||||
"""
|
||||
|
||||
# Register basic (required) variables
|
||||
self.author = author
|
||||
self.name = name
|
||||
self.profile = profile
|
||||
self.version = version
|
||||
|
||||
# Have the subclass register games
|
||||
self.registered_games = {}
|
||||
|
||||
def add_game(
|
||||
self,
|
||||
name : str,
|
||||
actions : dict[str, ActionFunction],
|
||||
profile : Payload,
|
||||
required_actions : list[str] = [],
|
||||
) -> None:
|
||||
"""
|
||||
Add a new game to the player. Replaces any prior definition for the
|
||||
game.
|
||||
|
||||
:param name: Game name
|
||||
:type name: str
|
||||
:param actions: Dictionary containing all actions the player is willing to take.
|
||||
:type actions: dict[str, Callable[[dict[str, Any]], dict[str, Any]]]
|
||||
:param profile: Custom profile describing some details about a game.
|
||||
:type profile: dict[str, Any]
|
||||
:param required_actions: Optional completeness check. If provided,
|
||||
all required action names must exist in `actions` and map to
|
||||
callables.
|
||||
:type required_actions: list[str]
|
||||
:raises AssertionError: Some of the required actions aren't present.
|
||||
"""
|
||||
|
||||
# Verify that all required actions exist
|
||||
for ra in required_actions:
|
||||
if ra not in actions.keys():
|
||||
raise AssertionError(
|
||||
f"Missing required action `{ra}` in action mapping."
|
||||
)
|
||||
|
||||
self.registered_games[name] = ( profile, actions )
|
||||
|
||||
def add_tic_tac_toe(
|
||||
self,
|
||||
on_move : ActionFunction,
|
||||
profile : Payload = {},
|
||||
) -> None:
|
||||
"""
|
||||
Convenience registration for the tic-tac-toe game.
|
||||
|
||||
:param on_move: Called function when the player is to make a move.
|
||||
:type on_move: Callable[[dict[str, Any]], dict[str, Any]]
|
||||
:param profile: Custom details to share about the game.
|
||||
:type profile: dict[str, Any]
|
||||
"""
|
||||
return self.add_game(
|
||||
name="tic-tac-toe",
|
||||
actions={"": on_move},
|
||||
profile=profile,
|
||||
required_actions=[""],
|
||||
)
|
||||
|
||||
def get_action(self, game : str, action : str | None) -> ActionFunction | None:
|
||||
"""
|
||||
Get an action function for a given game, if it exists.
|
||||
|
||||
:param game: The name of the game.
|
||||
:type game: str
|
||||
:param action: The name of the action, or None if the game only has one action.
|
||||
:type action: str | None
|
||||
:return: The action with which the agent would like to act, if at all.
|
||||
:rtype: Callable[[dict[str, Any]], dict[str, Any]] | None
|
||||
"""
|
||||
_, actions = self.registered_games.get(game, ({}, {}))
|
||||
|
||||
if action is None:
|
||||
return list(actions.values())[0] if len(actions) == 1 else None
|
||||
else:
|
||||
return actions.get(action, None)
|
||||
|
|
@ -19,7 +19,6 @@ class PyServer:
|
|||
name: str,
|
||||
import_name: str = __name__,
|
||||
profile: Optional[PayloadType] = None,
|
||||
subpath : str = "",
|
||||
) -> None:
|
||||
"""
|
||||
Create a PyServer.
|
||||
|
|
@ -30,7 +29,6 @@ class PyServer:
|
|||
:type import_name: str
|
||||
:param profile: Additional root-level discovery metadata.
|
||||
:type profile: Optional[Dict[str, Any]]
|
||||
:type subpath: str
|
||||
:raises ValueError: The input contains invalid information.
|
||||
"""
|
||||
self.name = name
|
||||
|
|
|
|||
Loading…
Reference in New Issue