pacai.agents.minimax

  1import logging
  2import math
  3import typing
  4
  5import pacai.core.action
  6import pacai.core.agent
  7import pacai.core.gamestate
  8import pacai.util.parse
  9
 10DEFAULT_PLY_COUNT: int = 2
 11
 12class MinimaxLikeAgent(pacai.core.agent.Agent):
 13    """
 14    An agent that follow the general procedure of Minimax,
 15    but is abstracted to support things like alpha-beta pruning and expectimax.
 16
 17    Currently, minimax_step_max(), minimax_step_min(), and minimax_step_expected_min() are all filled with dummy implementations.
 18    Child classes should implement those to get proper minimax functionality.
 19    """
 20
 21    def __init__(self,
 22            ply_count: int = DEFAULT_PLY_COUNT,
 23            alphabeta_prune: bool = False,
 24            expectimax: bool = False,
 25            **kwargs: typing.Any) -> None:
 26        super().__init__(**kwargs)
 27
 28        # Parse (possibly string) arguments.
 29        ply_count = int(ply_count)
 30        alphabeta_prune = pacai.util.parse.boolean(alphabeta_prune)
 31        expectimax = pacai.util.parse.boolean(expectimax)
 32
 33        if (ply_count < 1):
 34            raise ValueError(f"Ply count must be at least 1, found {ply_count}.")
 35
 36        self.ply_count: int = ply_count
 37        """
 38        How many minimax plys to descend.
 39        A "ply" is one set of actions by each agent.
 40        When thinking about minimax in terms of a search tree,
 41        a ply will be a max layer followed by as many (usually min) layers until the original agent is reached again.
 42        """
 43
 44        self.alphabeta_prune: bool = alphabeta_prune
 45        """ Whether or not to use alpha-beta pruning. """
 46
 47        self.expectimax: bool = expectimax
 48        """ Whether or not to use expectimax. """
 49
 50        self._stats_states_evaluated: list[int] = []
 51        """ Track how many states have been evaluated for each call to get_action(). """
 52
 53        self._stats_nodes_visited: list[int] = []
 54        """ Track how many search nodes have been visited for each call to get_action(). """
 55
 56    def evaluate_state(self,
 57            state: pacai.core.gamestate.GameState,
 58            action: pacai.core.action.Action | None = None,
 59            **kwargs: typing.Any) -> float:
 60        self._stats_states_evaluated[-1] += 1
 61        return super().evaluate_state(state, action)
 62
 63    def game_complete(self, final_state: pacai.core.gamestate.GameState) -> None:
 64        logging.debug(("Minimax-like agent complete."
 65                + " Agent Index: %d, Ply Count: %d, Use Alpha-Beta Pruning: %s, Use Expectimax: %s,"
 66                + " States Evaluated: %d, Nodes Visited: %d."),
 67                self.agent_index,
 68                self.ply_count, self.alphabeta_prune, self.expectimax,
 69                sum(self._stats_states_evaluated), sum(self._stats_nodes_visited))
 70
 71    def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
 72        # Start the stat collection for this round at 0.
 73        self._stats_states_evaluated.append(0)
 74        self._stats_nodes_visited.append(0)
 75
 76        actions, score = self.minimax_step(state, self.ply_count + 1, -math.inf, math.inf)
 77        action = self.rng.choice(actions)
 78
 79        logging.debug("Turn: %d, Game State Score: %d, Minimax Score: %d, Chosen Action: %s, States Evaluated: %d, Nodes Visited: %d.",
 80                state.turn_count, state.score, score, action,
 81                self._stats_states_evaluated[-1], self._stats_nodes_visited[-1])
 82
 83        if (action is None):
 84            raise ValueError("Did not get an action out of Minimax.")
 85
 86        return action
 87
 88    def minimax_step(self,
 89            state: pacai.core.gamestate.GameState,
 90            ply_count: int,
 91            alpha: float,
 92            beta: float,
 93            ) -> tuple[list[pacai.core.action.Action], float]:
 94        """
 95        Step through one layer (one agent) of minimax and return all the best actions (there may be ties) along with their score.
 96        This method will handle various book keeping and call the correct minimax_step_min() or minimax_step_max() method.
 97
 98        When doing alpha-beta pruning,
 99        alpha represents the "best minimum" score
100        while beta represents the "best maximum" score.
101        They will typically start at inf and -inf, respectively.
102
103        Return: ([best action, ...], best score).
104        """
105
106        self._stats_nodes_visited[-1] += 1
107
108        # If we see ourselves, then we have descended a full ply.
109        if (state.agent_index == self.agent_index):
110            ply_count -= 1
111
112        # At ply count zero, we just evaluate the current state and return up the tree.
113        # Note that we only hit this when we are the target agent (since we just decremented the ply count).
114        if (ply_count <= 0):
115            return [], self.evaluate_state(state)
116
117        # If the game is over, then stop descending.
118        if (state.game_over):
119            return [], self.evaluate_state(state)
120
121        legal_actions = state.get_legal_actions()
122
123        # Don't consider stopping unless we can do nothing else.
124        # This will help keep the game moving along.
125        if ((len(legal_actions) > 1) and (pacai.core.action.STOP in legal_actions)):
126            legal_actions.remove(pacai.core.action.STOP)
127
128        if (state.agent_index == self.agent_index):
129            # We are considering ourselves, get the max.
130            return self.minimax_step_max(state, ply_count, legal_actions, alpha, beta)
131
132        # We are considering an opposing agent (like a ghost), get the min or expected min.
133        if (self.expectimax):
134            return [], self.minimax_step_expected_min(state, ply_count, legal_actions, alpha, beta)
135
136        return self.minimax_step_min(state, ply_count, legal_actions, alpha, beta)
137
138    def minimax_step_max(self,
139            state: pacai.core.gamestate.GameState,
140            ply_count: int,
141            legal_actions: list[pacai.core.action.Action],
142            alpha: float,
143            beta: float,
144            ) -> tuple[list[pacai.core.action.Action], float]:
145        """
146        Perform a max step in minimax.
147        minimax_step() has already taken care of all the bookkeeping,
148        this method just needs to return the best actions along with their score.
149
150        alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.
151
152        The default implementation is just random and does not follow any minimax procedure.
153        Child classes should override this method.
154
155        Return: ([best action, ...], best score).
156        """
157
158        # Randomly choose an action.
159        action = self.rng.choice(legal_actions)
160
161        # Score the action.
162        successor = state.generate_successor(action, self.rng)
163        _, score = self.minimax_step(successor, ply_count, alpha, beta)
164
165        return [action], score
166
167    def minimax_step_min(self,
168            state: pacai.core.gamestate.GameState,
169            ply_count: int,
170            legal_actions: list[pacai.core.action.Action],
171            alpha: float,
172            beta: float,
173            ) -> tuple[list[pacai.core.action.Action], float]:
174        """
175        Perform a min step in minimax.
176        minimax_step() has already taken care of all the bookkeeping,
177        this method just needs to return the best actions along with their score.
178
179        alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.
180
181        The default implementation is just random and does not follow any minimax procedure.
182        Child classes should override this method.
183
184        Return: ([best action, ...], best score).
185        """
186
187        # Randomly choose an action.
188        action = self.rng.choice(legal_actions)
189
190        # Score the action.
191        successor = state.generate_successor(action, self.rng)
192        _, score = self.minimax_step(successor, ply_count, alpha, beta)
193
194        return [action], score
195
196    def minimax_step_expected_min(self,
197            state: pacai.core.gamestate.GameState,
198            ply_count: int,
199            legal_actions: list[pacai.core.action.Action],
200            alpha: float,
201            beta: float,
202            ) -> float:
203        """
204        Perform a min step in expectimax.
205        minimax_step() has already taken care of all the bookkeeping,
206        this method just needs to return the expected score.
207
208        Note that unlike minimax_step_max() and minimax_step_min(),
209        no action is returned.
210
211        alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.
212
213        The default implementation is just random and does not follow any minimax procedure.
214        Child classes should override this method.
215        """
216
217        # Randomly choose an action.
218        action = self.rng.choice(legal_actions)
219
220        # Score the action.
221        successor = state.generate_successor(action, self.rng)
222        _, score = self.minimax_step(successor, ply_count, alpha, beta)
223
224        return score
DEFAULT_PLY_COUNT: int = 2
class MinimaxLikeAgent(pacai.core.agent.Agent):
 13class MinimaxLikeAgent(pacai.core.agent.Agent):
 14    """
 15    An agent that follow the general procedure of Minimax,
 16    but is abstracted to support things like alpha-beta pruning and expectimax.
 17
 18    Currently, minimax_step_max(), minimax_step_min(), and minimax_step_expected_min() are all filled with dummy implementations.
 19    Child classes should implement those to get proper minimax functionality.
 20    """
 21
 22    def __init__(self,
 23            ply_count: int = DEFAULT_PLY_COUNT,
 24            alphabeta_prune: bool = False,
 25            expectimax: bool = False,
 26            **kwargs: typing.Any) -> None:
 27        super().__init__(**kwargs)
 28
 29        # Parse (possibly string) arguments.
 30        ply_count = int(ply_count)
 31        alphabeta_prune = pacai.util.parse.boolean(alphabeta_prune)
 32        expectimax = pacai.util.parse.boolean(expectimax)
 33
 34        if (ply_count < 1):
 35            raise ValueError(f"Ply count must be at least 1, found {ply_count}.")
 36
 37        self.ply_count: int = ply_count
 38        """
 39        How many minimax plys to descend.
 40        A "ply" is one set of actions by each agent.
 41        When thinking about minimax in terms of a search tree,
 42        a ply will be a max layer followed by as many (usually min) layers until the original agent is reached again.
 43        """
 44
 45        self.alphabeta_prune: bool = alphabeta_prune
 46        """ Whether or not to use alpha-beta pruning. """
 47
 48        self.expectimax: bool = expectimax
 49        """ Whether or not to use expectimax. """
 50
 51        self._stats_states_evaluated: list[int] = []
 52        """ Track how many states have been evaluated for each call to get_action(). """
 53
 54        self._stats_nodes_visited: list[int] = []
 55        """ Track how many search nodes have been visited for each call to get_action(). """
 56
 57    def evaluate_state(self,
 58            state: pacai.core.gamestate.GameState,
 59            action: pacai.core.action.Action | None = None,
 60            **kwargs: typing.Any) -> float:
 61        self._stats_states_evaluated[-1] += 1
 62        return super().evaluate_state(state, action)
 63
 64    def game_complete(self, final_state: pacai.core.gamestate.GameState) -> None:
 65        logging.debug(("Minimax-like agent complete."
 66                + " Agent Index: %d, Ply Count: %d, Use Alpha-Beta Pruning: %s, Use Expectimax: %s,"
 67                + " States Evaluated: %d, Nodes Visited: %d."),
 68                self.agent_index,
 69                self.ply_count, self.alphabeta_prune, self.expectimax,
 70                sum(self._stats_states_evaluated), sum(self._stats_nodes_visited))
 71
 72    def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
 73        # Start the stat collection for this round at 0.
 74        self._stats_states_evaluated.append(0)
 75        self._stats_nodes_visited.append(0)
 76
 77        actions, score = self.minimax_step(state, self.ply_count + 1, -math.inf, math.inf)
 78        action = self.rng.choice(actions)
 79
 80        logging.debug("Turn: %d, Game State Score: %d, Minimax Score: %d, Chosen Action: %s, States Evaluated: %d, Nodes Visited: %d.",
 81                state.turn_count, state.score, score, action,
 82                self._stats_states_evaluated[-1], self._stats_nodes_visited[-1])
 83
 84        if (action is None):
 85            raise ValueError("Did not get an action out of Minimax.")
 86
 87        return action
 88
 89    def minimax_step(self,
 90            state: pacai.core.gamestate.GameState,
 91            ply_count: int,
 92            alpha: float,
 93            beta: float,
 94            ) -> tuple[list[pacai.core.action.Action], float]:
 95        """
 96        Step through one layer (one agent) of minimax and return all the best actions (there may be ties) along with their score.
 97        This method will handle various book keeping and call the correct minimax_step_min() or minimax_step_max() method.
 98
 99        When doing alpha-beta pruning,
100        alpha represents the "best minimum" score
101        while beta represents the "best maximum" score.
102        They will typically start at inf and -inf, respectively.
103
104        Return: ([best action, ...], best score).
105        """
106
107        self._stats_nodes_visited[-1] += 1
108
109        # If we see ourselves, then we have descended a full ply.
110        if (state.agent_index == self.agent_index):
111            ply_count -= 1
112
113        # At ply count zero, we just evaluate the current state and return up the tree.
114        # Note that we only hit this when we are the target agent (since we just decremented the ply count).
115        if (ply_count <= 0):
116            return [], self.evaluate_state(state)
117
118        # If the game is over, then stop descending.
119        if (state.game_over):
120            return [], self.evaluate_state(state)
121
122        legal_actions = state.get_legal_actions()
123
124        # Don't consider stopping unless we can do nothing else.
125        # This will help keep the game moving along.
126        if ((len(legal_actions) > 1) and (pacai.core.action.STOP in legal_actions)):
127            legal_actions.remove(pacai.core.action.STOP)
128
129        if (state.agent_index == self.agent_index):
130            # We are considering ourselves, get the max.
131            return self.minimax_step_max(state, ply_count, legal_actions, alpha, beta)
132
133        # We are considering an opposing agent (like a ghost), get the min or expected min.
134        if (self.expectimax):
135            return [], self.minimax_step_expected_min(state, ply_count, legal_actions, alpha, beta)
136
137        return self.minimax_step_min(state, ply_count, legal_actions, alpha, beta)
138
139    def minimax_step_max(self,
140            state: pacai.core.gamestate.GameState,
141            ply_count: int,
142            legal_actions: list[pacai.core.action.Action],
143            alpha: float,
144            beta: float,
145            ) -> tuple[list[pacai.core.action.Action], float]:
146        """
147        Perform a max step in minimax.
148        minimax_step() has already taken care of all the bookkeeping,
149        this method just needs to return the best actions along with their score.
150
151        alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.
152
153        The default implementation is just random and does not follow any minimax procedure.
154        Child classes should override this method.
155
156        Return: ([best action, ...], best score).
157        """
158
159        # Randomly choose an action.
160        action = self.rng.choice(legal_actions)
161
162        # Score the action.
163        successor = state.generate_successor(action, self.rng)
164        _, score = self.minimax_step(successor, ply_count, alpha, beta)
165
166        return [action], score
167
168    def minimax_step_min(self,
169            state: pacai.core.gamestate.GameState,
170            ply_count: int,
171            legal_actions: list[pacai.core.action.Action],
172            alpha: float,
173            beta: float,
174            ) -> tuple[list[pacai.core.action.Action], float]:
175        """
176        Perform a min step in minimax.
177        minimax_step() has already taken care of all the bookkeeping,
178        this method just needs to return the best actions along with their score.
179
180        alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.
181
182        The default implementation is just random and does not follow any minimax procedure.
183        Child classes should override this method.
184
185        Return: ([best action, ...], best score).
186        """
187
188        # Randomly choose an action.
189        action = self.rng.choice(legal_actions)
190
191        # Score the action.
192        successor = state.generate_successor(action, self.rng)
193        _, score = self.minimax_step(successor, ply_count, alpha, beta)
194
195        return [action], score
196
197    def minimax_step_expected_min(self,
198            state: pacai.core.gamestate.GameState,
199            ply_count: int,
200            legal_actions: list[pacai.core.action.Action],
201            alpha: float,
202            beta: float,
203            ) -> float:
204        """
205        Perform a min step in expectimax.
206        minimax_step() has already taken care of all the bookkeeping,
207        this method just needs to return the expected score.
208
209        Note that unlike minimax_step_max() and minimax_step_min(),
210        no action is returned.
211
212        alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.
213
214        The default implementation is just random and does not follow any minimax procedure.
215        Child classes should override this method.
216        """
217
218        # Randomly choose an action.
219        action = self.rng.choice(legal_actions)
220
221        # Score the action.
222        successor = state.generate_successor(action, self.rng)
223        _, score = self.minimax_step(successor, ply_count, alpha, beta)
224
225        return score

