C++ Tic-Tac-Toe with AI Project
The beginner board grows a brain: an AI that can never lose.
What you will build
Take the two-player Tic-Tac-Toe and add a computer opponent powered by minimax — the algorithm that explores every possible future and chooses the move that can't be beaten. This is step two of the Tic-Tac-Toe path, and your first real game AI.
What you will learn
- The minimax algorithm
- Scoring terminal states (win/loss/draw)
- Recursion over a game tree
- Difficulty levels via search depth or mistakes
Feature checklist
- Play as X against the computer's O
- AI plays perfectly (unbeatable on hard)
- Easy mode: AI occasionally picks randomly
- Win/draw detection from the base game
- Score tally across rounds
- Choose who moves first
Starter code
#include <array>
#include <algorithm>
using Board = std::array<std::array<char, 3>, 3>;
char winner(const Board& b); // 'X', 'O', 'D' draw, ' ' still playing
// Score from O's perspective: +10 win, -10 loss, 0 draw.
int minimax(Board& b, bool aiTurn) {
char w = winner(b);
if (w == 'O') return 10;
if (w == 'X') return -10;
if (w == 'D') return 0;
int best = aiTurn ? -100 : 100;
for (int r = 0; r < 3; ++r)
for (int c = 0; c < 3; ++c)
if (b[r][c] == ' ') {
b[r][c] = aiTurn ? 'O' : 'X';
int s = minimax(b, !aiTurn);
b[r][c] = ' ';
best = aiTurn ? std::max(best, s) : std::min(best, s);
}
return best;
}
std::pair<int,int> bestMove(Board& b) {
int bestScore = -100; std::pair<int,int> move{-1, -1};
for (int r = 0; r < 3; ++r)
for (int c = 0; c < 3; ++c)
if (b[r][c] == ' ') {
b[r][c] = 'O';
int s = minimax(b, false);
b[r][c] = ' ';
if (s > bestScore) { bestScore = s; move = {r, c}; }
}
return move;
}Full walkthrough
Step 1: Start from the beginner game — literally
Reuse the Board type (std::array<std::array<char, 3>, 3>), draw(), and wins() from the beginner Tic-Tac-Toe unchanged. This project is step 2 of the learning path precisely because the board machinery is solved; the ONLY new problem is choosing the computer's move.
Step 2: Score a finished board
Write a helper that returns +10 if O (the AI) has won, -10 if X has won, 0 otherwise (draw or unfinished). Minimax needs a number to maximise; these values are the entire definition of 'good' the AI will ever know.
Step 3: Write minimax(): the AI imagines every future
minimax(board, isMaximising) — if the board is terminal, return its score. Otherwise: for every empty cell, place the current side's mark, recurse for the other side, undo the mark, and keep the best score (max for O, min for X). The undo step is backtracking again — Sudoku's move in a new costume. On a 3×3 board the whole game tree is tiny, so no pruning is needed to be instant.
int minimax(Board& b, bool isMax) {
int s = score(b);
if (s != 0 || full(b)) return s;
int best = isMax ? -100 : 100;
for each empty cell {
b[r][c] = isMax ? 'O' : 'X';
int v = minimax(b, !isMax);
b[r][c] = ' ';
best = isMax ? std::max(best, v) : std::min(best, v);
}
return best;
}Step 4: bestMove(): try every move, keep the best score
bestMove() loops over empty cells, tentatively places 'O', calls minimax for the human's reply, undoes, and remembers the cell with the highest value. First-best wins ties, which is fine. This function is the whole AI: everything else is plumbing you already wrote.
Step 5: Add Easy mode by sometimes not thinking
Easy mode: with probability ~50% (use your mt19937), pick a random empty cell instead of bestMove(). The AI becomes beatable without becoming stupid on every turn — difficulty as a dial on how OFTEN the AI plays perfectly is a trick real games use constantly.
Step 6: Wire the round loop and score tally
Alternate human (X) and AI (O) turns with wins() checked after each, tracking score across rounds and letting the player choose who moves first (moving first is a real advantage the tally makes visible). If the AI goes first, it opens in a corner or centre — minimax will find those itself; watch it happen.
Step 7: Try to beat Hard mode — you will not
Play twenty games on Hard and count your wins: the correct count is 0 (draws and losses only). If you ever win, either minimax's min/max is flipped somewhere or score() mislabels a winner — the unbeatability is a testable specification, not a boast. That is a rare and wonderful property in a program: a proof you can play.
Common mistakes
- Placing the mark and forgetting to undo it inside minimax, so the imagined board corrupts the real one.
- Maximising on the human's turn too — the opponent must MINIMISE your score; symmetric code, opposite goal.
- Scoring a draw the same as an AI win, teaching the AI to prefer... everything equally; draws are 0, strictly between -10 and +10.
Compile and run
g++ -std=c++17 -o tictactoe_ai tictactoe_ai.cppSample run
$ ./tictactoe_ai
Tic-Tac-Toe vs the computer. Difficulty (E)asy or (H)ard:
> H
You are X. Who moves first — (Y)ou or (C)omputer?
> Y
Your move, row and column:
> 1 1
Computer plays O at row 0, column 0.
O | |
---+---+---
| X |
---+---+---
| |
Your move, row and column:
> 2 2
Computer plays O at row 0, column 2.
Your move, row and column:
> 0 1
Computer plays O at row 2, column 1 — blocked your column!
(...the game grinds to the only result Hard mode allows...)
It's a draw. Score: you 0 · computer 0 · draws 1
On Hard, minimax never loses.Try it in your browser
No browser demo is included for this project. Build the C++ version below — that is the real program.