pacai.core.agent

  1import abc
  2import logging
  3import random
  4import typing
  5
  6import pacai.core.agentaction
  7import pacai.core.action
  8import pacai.core.agentinfo
  9import pacai.core.gamestate
 10import pacai.util.alias
 11import pacai.util.reflection
 12
 13class Agent(abc.ABC):
 14    """
 15    The base for all agents in the pacai system.
 16
 17    Agents are called on by the game engine for three things:
 18    1) `game_start_full()`/`game_start()` - a notification that the game has started.
 19    2) `game_complete()` - a notification that the game has ended.
 20    3) `get_action()` - a request for the agent to provide its next action.
 21
 22    For the three core agent methods: get_action(), game_start(), and game_complete(),
 23    this class provides "full" versions of these methods (suffixed with "_full").
 24    These methods may have more information and allow the agent to provide more information,
 25    but are a little more complex.
 26    By default, this class will just call the simple methods from the "full" ones,
 27    allowing children to just implement the simple methods.
 28
 29    Agents should avoid doing any heavy work in their constructors,
 30    and instead do that work in game_start_full()/game_start() (where they will have access to the game state).
 31    """
 32
 33    def __init__(self,
 34            name: pacai.util.reflection.Reference | str = pacai.util.alias.AGENT_DUMMY.long,
 35            move_delay: int = pacai.core.agentinfo.DEFAULT_MOVE_DELAY,
 36            state_eval_func: pacai.core.gamestate.AgentStateEvaluationFunction | pacai.util.reflection.Reference | str =
 37                    pacai.core.agentinfo.DEFAULT_STATE_EVAL,
 38            training: bool = False,
 39            training_epoch: int = 0,
 40            **kwargs: typing.Any) -> None:
 41        self.name: pacai.util.reflection.Reference = pacai.util.reflection.Reference(name)
 42        """ The name of this agent. """
 43
 44        self.move_delay: int = move_delay
 45        """
 46        The delay between moves for this agent.
 47        This value is abstract and has not real units,
 48        i.e., it is not something like a number of seconds.
 49        Instead, this is a relative "time" that is used to decide the next agent to move.
 50        Lower values (relative to other agents) times means the agent will move more times and thus be "faster".
 51        For example, an agent with a move delay of 50 will move twice as often as an agent with a move delay of 100.
 52        """
 53
 54        clean_state_eval_func = pacai.util.reflection.resolve_and_fetch(pacai.core.gamestate.AgentStateEvaluationFunction, state_eval_func)
 55        self.evaluation_function: pacai.core.gamestate.AgentStateEvaluationFunction = clean_state_eval_func
 56        """ The evaluation function that agent will use to assess game states. """
 57
 58        self.rng: random.Random = random.Random(4)
 59        """
 60        The RNG this agent should use whenever it wants randomness.
 61        This object will be constructed right away,
 62        but will be recreated with the suggested seed from the game engine during game_start_full().
 63        """
 64
 65        self.agent_index: int = -1
 66        """
 67        The index this agent has been assigned for this game.
 68        It is initialized to -1 (before the game starts), but gets populated during game_start_full().
 69        """
 70
 71        self.last_positions: list[pacai.core.board.Position | None] = []
 72        """
 73        Keep track of the last positions this agent was in.
 74        This is updated in the beginning of get_action_full().
 75        This will include times when the agent was not on the board (a None position).
 76        """
 77
 78        self.training: bool = training
 79        """ This instance of this agent has been created for training. """
 80
 81        self.training_epoch: int = training_epoch
 82        """ The training epoch (number of training games) the agent has completed. """
 83
 84        self.extra_storage: dict[str, typing.Any] = {}
 85        """ An extra place that can be used by and agent subcomponents for persistent storage. """
 86
 87        logging.debug("Created agent '%s' with move delay %d and state evaluation function '%s'.",
 88                pacai.util.reflection.get_qualified_name(self.name),
 89                self.move_delay,
 90                pacai.util.reflection.get_qualified_name(state_eval_func))
 91
 92    def get_action_full(self,
 93            state: pacai.core.gamestate.GameState,
 94            user_inputs: list[pacai.core.action.Action],
 95            ) -> pacai.core.agentaction.AgentAction:
 96        """
 97        Get an action for this agent given the current state of the game.
 98        Agents may keep internal state, but the given state should be considered the source of truth.
 99        Calls to this method may be subject to a timeout (enforced by the isolator).
100
101        By default, this method just calls get_action().
102        Agent classes should typically just implement get_action(),
103        and only implement this if they need additional functionality.
104        """
105
106        self.last_positions.append(state.get_agent_position(self.agent_index))
107
108        action = self.get_action(state)
109
110        return pacai.core.agentaction.AgentAction(action)
111
112    def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
113        """
114        Get an action for this agent given the current state of the game.
115        This is simplified version of get_action_full(),
116        see that method for full details.
117        """
118
119        return pacai.core.action.STOP
120
121    def game_start_full(self,
122            agent_index: int,
123            suggested_seed: int,
124            initial_state: pacai.core.gamestate.GameState,
125            ) -> pacai.core.agentaction.AgentAction:
126        """
127        Notify this agent that the game is about to start.
128        The provided agent index is the game's index/id for this agent.
129        The state represents the initial state of the game.
130        Any precomputation for this game should be done in this method.
131        Calls to this method may be subject to a timeout.
132        """
133
134        self.agent_index = agent_index
135        self.rng = random.Random(suggested_seed)
136
137        self.game_start(initial_state)
138
139        return pacai.core.agentaction.AgentAction(pacai.core.action.STOP)
140
141    def game_start(self,
142            initial_state: pacai.core.gamestate.GameState,
143            ) -> None:
144        """
145        Notify this agent that the game is about to start.
146        The provided agent index is the game's index/id for this agent.
147        The state represents the initial state of the game.
148        Any precomputation for this game should be done in this method.
149        Calls to this method may be subject to a timeout.
150        """
151
152    def game_complete_full(self,
153            final_state: pacai.core.gamestate.GameState,
154            ) -> pacai.core.agentaction.AgentAction:
155        """
156        Notify this agent that the game has concluded.
157        Agents should use this as an opportunity to make any final calculations and close any game-related resources.
158        """
159
160        self.game_complete(final_state)
161
162        return pacai.core.agentaction.AgentAction(pacai.core.action.STOP)
163
164    def game_complete(self,
165            final_state: pacai.core.gamestate.GameState,
166            ) -> None:
167        """
168        Notify this agent that the game has concluded.
169        Agents should use this as an opportunity to make any final calculations and close any game-related resources.
170        """
171
172    def evaluate_state(self,
173            state: pacai.core.gamestate.GameState,
174            action: pacai.core.action.Action | None = None,
175            **kwargs: typing.Any) -> float:
176        """
177        Evaluate the state to get a decide how good an action was.
178        The base implementation for this function just calls `self.evaluation_function`,
179        but child classes may override this method to easily implement their own evaluations.
180        """
181
182        return self.evaluation_function(state, agent = self, action = action, **kwargs)
183
184def load(agent_info: pacai.core.agentinfo.AgentInfo) -> Agent:
185    """
186    Construct a new agent object using the given agent info.
187    The name of the agent will be used as a reference to (e.g., name of) the agent's class.
188    """
189
190    agent = pacai.util.reflection.new_object(agent_info.name, **agent_info.to_flat_dict())
191
192    if (not isinstance(agent, Agent)):
193        raise ValueError(f"Loaded class is not an agent: '{agent_info.name}'.")
194
195    return agent
class Agent(abc.ABC):
 14class Agent(abc.ABC):
 15    """
 16    The base for all agents in the pacai system.
 17
 18    Agents are called on by the game engine for three things:
 19    1) `game_start_full()`/`game_start()` - a notification that the game has started.
 20    2) `game_complete()` - a notification that the game has ended.
 21    3) `get_action()` - a request for the agent to provide its next action.
 22
 23    For the three core agent methods: get_action(), game_start(), and game_complete(),
 24    this class provides "full" versions of these methods (suffixed with "_full").
 25    These methods may have more information and allow the agent to provide more information,
 26    but are a little more complex.
 27    By default, this class will just call the simple methods from the "full" ones,
 28    allowing children to just implement the simple methods.
 29
 30    Agents should avoid doing any heavy work in their constructors,
 31    and instead do that work in game_start_full()/game_start() (where they will have access to the game state).
 32    """
 33
 34    def __init__(self,
 35            name: pacai.util.reflection.Reference | str = pacai.util.alias.AGENT_DUMMY.long,
 36            move_delay: int = pacai.core.agentinfo.DEFAULT_MOVE_DELAY,
 37            state_eval_func: pacai.core.gamestate.AgentStateEvaluationFunction | pacai.util.reflection.Reference | str =
 38                    pacai.core.agentinfo.DEFAULT_STATE_EVAL,
 39            training: bool = False,
 40            training_epoch: int = 0,
 41            **kwargs: typing.Any) -> None:
 42        self.name: pacai.util.reflection.Reference = pacai.util.reflection.Reference(name)
 43        """ The name of this agent. """
 44
 45        self.move_delay: int = move_delay
 46        """
 47        The delay between moves for this agent.
 48        This value is abstract and has not real units,
 49        i.e., it is not something like a number of seconds.
 50        Instead, this is a relative "time" that is used to decide the next agent to move.
 51        Lower values (relative to other agents) times means the agent will move more times and thus be "faster".
 52        For example, an agent with a move delay of 50 will move twice as often as an agent with a move delay of 100.
 53        """
 54
 55        clean_state_eval_func = pacai.util.reflection.resolve_and_fetch(pacai.core.gamestate.AgentStateEvaluationFunction, state_eval_func)
 56        self.evaluation_function: pacai.core.gamestate.AgentStateEvaluationFunction = clean_state_eval_func
 57        """ The evaluation function that agent will use to assess game states. """
 58
 59        self.rng: random.Random = random.Random(4)
 60        """
 61        The RNG this agent should use whenever it wants randomness.
 62        This object will be constructed right away,
 63        but will be recreated with the suggested seed from the game engine during game_start_full().
 64        """
 65
 66        self.agent_index: int = -1
 67        """
 68        The index this agent has been assigned for this game.
 69        It is initialized to -1 (before the game starts), but gets populated during game_start_full().
 70        """
 71
 72        self.last_positions: list[pacai.core.board.Position | None] = []
 73        """
 74        Keep track of the last positions this agent was in.
 75        This is updated in the beginning of get_action_full().
 76        This will include times when the agent was not on the board (a None position).
 77        """
 78
 79        self.training: bool = training
 80        """ This instance of this agent has been created for training. """
 81
 82        self.training_epoch: int = training_epoch
 83        """ The training epoch (number of training games) the agent has completed. """
 84
 85        self.extra_storage: dict[str, typing.Any] = {}
 86        """ An extra place that can be used by and agent subcomponents for persistent storage. """
 87
 88        logging.debug("Created agent '%s' with move delay %d and state evaluation function '%s'.",
 89                pacai.util.reflection.get_qualified_name(self.name),
 90                self.move_delay,
 91                pacai.util.reflection.get_qualified_name(state_eval_func))
 92
 93    def get_action_full(self,
 94            state: pacai.core.gamestate.GameState,
 95            user_inputs: list[pacai.core.action.Action],
 96            ) -> pacai.core.agentaction.AgentAction:
 97        """
 98        Get an action for this agent given the current state of the game.
 99        Agents may keep internal state, but the given state should be considered the source of truth.
100        Calls to this method may be subject to a timeout (enforced by the isolator).
101
102        By default, this method just calls get_action().
103        Agent classes should typically just implement get_action(),
104        and only implement this if they need additional functionality.
105        """
106
107        self.last_positions.append(state.get_agent_position(self.agent_index))
108
109        action = self.get_action(state)
110
111        return pacai.core.agentaction.AgentAction(action)
112
113    def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
114        """
115        Get an action for this agent given the current state of the game.
116        This is simplified version of get_action_full(),
117        see that method for full details.
118        """
119
120        return pacai.core.action.STOP
121
122    def game_start_full(self,
123            agent_index: int,
124            suggested_seed: int,
125            initial_state: pacai.core.gamestate.GameState,
126            ) -> pacai.core.agentaction.AgentAction:
127        """
128        Notify this agent that the game is about to start.
129        The provided agent index is the game's index/id for this agent.
130        The state represents the initial state of the game.
131        Any precomputation for this game should be done in this method.
132        Calls to this method may be subject to a timeout.
133        """
134
135        self.agent_index = agent_index
136        self.rng = random.Random(suggested_seed)
137
138        self.game_start(initial_state)
139
140        return pacai.core.agentaction.AgentAction(pacai.core.action.STOP)
141
142    def game_start(self,
143            initial_state: pacai.core.gamestate.GameState,
144            ) -> None:
145        """
146        Notify this agent that the game is about to start.
147        The provided agent index is the game's index/id for this agent.
148        The state represents the initial state of the game.
149        Any precomputation for this game should be done in this method.
150        Calls to this method may be subject to a timeout.
151        """
152
153    def game_complete_full(self,
154            final_state: pacai.core.gamestate.GameState,
155            ) -> pacai.core.agentaction.AgentAction:
156        """
157        Notify this agent that the game has concluded.
158        Agents should use this as an opportunity to make any final calculations and close any game-related resources.
159        """
160
161        self.game_complete(final_state)
162
163        return pacai.core.agentaction.AgentAction(pacai.core.action.STOP)
164
165    def game_complete(self,
166            final_state: pacai.core.gamestate.GameState,
167            ) -> None:
168        """
169        Notify this agent that the game has concluded.
170        Agents should use this as an opportunity to make any final calculations and close any game-related resources.
171        """
172
173    def evaluate_state(self,
174            state: pacai.core.gamestate.GameState,
175            action: pacai.core.action.Action | None = None,
176            **kwargs: typing.Any) -> float:
177        """
178        Evaluate the state to get a decide how good an action was.
179        The base implementation for this function just calls `self.evaluation_function`,
180        but child classes may override this method to easily implement their own evaluations.
181        """
182
183        return self.evaluation_function(state, agent = self, action = action, **kwargs)