An agent that follow the general procedure of Minimax, but is abstracted to support things like alpha-beta pruning and expectimax.

Currently, minimax_step_max(), minimax_step_min(), and minimax_step_expected_min() are all filled with dummy implementations. Child classes should implement those to get proper minimax functionality.

MinimaxLikeAgent( ply_count: int = 2, alphabeta_prune: bool = False, expectimax: bool = False, **kwargs: Any)
22    def __init__(self,
23            ply_count: int = DEFAULT_PLY_COUNT,
24            alphabeta_prune: bool = False,
25            expectimax: bool = False,
26            **kwargs: typing.Any) -> None:
27        super().__init__(**kwargs)
28
29        # Parse (possibly string) arguments.
30        ply_count = int(ply_count)
31        alphabeta_prune = pacai.util.parse.boolean(alphabeta_prune)
32        expectimax = pacai.util.parse.boolean(expectimax)
33
34        if (ply_count < 1):
35            raise ValueError(f"Ply count must be at least 1, found {ply_count}.")
36
37        self.ply_count: int = ply_count
38        """
39        How many minimax plys to descend.
40        A "ply" is one set of actions by each agent.
41        When thinking about minimax in terms of a search tree,
42        a ply will be a max layer followed by as many (usually min) layers until the original agent is reached again.
43        """
44
45        self.alphabeta_prune: bool = alphabeta_prune
46        """ Whether or not to use alpha-beta pruning. """
47
48        self.expectimax: bool = expectimax
49        """ Whether or not to use expectimax. """
50
51        self._stats_states_evaluated: list[int] = []
52        """ Track how many states have been evaluated for each call to get_action(). """
53
54        self._stats_nodes_visited: list[int] = []
55        """ Track how many search nodes have been visited for each call to get_action(). """
ply_count: int

