How a computer plays perfect Tic Tac Toe: minimax explained
The Hard level in TicTacToe never loses. It doesn't memorise openings or use tricks. It uses minimax, a classic algorithm that looks at every possible future of the game.
The idea in one sentence
Assume your opponent will always make the move that's worst for you, and choose the move whose worst case is best. That's minimax: you maximise your result while your opponent minimises it.
Scoring the end of the game
Start with finished games, because they're easy to score. From the computer's point of view:
- a win is worth +10,
- a loss is worth −10,
- a draw is worth 0.
Every unfinished position gets its score from the positions it can lead to. If it's the computer's turn, the position is worth the highest score among its moves, because the computer will pick that move. If it's the opponent's turn, the position is worth the lowest score, because a good opponent will pick that move. Repeat this all the way back to the current position, and you know exactly what each move is worth.
A small example
Here X (the computer) is to move, and the bottom row is empty.
- Bottom-left: O picks one of the two remaining squares and X takes the last one. Neither side can complete a line, so whatever O does, it's a draw. This move scores 0.
- Bottom-middle: O replies bottom-left and completes the diagonal from top-right to bottom-left. This move scores −10.
- Bottom-right: the same reply wins for O again. This move also scores −10.
The best worst case is 0, so X plays bottom-left: it's the only move that blocks O's diagonal. Minimax found the block without being told that blocking matters. That's the beauty of it: there are no strategy rules in the code at all, only scoring and search.
Preferring faster wins
A plain +10/−10 score has a flaw. If the computer can win now or win in three moves, it scores both as +10 and might pick the slow one, which looks odd to a human. The fix is to subtract the search depth: a win found one move ahead scores 10 − 1 = 9, a win found five moves ahead scores 10 − 5 = 5. Losses work the other way, so the computer also delays defeat as long as possible when it's losing. The TicTacToe app uses exactly this scoring.
The code
Here is minimax in a few lines of pseudocode:
function minimax(board, depth, computersTurn):
if computer has won: return 10 - depth
if opponent has won: return depth - 10
if board is full: return 0
if computersTurn:
best = -infinity
for each empty square:
best = max(best, minimax(board with computer's mark, depth + 1, false))
else:
best = +infinity
for each empty square:
best = min(best, minimax(board with opponent's mark, depth + 1, true))
return best
To choose a move, run minimax for each empty square and play the one with the highest score.
How much work is it?
Searching the whole game from the empty board means visiting 549,946 positions, counting each one every time a different move order reaches it. That's nothing for a modern phone, but it can be reduced a lot.
Alpha-beta pruning
While searching, keep track of two numbers: the best score the computer is already guaranteed (alpha) and the best score the opponent is already guaranteed (beta). If a branch can be shown to be worse than an option already found, there's no point finishing it. For example, once the computer has found a move that draws, any move where the opponent has a reply that wins can be abandoned as soon as that reply is found. The remaining replies don't matter. Alpha-beta pruning gives exactly the same answer as full minimax while skipping a large share of the work. The TicTacToe app uses it.
Skipping the first move
There's an even simpler saving. On an empty board every move leads to a draw with perfect play, so there's nothing to calculate. When Hard moves first, the app skips the search and picks one of five squares at random: the centre or one of the four corners, which give the opponent the most chances to go wrong.
Making a perfect player less predictable
A perfect player can still be boring if it always plays the same game. Often several moves have the same best score, so the app collects all of them and picks one at random. You'll see different games against Hard, but it will never choose a move that's worse than another.
Why the other levels don't use minimax
Perfect play isn't much fun for everyone, so the easier levels deliberately play worse. Easy picks a random empty square. Medium takes a win if it has one, blocks yours, and otherwise plays a slightly random move that favours the centre and corners. Neither looks more than one move ahead, which is why both can be beaten with a fork. See how to beat the computer for your exact chances against each.
Beyond Tic Tac Toe
Minimax with alpha-beta pruning is one of the oldest ideas in game-playing programs. For games like chess, the full tree is far too large to search, so programs stop after a fixed number of moves and estimate the position with a scoring function. Tic Tac Toe is small enough to search to the very end, which is why the computer can play it perfectly.