Skip to Content
Course content

182: Building a Tic-Tac-Toe Game with a Minimax AI

Click on the "Edit" button in the top corner of the screen to edit your slide content.

By now, you've likely got the basic mechanics of Tic-Tac-Toe working—handling the 3x3 grid, detecting a win, and swapping turns. But a game against a human is only half the battle. To make it a real challenge, we need an AI. When I first started writing game AI, my instinct was to treat it like a series of "if-then" rules. It seems intuitive, but as you'll see, that path leads to a lot of frustration.

The Trap of Hardcoded Heuristics

The naive way to build a Tic-Tac-Toe AI is to write a checklist of priorities. You might tell the computer: "First, check if you can win in one move. If not, check if the player can win in one move and block them. If neither, take the center square." This feels like how a human thinks, and for a casual player, it's often enough. But here is the problem: you're essentially trying to manually map out every possible strategic scenario in the game.

This approach breaks the moment the opponent does something unexpected or sets up a "fork"—a situation where they have two ways to win, and you can only block one. To fix a fork using hardcoded rules, you'd have to write an increasingly bloated set of nested if statements to detect specific patterns across the board. I've seen developers spend days polishing these rules only to find a simple sequence of moves that makes the AI look completely mindless. It's fragile, hard to maintain, and fundamentally limited.

Thinking Ahead with Minimax

Instead of telling the AI how to play, we should tell it how to evaluate the outcome. This is where the Minimax algorithm comes in. The core idea is that the AI simulates every single possible move it could make, then simulates every possible response the human could make, and so on, until the game ends in a win, loss, or draw.

In C++, this is a perfect use case for recursion. We treat the game as a "zero-sum" match: if the AI wins, that's a score of +10; if the human wins, it's -10; a draw is 0. The AI (the "Maximizer") wants to pick the move that leads to the highest possible score, while it assumes the human (the "Minimizer") will always pick the move that results in the lowest score for the AI.

// A simplified conceptual look at the recursive step
int minimax(Board& board, bool isMaximizing) {
    if (board.hasWon(AI)) return 10;
    if (board.hasWon(Human)) return -10;
    if (board.isFull()) return 0;

    if (isMaximizing) {
        int bestScore = -1000;
        for (auto move : board.getAvailableMoves()) {
            board.makeMove(move, AI);
            bestScore = std::max(bestScore, minimax(board, false));
            board.undoMove(move); // Backtrack to keep the board clean
        }
        return bestScore;
    } else {
        int bestScore = 1000;
        for (auto move : board.getAvailableMoves()) {
            board.makeMove(move, Human);
            bestScore = std::min(bestScore, minimax(board, true));
            board.undoMove(move);
        }
        return bestScore;
    }
}

The Trade-off: Computation vs. Certainty

Now, you might be wondering why we wouldn't use this for everything. The trade-off is computational cost. For Tic-Tac-Toe, the "state space" is tiny—there are fewer than 255,168 possible game positions. Your modern CPU can crunch through all of those in a fraction of a second. However, if you tried to apply a raw Minimax search to Chess, the number of possibilities would exceed the number of atoms in the observable universe. You'd be waiting until the end of time for the AI to make its first move.

But for our project, the trade-off is heavily in favor of Minimax. By shifting from "rules" to "search," we move from a bot that can be tricked to a bot that is mathematically unbeatable. It won't ever make a mistake because it has already seen the end of the game before it even touches the board. Just remember to handle your backtracking correctly—if you forget to undo a move during the recursive search, your AI will hallucinate a board state that doesn't exist and make nonsensical moves.




📋 Practical Task

Exercise: Implementing the Minimax Decision Engine

Your task is to implement the getBestMove() function for the Tic-Tac-Toe AI. You will be provided with a Board class that includes methods for makeMove(), undoMove(), and checkWinner().

Write a function that iterates through all available squares on the current board, calls the recursive minimax() function for each, and returns the coordinates of the move that yields the highest score. Ensure that your implementation correctly handles the "backtracking" phase so that the actual game board remains unchanged after the AI has decided on its move.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.