How many minimax plys to descend. A "ply" is one set of actions by each agent. When thinking about minimax in terms of a search tree, a ply will be a max layer followed by as many (usually min) layers until the original agent is reached again.

alphabeta_prune: bool

Whether or not to use alpha-beta pruning.

expectimax: bool

Whether or not to use expectimax.

def evaluate_state( self, state: pacai.core.gamestate.GameState, action: pacai.core.action.Action | None = None, **kwargs: Any) -> float:
57    def evaluate_state(self,
58            state: pacai.core.gamestate.GameState,
59            action: pacai.core.action.Action | None = None,
60            **kwargs: typing.Any) -> float:
61        self._stats_states_evaluated[-1] += 1
62        return super().evaluate_state(state, action)

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 game_complete(self, final_state: pacai.core.gamestate.GameState) -> None:
64    def game_complete(self, final_state: pacai.core.gamestate.GameState) -> None:
65        logging.debug(("Minimax-like agent complete."
66                + " Agent Index: %d, Ply Count: %d, Use Alpha-Beta Pruning: %s, Use Expectimax: %s,"
67                + " States Evaluated: %d, Nodes Visited: %d."),
68                self.agent_index,
69                self.ply_count, self.alphabeta_prune, self.expectimax,
70                sum(self._stats_states_evaluated), sum(self._stats_nodes_visited))

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 get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
72    def get_action(self, state: pacai.core.gamestate.GameState) -> pacai.core.action.Action:
73        # Start the stat collection for this round at 0.
74        self._stats_states_evaluated.append(0)
75        self._stats_nodes_visited.append(0)
76
77        actions, score = self.minimax_step(state, self.ply_count + 1, -math.inf, math.inf)
78        action = self.rng.choice(actions)
79
80        logging.debug("Turn: %d, Game State Score: %d, Minimax Score: %d, Chosen Action: %s, States Evaluated: %d, Nodes Visited: %d.",
81                state.turn_count, state.score, score, action,
82                self._stats_states_evaluated[-1], self._stats_nodes_visited[-1])
83
84        if (action is None):
85            raise ValueError("Did not get an action out of Minimax.")
86
87        return action

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 minimax_step( self, state: pacai.core.gamestate.GameState, ply_count: int, alpha: float, beta: float) -> tuple[list[pacai.core.action.Action], float]:
 89    def minimax_step(self,
 90            state: pacai.core.gamestate.GameState,
 91            ply_count: int,
 92            alpha: float,
 93            beta: float,
 94            ) -> tuple[list[pacai.core.action.Action], float]:
 95        """
 96        Step through one layer (one agent) of minimax and return all the best actions (there may be ties) along with their score.
 97        This method will handle various book keeping and call the correct minimax_step_min() or minimax_step_max() method.
 98
 99        When doing alpha-beta pruning,
100        alpha represents the "best minimum" score
101        while beta represents the "best maximum" score.
102        They will typically start at inf and -inf, respectively.
103
104        Return: ([best action, ...], best score).
105        """
106
107        self._stats_nodes_visited[-1] += 1
108
109        # If we see ourselves, then we have descended a full ply.
110        if (state.agent_index == self.agent_index):
111            ply_count -= 1
112
113        # At ply count zero, we just evaluate the current state and return up the tree.
114        # Note that we only hit this when we are the target agent (since we just decremented the ply count).
115        if (ply_count <= 0):
116            return [], self.evaluate_state(state)
117
118        # If the game is over, then stop descending.
119        if (state.game_over):
120            return [], self.evaluate_state(state)
121
122        legal_actions = state.get_legal_actions()
123
124        # Don't consider stopping unless we can do nothing else.
125        # This will help keep the game moving along.
126        if ((len(legal_actions) > 1) and (pacai.core.action.STOP in legal_actions)):
127            legal_actions.remove(pacai.core.action.STOP)
128
129        if (state.agent_index == self.agent_index):
130            # We are considering ourselves, get the max.
131            return self.minimax_step_max(state, ply_count, legal_actions, alpha, beta)
132
133        # We are considering an opposing agent (like a ghost), get the min or expected min.
134        if (self.expectimax):
135            return [], self.minimax_step_expected_min(state, ply_count, legal_actions, alpha, beta)
136
137        return self.minimax_step_min(state, ply_count, legal_actions, alpha, beta)

