def _value_eval(self, game: ReversiGame, piece: str) -> Union[float, int]: """The evaluation function for minimax. For Positional Player, the evaluation function will evaluate the positional advantage of the pieces on the board. Preconditions: - piece in {BLACK, WHITE} :param game: the current game state for evaluation :return: value evaluated from the current game state """ if game.get_winner() is not None: if game.get_winner() == piece: # win return math.inf elif game.get_winner() == 'Draw': # draw return 0 else: # lose return -math.inf else: num_black, num_white = game.get_num_pieces( )[BLACK], game.get_num_pieces()[WHITE] corner_black, corner_white = check_corners(game) board_filled = (num_black + num_white) / (game.get_size()**2) if piece == BLACK: if board_filled < 0.80: # early to middle game return 10 * (corner_black - corner_white) + len( game.get_valid_moves()) else: # end game return num_black / num_white else: if board_filled < 0.80: # early to middle game return 10 * (corner_white - corner_black) + len( game.get_valid_moves()) else: # end game return num_white / num_black
def make_move(self, game: ReversiGame, previous_move: Optional[str]) -> str: """Make a move given the current game. previous_move is the opponent player's most recent move, or None if no moves have been made. Preconditions: - There is at least one valid move for the given game state - len(game.get_valid_moves) > 0 :param game: the current game state :param previous_move: the opponent player's most recent move, or None if no moves have been made :return: a move to be made """ if len(game.get_valid_moves()) == 1: return game.get_valid_moves()[0] if previous_move is None: tree = MCTSTree(START_MOVE, copy.deepcopy(game)) else: tree = MCTSTree(previous_move, copy.deepcopy(game)) # assert self._tree.get_game_after_move().get_game_board() == game.get_game_board() # assert self._tree.get_game_after_move().get_current_player() == game.get_current_player() runs_so_far = 0 # the counter for the rounds of MCTS run time_start = time.time() # at least run 1 second, ends when exceeds time limit or finishes n runs while not (time.time() - time_start > max(self.time_limit, 1) or runs_so_far == self.n): tree.mcts_round(self._c) runs_so_far += 1 # update tree with the decided move move = tree.get_most_confident_move() return move
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
def build_minimax_tree(self, game: ReversiGame, piece: str, depth: int, find_max: bool, previous_move: str, alpha: float = -math.inf, beta: float = math.inf) -> MinimaxTree: """Construct a tree with a height of depth, prune branches based on the Tree's evaluate function""" game_tree = MinimaxTree(move=previous_move, maximize=find_max, alpha=alpha, beta=beta) if game.get_winner() is not None or depth == 0: game_tree.eval = self._value_eval(game, piece) else: valid_moves = game.get_valid_moves() random.shuffle(valid_moves) for move in valid_moves: game_after_move = game.simulate_move(move) subtree = self.build_minimax_tree(game_after_move, piece, depth - 1, not find_max, move, alpha=game_tree.alpha, beta=game_tree.beta) game_tree.add_subtree(subtree) if game_tree.beta <= game_tree.alpha: break return game_tree
def _run_ai_game(game_surface: pygame.Surface, size: int, ai_player: Union[MobilityTreePlayer, PositionalTreePlayer, RandomPlayer, ReversiGame, MCTSTimeSavingPlayer], user_side: str = BLACK) -> None: if size == 8: background = pygame.image.load('assets/gameboard8.png') elif size == 6: background = pygame.image.load('assets/gameboard6.png') else: raise ValueError("invalid size.") game_surface.blit(background, (0, 0)) pygame.display.flip() game = ReversiGame(size) previous_move = '*' if user_side == BLACK: ai_side: str = WHITE else: ai_side: str = BLACK board = game.get_game_board() _draw_game_state(game_surface, background, size, board) pass_move = pygame.image.load('assets/pass.png') while game.get_winner() is None: if (previous_move == '*' and user_side == WHITE) or game.get_current_player() == user_side: if game.get_valid_moves() == ['pass']: game.make_move('pass') previous_move = 'pass' surface = game_surface game_surface.blit(pass_move, (300, 300)) pygame.display.flip() pygame.time.wait(1000) game_surface.blit(surface, (0, 0)) pygame.display.flip() continue while True: event = pygame.event.wait() if event.type == pygame.MOUSEBUTTONDOWN: mouse_pos = pygame.mouse.get_pos() if 585 <= mouse_pos[0] <= 795 and 10 <= mouse_pos[1] <= 41: return else: move = _search_for_move(mouse_pos, size) print(move) if move == '' or move not in game.get_valid_moves(): continue else: previous_move = move game.make_move(move) board = game.get_game_board() _draw_game_state(game_surface, background, size, board) pygame.time.wait(1000) break if event.type == pygame.QUIT: return else: move = ai_player.make_move(game, previous_move) previous_move = move game.make_move(move) if move == 'pass': surface = game_surface game_surface.blit(pass_move, (300, 300)) pygame.display.flip() pygame.time.wait(1000) game_surface.blit(surface, (0, 0)) pygame.display.flip() else: board = game.get_game_board() _draw_game_state(game_surface, background, size, board) winner = game.get_winner() if winner == user_side: victory = pygame.image.load('assets/victory.png') game_surface.blit(victory, (300, 300)) pygame.display.flip() pygame.time.wait(3000) return elif winner == ai_side: defeat = pygame.image.load('assets/defeat.png') game_surface.blit(defeat, (300, 300)) pygame.display.flip() pygame.time.wait(3000) return else: draw = pygame.image.load('assets/draw.png') game_surface.blit(draw, (300, 300)) pygame.display.flip() pygame.time.wait(3000) return
def make_move(self, game: ReversiGame, previous_move: tuple[int, int]) -> tuple[int, int]: """Make a random move.""" return random.choice(list(game.get_valid_moves()))