def minimax(board, depth, is_maximizing_player): """ Using minimax algorithm to find the optimal move for the current state of the game. :param board: type: numpy.ndarray The current state of the Tic Tac Toe board game :param depth: type: int How deep the decision tree to search The depth at the top of the tree is 0, as you go deeper, depth increases Maximum depth is 9, as Tic Tact Toe only have 9 moves available :param is_maximizing_player: type: bool True if maximizing player's turn (Bot) False if minimizing player's turn (Human) :return: type: tuple Contains the best minimax score and a list of moves that is derived from that score """ # Check if last node if win_check(board) or depth == 9: return heuristic_evaluation(board, depth), None best_moves = [] if is_maximizing_player: max_score = -inf # Loop possible moves in a single turn for branch, move in zip(*get_possible_branches(board, True)): score, _ = minimax(branch, depth + 1, False) if score > max_score: max_score = score best_moves = [move] elif score == max_score: best_moves.append(move) return max_score, best_moves else: min_score = +inf # Loop possible moves in a single turn for branch, move in zip(*get_possible_branches(board, False)): score, _ = minimax(branch, depth + 1, True) if score < min_score: min_score = score best_moves = [move] elif score == min_score: best_moves.append(move) return min_score, best_moves
def get_all_possible_board_states(turn_num, primary_state, secondary_state): """ Loops through all possible states of the board starting from the turn_num provided and ending on the last turn. Finally, collates them in a list :param turn_num: type: int The number of moves on the board :param primary_state: type: int The state of the player that had the first move, HUMAN_STATE or BOT_STATE :param secondary_state: type: int The state of the player that had the second move, HUMAN_STATE or BOT_STATE :return: type: list Containing all the possible board states given a turn number """ # Check for valid turn number if turn_num < 0 or turn_num > 9: raise ValueError("Turn number must be between 0 and 9") # Return blank board for turn 0 if not turn_num: return create_board() boards = [] temp_boards = [] board = create_board() # Player for that turn number for move in combinations(get_possible_moves(board), ceil(turn_num / 2)): temp_boards.append(deepcopy(board)) for row, box in move: temp_boards[-1][row][ box] = secondary_state if turn_num % 2 == 0 else primary_state # Other player for board in temp_boards: for move in combinations(get_possible_moves(board), floor(turn_num / 2)): boards.append(deepcopy(board)) for row, box in move: boards[-1][row][ box] = primary_state if turn_num % 2 == 0 else secondary_state # Remove all won/terminal board states if win_check(boards[-1]): del boards[-1] return boards
def main(): """ The main function of the application. Creates the board, players and loop their turns infinitely until a winner is found. """ # Introduction print( "Welcome to the game of Tic Tac Toe, your opponent is a bot running the Minimax algorithm" ) # Create Tic Tac Toe board board = create_board() # Create players human_mark, bot_mark = choose_mark() bot = Player(bot=True, state=BOT_STATE, mark=bot_mark) human = Player(bot=False, state=HUMAN_STATE, mark=human_mark) # Random starting player players = [human, bot] random.shuffle(players) # Loop turns while True: for player in players: print("\n{}'s turn".format( player_name := "Bot" if player.bot else "Human")) move = player.make_move(board) update_board(board, move, player) display_board(board, players) # Break infinite loop under conditions if win_check(board): return print("Game over. {} wins!".format(player_name)) elif is_board_full(board): return print("Game over. Draw!")
def minimax_alpha_beta(board, depth, is_maximizing_player, alpha, beta): """ Using minimax algorithm to find the optimal move for the current state of the game. This version of the minimax algorithm includes alpha beta pruning. The pruning prunes all the values that is equal or smaller/larger than the required threshold depending on the player. Thus, this version can only return 1 move, as the other moves are pruned. :param board: type: numpy.ndarray The current state of the Tic Tac Toe board game :param depth: type: int How deep the decision tree to search The depth at the top of the tree is 0, as you go deeper, depth increases Maximum depth is 9, as Tic Tact Toe only have 9 moves available :param is_maximizing_player: type: bool True if maximizing player's turn (Bot) False if minimizing player's turn (Human) :return: type: tuple Contains the best minimax score and a single move that is derived from that score """ # Check if leaf node if win_check(board) or depth == 9: return heuristic_evaluation(board, depth), None best_moves = None if is_maximizing_player: max_score = -inf # Loop possible moves in a single turn for branch, move in zip(*get_possible_branches(board, True)): score, _ = minimax_alpha_beta(branch, depth + 1, False, alpha, beta) if score > max_score: max_score = score best_moves = [move] # Alpha beta pruning alpha = max(alpha, score) if beta <= alpha: break return max_score, best_moves else: min_score = +inf # Loop possible moves in a single turn for branch, move in zip(*get_possible_branches(board, False)): score, _ = minimax_alpha_beta(branch, depth + 1, True, alpha, beta) if score < min_score: min_score = score best_moves = [move] elif score == min_score: best_moves.append(move) # Alpha beta pruning beta = min(beta, score) if beta <= alpha: break return min_score, best_moves
def main(): """ The main function of the game. Responsible for the setup of game window properties, creating players, scheduling scenes in the game, recording player statistics and looping the game. """ # Setup game screen, clock = setup_game() # Create list of players players = [] # Define whose turn player = None # Define stats recording records = { # Record turn number 'turn_num': 0, # Record bot wins 'bot_win': 0, # Record human wins 'human_win': 0, # Record draws 'draw': 0 } # Define screen states intro = True game = True # Create a blank Tic Tac Toe board board = create_board() # Game loop while True: # tick rate clock.tick(30) mouse_position = pygame.mouse.get_pos() mouse_clicked = False for event in pygame.event.get(): # Break loop if window is closed if event.type == pygame.QUIT: pygame.quit() sys.exit() # Break loop if ESC key is pressed elif event.type == pygame.KEYDOWN: if event.key == pygame.K_ESCAPE: pygame.quit() sys.exit() elif event.type == pygame.MOUSEBUTTONDOWN: mouse_clicked = True # White background/clear previous objects screen.fill(color_to_rgb("white")) if intro: # Draw selection screen interface_items = selection_screen(screen, mouse_position) render_items_to_screen(screen, interface_items) # Handle user input if mouse_clicked: human_input_selection_screen_handler(interface_items, players, mouse_position) # Proceed to next screen if user selected a choice & assign players if players: # Unpack players bot, human = players[0], players[1] # Random starting player player = random.choice(players) # Move on to game screen intro = False elif game: # Game scene # Draw board information interface_items = board_information(screen, records) render_items_to_screen(screen, interface_items) # Draw tic tac toe board interface_items = game_board(screen, board, players) render_items_to_screen(screen, interface_items) # Check if game is finished if win_check(board): # Game is finished # Highlight the winning row interface_items = highlight_win(interface_items, board) render_items_to_screen(screen, interface_items) # Add delay post_game_delay() # Record stats record_win(player, records) # Reset board board = create_board() # Next game, random starting turn again player = random.choice(players) elif is_board_full(board): # Game is finished # Add delay post_game_delay() # Record stats record_draw(records) # Reset board board = create_board() # Next game, random starting turn again player = random.choice(players) else: # Game not finished # Make a move (bot/human) if player.bot: # Bot turn bot_move_input_handler(board, bot) else: if mouse_clicked: # Human turn human_move_input_handler(board, interface_items, mouse_position, human) # Cycle turns if get_turn_number(board) != records["turn_num"]: if not win_check(board) and not is_board_full(board): # Subsequent turns player = human if player.bot else bot records["turn_num"] = get_turn_number(board) # Update screen pygame.display.update()