Step through one layer (one agent) of minimax and return all the best actions (there may be ties) along with their score. This method will handle various book keeping and call the correct minimax_step_min() or minimax_step_max() method.

When doing alpha-beta pruning, alpha represents the "best minimum" score while beta represents the "best maximum" score. They will typically start at inf and -inf, respectively.

Return: ([best action, ...], best score).

def minimax_step_max( self, state: pacai.core.gamestate.GameState, ply_count: int, legal_actions: list[pacai.core.action.Action], alpha: float, beta: float) -> tuple[list[pacai.core.action.Action], float]:
139    def minimax_step_max(self,
140            state: pacai.core.gamestate.GameState,
141            ply_count: int,
142            legal_actions: list[pacai.core.action.Action],
143            alpha: float,
144            beta: float,
145            ) -> tuple[list[pacai.core.action.Action], float]:
146        """
147        Perform a max step in minimax.
148        minimax_step() has already taken care of all the bookkeeping,
149        this method just needs to return the best actions along with their score.
150
151        alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.
152
153        The default implementation is just random and does not follow any minimax procedure.
154        Child classes should override this method.
155
156        Return: ([best action, ...], best score).
157        """
158
159        # Randomly choose an action.
160        action = self.rng.choice(legal_actions)
161
162        # Score the action.
163        successor = state.generate_successor(action, self.rng)
164        _, score = self.minimax_step(successor, ply_count, alpha, beta)
165
166        return [action], score

