Week 37 · Phase 5 — The Builds
First capstone — logical foundations and the simplest meaningful game in computing.
Photo · Franco Debartolo / Unsplash
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++.
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.
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.
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.
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.
tictactoe.cpp. Compile. Play it. Beat it. (You will — it has no AI yet.)std::array<std::array<Cell, 3>, 3>. Same code, slightly safer.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 free under the Unsplash license. Tic-tac-toe · Franco Debartolo.