def test_players(player1: Player, player2: Player, iterations: int) -> None: """ test_players is a function that runs <iterations> number of games between player1 and player2 """ white = 0 black = 0 ties = 0 for _ in range(iterations): game = ReversiGame() prev_move = (-1, -1) while game.get_winner() is None: move = player1.make_move(game, prev_move) game.try_make_move(move) if game.get_winner() is None: prev_move = player2.make_move(game, move) game.try_make_move(prev_move) if game.get_winner() == 'white': print('White WINS') white += 1 elif game.get_winner() == 'black': print('Black WINS') black += 1 else: print('TIE') ties += 1 print("Player 1 Wins: " + str(black)) print("Player 2 Wins: " + str(white))
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 _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 run_game(black: Player, white: Player, size: int, verbose: bool = False) -> tuple[str, list[str]]: """Run a Reversi game between the two given players. Return the winner and list of moves made in the game. """ game = ReversiGame(size) move_sequence = [] previous_move = None timer = {BLACK: 0, WHITE: 0} current_player = black if verbose: game.print_game() while game.get_winner() is None: t0 = time.time() # record time before player make move previous_move = current_player.make_move(game, previous_move) t = time.time() # record time after player make move game.make_move(previous_move) move_sequence.append(previous_move) if verbose: if current_player is black: print(f'{BLACK} moved {previous_move}. Used {t - t0:.2f}s') else: print(f'{WHITE} moved {previous_move}. Used {t - t0:.2f}s') game.print_game() if current_player is black: timer[BLACK] += t - t0 current_player = white else: timer[WHITE] += t - t0 current_player = black # print winner if verbose: print(f'Winner: {game.get_winner()}') print( f'{BLACK}: {game.get_num_pieces()[BLACK]}, {WHITE}: {game.get_num_pieces()[WHITE]}' ) print( f'{BLACK} used {timer[BLACK]:.2f}s, {WHITE} used {timer[WHITE]:.2f}s' ) return game.get_winner(), move_sequence
def check_corners(game: ReversiGame) -> tuple[int, int]: """Return a tuple representing the number of corner taken by each side :param game: the game state to be checked :return: (corner taken by black, corner taken by white) """ board = game.get_game_board() corner_black, corner_white = 0, 0 for i in [0, game.get_size() - 1]: for j in [0, game.get_size() - 1]: if board[i][j] == BLACK: corner_black += 1 elif board[i][j] == WHITE: corner_white += 1 return corner_black, corner_white
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 player_to_string(game: reversi.ReversiGame, player_colour: str, player: ai_players.Player) \ -> str: """ Returns the string representation of the type of the player. Preconditions: - player_colour in {'white', 'black'} """ if game.get_human_player() == 1 and player_colour == 'black': return 'Human' elif game.get_human_player() == -1 and player_colour == 'white': return 'Human' else: # the player is one of the AI players if isinstance(player, ai_players.RandomPlayer): return 'Random Moves' elif (isinstance(player, ai_players.MinimaxPlayer) or isinstance(player, ai_players.MinimaxABPlayer)): return 'Minimax ' + str(player.depth)
def __init__(self, window: VisualReversi, size: int) -> None: tk.Frame.__init__(self) self.window = window self._board_pos = (84, 120) self._board_pixel_size = 637 self.previous_move_dis = '' self._click_wanted = tk.BooleanVar() # initialize game self.game = ReversiGame(size)
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 positional_early(game: ReversiGame, selected_board_weight: list, player: str) -> Union[float, int]: """Evaluates a board based on the positional advantage of black Preconditions: player in {BLACK, WHITE} """ if player == BLACK: opponent = WHITE else: opponent = BLACK eval_so_far = 0 board = game.get_game_board() for i in range(game.get_size() - 1): for j in range(game.get_size() - 1): if board[i][j] == player: eval_so_far += selected_board_weight[i][j] elif board[i][j] == opponent: eval_so_far -= selected_board_weight[i][j] return eval_so_far
def check_same(player1: Player, player2: Player) -> None: """ check_same is a function that determines if two players return the same move throughout a game. this is particularly useful for comparison between MinimaxPlayer and MinimaxABPlayer. It also gives the time that each player takes to find a move. You must comment out the random.shuffle() line of code in both players before testing """ game = ReversiGame() prev_move = (-1, -1) while game.get_winner() is None: start_time = time.time() print("Player 1 CHOOSING") move1 = player1.make_move(game, prev_move) print("--- %s seconds ---" % (time.time() - start_time)) start_time = time.time() print("Player 2 CHOOSING") move2 = player2.make_move(game, prev_move) print("--- %s seconds ---" % (time.time() - start_time)) print("Player 1 chose: ", move1, " Player 2 chose: ", move2) assert move1 == move2 game.try_make_move(move1) prev_move = move1
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 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 helper_dropdown_select_board_size(g: ReversiGame, colour_to_player: Dict, text: str) -> None: """ Set the board size given the text. Preconditions: - text is of the form '<int>x<int>' where the two integers are the same and greater than 0. """ global board_size_current # Update the current board size (why?) board_size_current = int(text.split('x')[0]) # Set new heuristics for players colour_to_player[1].set_heuristic(board_size_current) colour_to_player[-1].set_heuristic(board_size_current) # Update game board size g.set_board_size(board_size_current) # Start new game. g.start_game(human_player=g.get_human_player())
def heuristic(game: ReversiGame, heuristic_list: list[list[int]]) -> float: """ heuristic takes a given heuristic_list and returns the game-state value given by the list """ if game.get_winner() is None: pieces = game.get_board().pieces black = 0 white = 0 length = len(pieces) for i in range(length): for m in range(length): if pieces[i][m] == 1: black += heuristic_list[i][m] elif pieces[i][m] == -1: white += heuristic_list[i][m] return white - black elif game.get_winner() == 'white': return 100000 elif game.get_winner() == 'black': return -100000 else: return 0
def helper_dropdown_select_player(g: ReversiGame, text: str) -> None: """HELPER FUNCTION: Select the players given the dropdown option selected.""" if text == "Human vs. AI": g.start_game(human_player=1) elif text == "AI vs. Human": g.start_game(human_player=-1) else: g.start_game(human_player=0)
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 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()
def _value_eval(self, game: ReversiGame, piece: str) -> Union[float, int]: """The evaluation function for minimax. The evaluation function will return the number of piece of its side Preconditions: - piece in {BLACK, WHITE} :param game: the current game state for evaluation :return: the evaluated value of the current 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: if piece == BLACK: return game.get_num_pieces()[BLACK] / game.get_num_pieces( )[WHITE] else: return game.get_num_pieces()[WHITE] / game.get_num_pieces( )[BLACK]
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] board_filled = (num_black + num_white) / (game.get_size()**2) if game.get_size() == 8: selected_board_weight = BOARD_WEIGHT_8 else: selected_board_weight = BOARD_WEIGHT_6 if board_filled < 0.80: return positional_early(game, selected_board_weight, piece) else: if piece == BLACK: return num_black / num_white if piece == WHITE: return num_white / num_black return 0
from window import Window from reversi import ReversiGame from board_manager import BoardManager from ai_players import MinimaxABPlayer if __name__ == "__main__": # Initialize PyGame pygame.init() # Create a window wrapper class instance window = Window() # Create a ReversiGame instance # Change human_player to 1 or -1 to play against AI, 0 if no human player game = ReversiGame(human_player=1) # Set to 1 by default. # Setup the BoardManager instance board_manager = BoardManager(window) ui_handler = UIHandler() # Minimax Player player1 = MinimaxABPlayer(2, 8) player2 = MinimaxABPlayer(2, 8) colour_to_player = {1: player1, -1: player2} # Set number of games num_games_left = 40
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()))