Perform a max step in minimax. minimax_step() has already taken care of all the bookkeeping, this method just needs to return the best actions along with their score.

alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.

The default implementation is just random and does not follow any minimax procedure. Child classes should override this method.

Return: ([best action, ...], best score).

def minimax_step_min( self, state: pacai.core.gamestate.GameState, ply_count: int, legal_actions: list[pacai.core.action.Action], alpha: float, beta: float) -> tuple[list[pacai.core.action.Action], float]:
168    def minimax_step_min(self,
169            state: pacai.core.gamestate.GameState,
170            ply_count: int,
171            legal_actions: list[pacai.core.action.Action],
172            alpha: float,
173            beta: float,
174            ) -> tuple[list[pacai.core.action.Action], float]:
175        """
176        Perform a min step in minimax.
177        minimax_step() has already taken care of all the bookkeeping,
178        this method just needs to return the best actions along with their score.
179
180        alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.
181
182        The default implementation is just random and does not follow any minimax procedure.
183        Child classes should override this method.
184
185        Return: ([best action, ...], best score).
186        """
187
188        # Randomly choose an action.
189        action = self.rng.choice(legal_actions)
190
191        # Score the action.
192        successor = state.generate_successor(action, self.rng)
193        _, score = self.minimax_step(successor, ply_count, alpha, beta)
194
195        return [action], score

