A simple tic-tac-toe board with X and O marks. Photo · Franco Debartolo / Unsplash
Three by three, two players, nine squares. Almost trivial — and an excellent first complete C++ program.

Welcome to Phase 5. You have all the language and toolbox pieces you need; from now on we put them to work. Three games, building in complexity: tic-tac-toe (this week), Scrabble (Week 39), chess (Week 41). Each one ends with an AI opponent.

Tic-tac-toe is small enough to fit in your head and rich enough to teach all the patterns: a 2D board state, a turn loop, a win check, and (next week) a complete-information AI. By the end of this week you'll have a playable command-line game in about 100 lines of C++.

The plan

  1. Represent the board. A 3×3 grid; each cell holds X, O, or empty.
  2. Print the board. ASCII output, simple and clear.
  3. Get a move. Read row,col from the user; validate.
  4. Apply the move. Place the player's mark.
  5. Check for win. Eight winning lines: 3 rows, 3 columns, 2 diagonals.
  6. Check for draw. Board full and no winner.
  7. Loop. Alternate players until a terminal state.

Modelling the board

enum class Cell { Empty, X, O };

class Board {
public:
    Board(): cells_{} {}

    Cell at(int r, int c) const { return cells_[r][c]; }

    bool place(int r, int c, Cell m) {
        if (r < 0 || r >= 3 || c < 0 || c >= 3) return false;
        if (cells_[r][c] != Cell::Empty) return false;
        cells_[r][c] = m;
        return true;
    }

    bool full() const {
        for (int r = 0; r < 3; r++)
            for (int c = 0; c < 3; c++)
                if (cells_[r][c] == Cell::Empty) return false;
        return true;
    }

private:
    Cell cells_[3][3];
};

Notice: the board's invariants are enforced by methods. Outside code can never write to cells_ directly, can never put two pieces in one square, can never place outside the grid. place() returns false on a bad move so the caller can prompt the user again. This is exactly the encapsulation pattern from Week 20.

The win check

Cell winner() const {
    // rows
    for (int r = 0; r < 3; r++)
        if (cells_[r][0] != Cell::Empty &&
            cells_[r][0] == cells_[r][1] &&
            cells_[r][1] == cells_[r][2])
            return cells_[r][0];
    // columns
    for (int c = 0; c < 3; c++)
        if (cells_[0][c] != Cell::Empty &&
            cells_[0][c] == cells_[1][c] &&
            cells_[1][c] == cells_[2][c])
            return cells_[0][c];
    // diagonals
    if (cells_[0][0] != Cell::Empty &&
        cells_[0][0] == cells_[1][1] &&
        cells_[1][1] == cells_[2][2])
        return cells_[0][0];
    if (cells_[0][2] != Cell::Empty &&
        cells_[0][2] == cells_[1][1] &&
        cells_[1][1] == cells_[2][0])
        return cells_[0][2];
    return Cell::Empty;     // no winner yet
}

Eight checks. Cells must agree, and not be empty. Returns the winning mark, or Cell::Empty if there's no winner. The verbosity is fine for tic-tac-toe; for chess (Week 41) we'll need a more efficient representation.

The game loop

int main() {
    Board board;
    Cell turn = Cell::X;

    while (true) {
        print_board(board);
        std::cout << (turn == Cell::X ? "X" : "O") << " move (row col): ";

        int r, c;
        std::cin >> r >> c;
        if (!board.place(r, c, turn)) {
            std::cout << "invalid move\n";
            continue;
        }

        Cell w = board.winner();
        if (w != Cell::Empty) {
            print_board(board);
            std::cout << (w == Cell::X ? "X" : "O") << " wins!\n";
            break;
        }
        if (board.full()) {
            print_board(board);
            std::cout << "draw.\n";
            break;
        }

        turn = (turn == Cell::X) ? Cell::O : Cell::X;
    }
}

That's it. Print, prompt, validate, check terminal states, swap turns, repeat. The whole game is a turn loop with three exit conditions (X wins, O wins, draw). Add the print_board function and you have a complete program.

Why this matters

You just used: encapsulation (private cells, public methods), 2D arrays, enums, member functions, validation, a main loop, input/output, conditional logic. Every concept from Phases 2 and 3, in one small program. This is the shape of every game program you'll ever write — model state, accept input, validate, check end conditions, render. The next two projects scale this up.

A game is just state and rules. The harder the game, the more interesting the rules.

Try it yourself

What's next

Right now the game has no opponent — it's two humans poking at the keyboard. Next week we add an AI that cannot lose. The algorithm is called minimax, it's old and elegant, and it's the conceptual ancestor of every game-AI system on Earth.

Week 38 is Tic-Tac-Toe AI — the Minimax algorithm.

Photo credit

Photo free under the Unsplash license. Tic-tac-toe · Franco Debartolo.