Kelick Tech Hub

Build, learn & keep up — tech, Linux & sports news included.

C++ Tic-Tac-Toe Project

Beginner•Half a day•2D arrays, functions, game logic

Two players, one 3×3 grid — the first project on the Tic-Tac-Toe path.

Share on X

What you will build

A two-player Tic-Tac-Toe game on a 3×3 board drawn in the console. The win-checking logic — rows, columns, diagonals — is a perfect workout in array indexing, and this humble game is step one of a three-project path that ends with networked multiplayer.

What you will learn

Feature checklist

Starter code

#include <iostream>
#include <array>

using Board = std::array<std::array<char, 3>, 3>;

bool wins(const Board& b, char p) {
    for (int i = 0; i < 3; ++i) {
        if (b[i][0] == p && b[i][1] == p && b[i][2] == p) return true; // row
        if (b[0][i] == p && b[1][i] == p && b[2][i] == p) return true; // col
    }
    if (b[0][0] == p && b[1][1] == p && b[2][2] == p) return true;
    if (b[0][2] == p && b[1][1] == p && b[2][0] == p) return true;
    return false;
}

void draw(const Board& b) {
    for (auto& row : b) {
        std::cout << " " << row[0] << " | " << row[1] << " | " << row[2] << "\n";
        std::cout << "---+---+---\n";
    }
}

// main(): fill board with ' ', alternate players,
// read row/col, place mark, check wins() after each move.

Full walkthrough

Step 1: Represent the board as data first

The starter defines using Board = std::array<std::array<char, 3>, 3> — a 3×3 grid of chars where ' ' means empty, 'X' and 'O' are the marks. Naming the type Board makes every function signature readable, and std::array gives you .fill() and safe copying with no manual memory work.

Step 2: Write draw() before any game logic

draw(const Board& b) walks the rows and prints ' X | O | ' style lines with a '---+---+---' separator between them. Build this first and call it on an empty board from main() — seeing the grid on screen proves your data layout before a single rule exists, which is exactly how you keep debugging sessions short.

void draw(const Board& b) {
    for (auto& row : b) {
        std::cout << " " << row[0] << " | " << row[1]
                  << " | " << row[2] << "\n";
        std::cout << "---+---+---\n";
    }
}

Step 3: Write wins() — the rule engine

wins(const Board& b, char p) returns true if player p owns any full line. One loop checks row i and column i together (that symmetry halves the code), then the two diagonals are checked explicitly. Writing it as a pure function of (board, player) matters: the AI and multiplayer versions later in this catalogue reuse this exact shape.

Step 4: Detect a draw separately from a win

A draw is not 'nobody won yet' — it is 'the board is full AND wins() is false for both players'. Count the non-space cells after each move; at 9 with no winner, declare the draw. Checking wins() first is essential, because the 9th move can also be the winning move.

Step 5: Build the turn loop in main()

Fill the board with spaces, set char turn = 'X', and loop: draw the board, prompt 'Player X, row and column (0-2): ', read two ints, and validate before placing. After a legal move, check wins() for the player who just moved, then swap turn with turn = (turn == 'X' ? 'O' : 'X').

Step 6: Validate moves without stealing the turn

A move is legal only if row and column are both within 0–2 AND the target cell is ' '. If it is illegal, print why ('That cell is taken.') and loop again WITHOUT swapping the turn — losing your move because of a typo feels unfair, and the checklist explicitly demands the retry behaviour.

if (r < 0 || r > 2 || c < 0 || c > 2 || b[r][c] != ' ') {
    std::cout << "Invalid move — try again.\n";
    continue;  // same player moves again
}

Step 7: End the game properly and offer a rematch

When wins() fires, draw the final board once more and announce 'Player X wins!' — players want to SEE the winning line, not just hear about it. Then ask 'Rematch? (y/n)': on 'y', refill the board with spaces and reset the turn to 'X'; on anything else, exit the loop and say goodbye.

Common mistakes

Compile and run

g++ -std=c++17 -o tic_tac_toe tic_tac_toe.cpp

Sample run

$ ./tic_tac_toe
  |   |  
---+---+---
  | X |  
---+---+---
  |   |  
Player X opened at 1,1 (centre). Player O, row and column (0-2):
> 0 0
Player X, row and column (0-2):
> 0 2
Player O, row and column (0-2):
> 2 2
Player X, row and column (0-2):
> 2 0
O |   | X
---+---+---
  | X |  
---+---+---
X |   | O
Player X wins! (the diagonal 0,2 - 1,1 - 2,0)
Rematch? (y/n):
> n

Try it in your browser

No browser demo is included for this project. Build the C++ version below — that is the real program.

Related Beginner projects

← All 60 projects · Classic site (Learn & Sandbox)

Download Fake GPS