The base for all agents in the pacai system.

Agents are called on by the game engine for three things: 1) game_start_full()/game_start() - a notification that the game has started. 2) game_complete() - a notification that the game has ended. 3) get_action() - a request for the agent to provide its next action.

For the three core agent methods: get_action(), game_start(), and game_complete(), this class provides "full" versions of these methods (suffixed with "_full"). These methods may have more information and allow the agent to provide more information, but are a little more complex. By default, this class will just call the simple methods from the "full" ones, allowing children to just implement the simple methods.

Agents should avoid doing any heavy work in their constructors, and instead do that work in game_start_full()/game_start() (where they will have access to the game state).

Agent( name: pacai.util.reflection.Reference | str = 'pacai.agents.dummy.DummyAgent', move_delay: int = 100, state_eval_func: pacai.core.gamestate.AgentStateEvaluationFunction | pacai.util.reflection.Reference | str = 'pacai.core.gamestate.base_eval', training: bool = False, training_epoch: int = 0, **kwargs: Any)
34    def __init__(self,
35            name: pacai.util.reflection.Reference | str = pacai.util.alias.AGENT_DUMMY.long,
36            move_delay: int = pacai.core.agentinfo.DEFAULT_MOVE_DELAY,
37            state_eval_func: pacai.core.gamestate.AgentStateEvaluationFunction | pacai.util.reflection.Reference | str =
38                    pacai.core.agentinfo.DEFAULT_STATE_EVAL,
39            training: bool = False,
40            training_epoch: int = 0,
41            **kwargs: typing.Any) -> None:
42        self.name: pacai.util.reflection.Reference = pacai.util.reflection.Reference(name)
43        """ The name of this agent. """
44
45        self.move_delay: int = move_delay
46        """
47        The delay between moves for this agent.
48        This value is abstract and has not real units,
49        i.e., it is not something like a number of seconds.
50        Instead, this is a relative "time" that is used to decide the next agent to move.
51        Lower values (relative to other agents) times means the agent will move more times and thus be "faster".
52        For example, an agent with a move delay of 50 will move twice as often as an agent with a move delay of 100.
53        """
54
55        clean_state_eval_func = pacai.util.reflection.resolve_and_fetch(pacai.core.gamestate.AgentStateEvaluationFunction, state_eval_func)
56        self.evaluation_function: pacai.core.gamestate.AgentStateEvaluationFunction = clean_state_eval_func
57        """ The evaluation function that agent will use to assess game states. """
58
59        self.rng: random.Random = random.Random(4)
60        """
61        The RNG this agent should use whenever it wants randomness.
62        This object will be constructed right away,
63        but will be recreated with the suggested seed from the game engine during game_start_full().
64        """
65
66        self.agent_index: int = -1
67        """
68        The index this agent has been assigned for this game.
69        It is initialized to -1 (before the game starts), but gets populated during game_start_full().
70        """
71
72        self.last_positions: list[pacai.core.board.Position | None] = []
73        """
74        Keep track of the last positions this agent was in.
75        This is updated in the beginning of get_action_full().
76        This will include times when the agent was not on the board (a None position).
77        """
78
79        self.training: bool = training
80        """ This instance of this agent has been created for training. """
81
82        self.training_epoch: int = training_epoch
83        """ The training epoch (number of training games) the agent has completed. """
84
85        self.extra_storage: dict[str, typing.Any] = {}
86        """ An extra place that can be used by and agent subcomponents for persistent storage. """
87
88        logging.debug("Created agent '%s' with move delay %d and state evaluation function '%s'.",
89                pacai.util.reflection.get_qualified_name(self.name),
90                self.move_delay,
91                pacai.util.reflection.get_qualified_name(state_eval_func))

