Python Noughts and Crosses - Bug Issues with Computer Strategy Logic

I’m developing a noughts and crosses game in Python where the computer is meant to play intelligently, either winning when it has the chance or blocking the player from winning. While the game is functional, I encounter errors during the player’s turn, and I’m at a loss regarding the issue.

Here’s the code I’m using:

import random

def start_game():
    available_spots = [0, 1, 2, 3, 4, 5, 6, 7, 8]
    grid = ["0", "1", "2", "3", "4", "5", "6", "7", "8"]
    
    def show_rules():
        print("Welcome to Noughts and Crosses! Pick X or O, then choose spots 0-8 to place your mark.")
    
    def pick_symbol():
        choice = input("Pick X or O: ")
        return choice
    
    def show_grid():
        print(f"{grid[0]}|{grid[1]}|{grid[2]}")
        print("-+-+-")
        print(f"{grid[3]}|{grid[4]}|{grid[5]}")
        print("-+-+-")
        print(f"{grid[6]}|{grid[7]}|{grid[8]}")
    
    def check_winner(board, human_symbol, ai_symbol):
        winning_combos = [
            [0,1,2], [3,4,5], [6,7,8],  # rows
            [0,3,6], [1,4,7], [2,5,8],  # columns
            [0,4,8], [2,4,6]            # diagonals
        ]
        for combo in winning_combos:
            if board[combo[0]] == board[combo[1]] == board[combo[2]]:
                return True
        return False
    
    def can_win_next(board):
        test_board = board.copy()
        for spot in available_spots:
            test_board[spot] = ai_symbol
            if check_winner(test_board, human_symbol, ai_symbol):
                return spot
        return None
    
    def must_block(board):
        test_board = board.copy()
        for spot in available_spots:
            test_board[spot] = human_symbol
            if check_winner(test_board, human_symbol, ai_symbol):
                return spot
        return None
    
    def ai_move():
        # Try to win first
        win_spot = can_win_next(grid)
        if win_spot is not None:
            return win_spot
        
        # Try to block player
        block_spot = must_block(grid)
        if block_spot is not None:
            return block_spot
        
        # Otherwise pick corners or center
        corners = [0, 2, 6, 8]
        for corner in corners:
            if corner in available_spots:
                return corner
        
        if 4 in available_spots:
            return 4
        
        return random.choice(available_spots)

Currently, the can_win_next and must_block functions are proving problematic. They should guide the computer in making wise moves, but something isn’t functioning correctly, causing errors during the human player’s turn. I’ve spent considerable time debugging this but haven’t pinpointed the issue.

Any suggestions on what might be wrong with the AI’s strategy logic?

Your main problem is defining functions inside start_game() while the AI strategy functions try accessing variables that don’t exist yet. When can_win_next() and must_block() run, human_symbol and ai_symbol aren’t initialized.

Beyond the scope issue, your check_winner function has another bug - it checks if three positions match but doesn’t verify they’re actual player symbols. It might return True for positions that still have the original grid numbers.

I ran into the same issues building my tic-tac-toe game. What fixed it: pass all needed parameters directly to each function, and make check_winner() confirm the matching positions contain real player symbols (X or O), not just any matching values. Also, initialize your symbols at the start of the game loop instead of inside nested functions.

your human_symbol and ai_symbol vars aren’t defined when can_win_next() and must_block() try to use them. they’re only local to wherever you created them. pass the symbols as params to these functions or make them global.

Yeah, scope’s part of it, but manually handling game state and AI logic is a nightmare.

I hit the same wall building automated game tests. Instead of fighting Python scope bugs, I just moved everything to Latenode workflows.

Set up your AI as decision trees - each move triggers a workflow that checks wins, blocks, and backup moves. State management becomes visual and actually debuggable.

Bonus: adding move history, difficulty levels, or multiplayer is just connecting new nodes. No code rewrites.

The visual editor shows exactly what happens at each decision. No more scope guessing games.