def _run_ai_simulation(game_surface: pygame.Surface, size: int, player1: Union[MobilityTreePlayer, PositionalTreePlayer, RandomPlayer, ReversiGame, MCTSTimeSavingPlayer], player2: Union[MobilityTreePlayer, PositionalTreePlayer, RandomPlayer, ReversiGame, MCTSTimeSavingPlayer]) -> 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 = '*' board = game.get_game_board() _draw_game_state(game_surface, background, size, board) pass_move = pygame.image.load('assets/pass.png') player1_side = BLACK while game.get_winner() is None: if previous_move == '*' or game.get_current_player() == player1_side: move = player1.make_move(game, previous_move) else: move = player2.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(500) game_surface.blit(surface, (0, 0)) pygame.display.flip() else: board = game.get_game_board() _draw_game_state(game_surface, background, size, board) pygame.time.wait(500) winner = game.get_winner() if winner == BLACK: victory = pygame.image.load('assets/player1_victory.png') game_surface.blit(victory, (300, 300)) pygame.display.flip() pygame.time.wait(3000) return elif winner == WHITE: defeat = pygame.image.load('assets/player2_victory.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 _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 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 self._tree is None: # initialize a tree if there is no tree if previous_move is None: self._tree = MCTSTree(START_MOVE, copy.deepcopy(game)) else: self._tree = MCTSTree(previous_move, copy.deepcopy(game)) else: # update tree with previous move if there is a tree if len(self._tree.get_subtrees()) == 0: self._tree.expand() if previous_move is not None: self._tree = self._tree.find_subtree_by_move(previous_move) 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() for _ in range(self._n): self._tree.mcts_round(self._c) # update tree with the decided move move = self._tree.get_most_confident_move() self._tree = self._tree.find_subtree_by_move(move) return move
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 """ piece = game.get_current_player() tree = self.build_minimax_tree(game, piece, depth=self._depth, find_max=True, previous_move=previous_move) return tree.get_best()
# Window loop while window.is_running(): """ UPDATE STUFF """ # Get game winner winner = game.get_winner() if winner is not None: results.append(winner) ui_handler.update_games_stored_text(len(results), window) increment_player_score(winner, window) game.start_game(human_player=game.get_human_player()) # If the game is not paused, look for mouse clicks and process moves. if not ui_handler.get_game_paused(): if game.get_human_player() == game.get_current_player(): # Look at the mouse clicks and see if they are in the board. for event in window.get_events(): if event[0] == pygame.MOUSEBUTTONUP: square = board_manager.check_mouse_press( event[1], game.get_board()) if square != (-1, -1): if game.try_make_move(square): moves_made.append(square) elif game.get_winner() is None: next_move = colour_to_player[ game.get_current_player()].make_move(game, moves_made[-1]) game.try_make_move(next_move) moves_made.append(next_move)
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