The name of this agent.

move_delay: int

The delay between moves for this agent. This value is abstract and has not real units, i.e., it is not something like a number of seconds. Instead, this is a relative "time" that is used to decide the next agent to move. Lower values (relative to other agents) times means the agent will move more times and thus be "faster". For example, an agent with a move delay of 50 will move twice as often as an agent with a move delay of 100.

The evaluation function that agent will use to assess game states.

rng: random.Random

The RNG this agent should use whenever it wants randomness. This object will be constructed right away, but will be recreated with the suggested seed from the game engine during game_start_full().

agent_index: int

The index this agent has been assigned for this game. It is initialized to -1 (before the game starts), but gets populated during game_start_full().

last_positions: list[pacai.core.board.Position | None]

Keep track of the last positions this agent was in. This is updated in the beginning of get_action_full(). This will include times when the agent was not on the board (a None position).

training: bool

This instance of this agent has been created for training.

training_epoch: int

The training epoch (number of training games) the agent has completed.

extra_storage: dict[str, typing.Any]

An extra place that can be used by and agent subcomponents for persistent storage.

def get_action_full( self, state: pacai.core.gamestate.GameState, user_inputs: list[pacai.core.action.Action]) -> pacai.core.agentaction.AgentAction:
 93    def get_action_full(self,
 94            state: pacai.core.gamestate.GameState,
 95            user_inputs: list[pacai.core.action.Action],
 96            ) -> pacai.core.agentaction.AgentAction:
 97        """
 98        Get an action for this agent given the current state of the game.
 99        Agents may keep internal state, but the given state should be considered the source of truth.
100        Calls to this method may be subject to a timeout (enforced by the isolator).
101
102        By default, this method just calls get_action().
103        Agent classes should typically just implement get_action(),
104        and only implement this if they need additional functionality.
105        """
106
107        self.last_positions.append(state.get_agent_position(self.agent_index))
108
109        action = self.get_action(state)
110
111        return pacai.core.agentaction.AgentAction(action)

