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
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.
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(). """
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.
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.
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.
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.
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).
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).
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).
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.