def _minimax(self, root_move: tuple[int, int], depth: int, game: ReversiGame, alpha: float, beta: float) -> GameTree: """ _minimax is a minimax function with alpha-beta pruning implemented that returns a full GameTree where each node stores the given evaluation Preconditions - depth >= 0 """ white_move = (game.get_current_player() == -1) ret = GameTree(move=root_move, is_white_move=white_move) # early return at max depth if depth == self.depth: ret.evaluation = heuristic(game, self.heuristic_list) return ret possible_moves = list(game.get_valid_moves()) if not possible_moves: if game.get_winner() == 'white': ret.evaluation = 10000 elif game.get_winner() == 'black': ret.evaluation = -10000 else: ret.evaluation = 0 return ret random.shuffle(possible_moves) best_value = float('-inf') if not white_move: best_value = float('inf') for move in possible_moves: new_game = game.copy_and_make_move(move) new_tree = self._minimax(move, depth + 1, new_game, alpha, beta) ret.add_subtree(new_tree) # we update the alpha value when the maximizer is playing (white) if white_move and best_value < new_tree.evaluation: best_value = new_tree.evaluation alpha = max(alpha, best_value) if beta <= alpha: break # we update the beta value when the minimizer is playing (black) elif not white_move and best_value > new_tree.evaluation: best_value = new_tree.evaluation beta = min(beta, best_value) if beta <= alpha: break ret.evaluation = best_value return ret
def _minimax(self, root_move: tuple[int, int], game: ReversiGame, depth: int) -> GameTree: """ _minimax is a function that returns a tree where each node has a value determined by the minimax search algorithm """ white_move = (game.get_current_player() == -1) ret = GameTree(move=root_move, is_white_move=white_move) # early return if we have reached max depth if depth == self.depth: ret.evaluation = heuristic(game, self.heuristic_list) return ret possible_moves = list(game.get_valid_moves()) # game is over if there are no possible moves in a position if not possible_moves: # if there are no moves, then the game is over so we check for the winner if game.get_winner() == 'white': ret.evaluation = 10000 elif game.get_winner() == 'black': ret.evaluation = -10000 else: ret.evaluation = 0 return ret # shuffle for randomness random.shuffle(possible_moves) # best_value tracks the best possible move that the player can make # this value is maximized by white and minimized by black best_value = float('-inf') if not white_move: best_value = float('inf') for move in possible_moves: new_game = game.copy_and_make_move(move) new_subtree = self._minimax(move, new_game, depth + 1) if white_move: best_value = max(best_value, new_subtree.evaluation) else: best_value = min(best_value, new_subtree.evaluation) ret.add_subtree(new_subtree) # update the evaluation value of the tree once all subtrees are added ret.evaluation = best_value return ret