Get an action for this agent given the current state of the game. Agents may keep internal state, but the given state should be considered the source of truth. Calls to this method may be subject to a timeout (enforced by the isolator).

By default, this method just calls get_action(). Agent classes should typically just implement get_action(), and only implement this if they need additional functionality.

def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
113    def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
114        """
115        Get an action for this agent given the current state of the game.
116        This is simplified version of get_action_full(),
117        see that method for full details.
118        """
119
120        return pacai.core.action.STOP

Get an action for this agent given the current state of the game. This is simplified version of get_action_full(), see that method for full details.

def game_start_full( self, agent_index: int, suggested_seed: int, initial_state: pacai.core.gamestate.GameState) -> pacai.core.agentaction.AgentAction:
122    def game_start_full(self,
123            agent_index: int,
124            suggested_seed: int,
125            initial_state: pacai.core.gamestate.GameState,
126            ) -> pacai.core.agentaction.AgentAction:
127        """
128        Notify this agent that the game is about to start.
129        The provided agent index is the game's index/id for this agent.
130        The state represents the initial state of the game.
131        Any precomputation for this game should be done in this method.
132        Calls to this method may be subject to a timeout.
133        """
134
135        self.agent_index = agent_index
136        self.rng = random.Random(suggested_seed)
137
138        self.game_start(initial_state)
139
140        return pacai.core.agentaction.AgentAction(pacai.core.action.STOP)