Perform a min step in minimax. minimax_step() has already taken care of all the bookkeeping, this method just needs to return the best actions along with their score.

alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.

The default implementation is just random and does not follow any minimax procedure. Child classes should override this method.

Return: ([best action, ...], best score).

def minimax_step_expected_min( self, state: pacai.core.gamestate.GameState, ply_count: int, legal_actions: list[pacai.core.action.Action], alpha: float, beta: float) -> float:
197    def minimax_step_expected_min(self,
198            state: pacai.core.gamestate.GameState,
199            ply_count: int,
200            legal_actions: list[pacai.core.action.Action],
201            alpha: float,
202            beta: float,
203            ) -> float:
204        """
205        Perform a min step in expectimax.
206        minimax_step() has already taken care of all the bookkeeping,
207        this method just needs to return the expected score.
208
209        Note that unlike minimax_step_max() and minimax_step_min(),
210        no action is returned.
211
212        alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.
213
214        The default implementation is just random and does not follow any minimax procedure.
215        Child classes should override this method.
216        """
217
218        # Randomly choose an action.
219        action = self.rng.choice(legal_actions)
220
221        # Score the action.
222        successor = state.generate_successor(action, self.rng)
223        _, score = self.minimax_step(successor, ply_count, alpha, beta)
224
225        return score

Perform a min step in expectimax. minimax_step() has already taken care of all the bookkeeping, this method just needs to return the expected score.

Note that unlike minimax_step_max() and minimax_step_min(), no action is returned.

alpha and beta can be ignored (and just passed along) when not doing alpha-beta pruning.

The default implementation is just random and does not follow any minimax procedure. Child classes should override this method.