Notify this agent that the game is about to start. The provided agent index is the game's index/id for this agent. The state represents the initial state of the game. Any precomputation for this game should be done in this method. Calls to this method may be subject to a timeout.

def game_start(self, initial_state: pacai.core.gamestate.GameState) -> None:
142    def game_start(self,
143            initial_state: pacai.core.gamestate.GameState,
144            ) -> None:
145        """
146        Notify this agent that the game is about to start.
147        The provided agent index is the game's index/id for this agent.
148        The state represents the initial state of the game.
149        Any precomputation for this game should be done in this method.
150        Calls to this method may be subject to a timeout.
151        """

Notify this agent that the game is about to start. The provided agent index is the game's index/id for this agent. The state represents the initial state of the game. Any precomputation for this game should be done in this method. Calls to this method may be subject to a timeout.

def game_complete_full( self, final_state: pacai.core.gamestate.GameState) -> pacai.core.agentaction.AgentAction:
153    def game_complete_full(self,
154            final_state: pacai.core.gamestate.GameState,
155            ) -> pacai.core.agentaction.AgentAction:
156        """
157        Notify this agent that the game has concluded.
158        Agents should use this as an opportunity to make any final calculations and close any game-related resources.
159        """
160
161        self.game_complete(final_state)
162
163        return pacai.core.agentaction.AgentAction(pacai.core.action.STOP)

Notify this agent that the game has concluded. Agents should use this as an opportunity to make any final calculations and close any game-related resources.

def game_complete(self, final_state: pacai.core.gamestate.GameState) -> None:
165    def game_complete(self,
166            final_state: pacai.core.gamestate.GameState,
167            ) -> None:
168        """
169        Notify this agent that the game has concluded.
170        Agents should use this as an opportunity to make any final calculations and close any game-related resources.
171        """

Notify this agent that the game has concluded. Agents should use this as an opportunity to make any final calculations and close any game-related resources.

def evaluate_state( self, state: pacai.core.gamestate.GameState, action: pacai.core.action.Action | None = None, **kwargs: Any) -> float:
173    def evaluate_state(self,
174            state: pacai.core.gamestate.GameState,
175            action: pacai.core.action.Action | None = None,
176            **kwargs: typing.Any) -> float:
177        """
178        Evaluate the state to get a decide how good an action was.
179        The base implementation for this function just calls `self.evaluation_function`,
180        but child classes may override this method to easily implement their own evaluations.
181        """
182
183        return self.evaluation_function(state, agent = self, action = action, **kwargs)

Evaluate the state to get a decide how good an action was. The base implementation for this function just calls self.evaluation_function, but child classes may override this method to easily implement their own evaluations.

def load(agent_info: pacai.core.agentinfo.AgentInfo) -> Agent:
185def load(agent_info: pacai.core.agentinfo.AgentInfo) -> Agent:
186    """
187    Construct a new agent object using the given agent info.
188    The name of the agent will be used as a reference to (e.g., name of) the agent's class.
189    """
190
191    agent = pacai.util.reflection.new_object(agent_info.name, **agent_info.to_flat_dict())
192
193    if (not isinstance(agent, Agent)):
194        raise ValueError(f"Loaded class is not an agent: '{agent_info.name}'.")
195
196    return agent

Construct a new agent object using the given agent info. The name of the agent will be used as a reference to (e.g., name of) the agent's class.