C Tutorial
C Project: Tic-Tac-Toe
A 3x3 board in a 2D array. Place X and O, print the grid, and detect a winner or a draw.
What you will build
Tic-tac-toe is a 3 by 3 grid of characters. Empty cells are a space. Players write 'X' and'O'. After every move the program prints the board and checks rows, columns, and both diagonals for a winner. If the grid fills with no winner, it is a draw.
Interactive play would wait on scanf. This tutorial plays a fixed sequence of coordinates so the output is the same every compile. Open Try it in C at /c/try — gcc, not Python, not HTML, not C++.
A 3x3 char board
Declare char board[3][3]. Fill it with spaces. A print helper writes three rows with| between cells so the grid is readable in a console.
Example
#include <stdio.h>
void print_board(char b[3][3]) {
for (int r = 0; r < 3; r++) {
printf(" %c | %c | %c\n", b[r][0], b[r][1], b[r][2]);
if (r < 2) {
printf("---+---+---\n");
}
}
}
int main(void) {
char board[3][3] = {
{' ', ' ', ' '},
{' ', ' ', ' '},
{' ', ' ', ' '},
};
board[0][0] = 'X';
board[1][1] = 'O';
print_board(board);
return 0;
}Row comes first, then column. board[0][0] is the top-left cell. That order must stay consistent in the move list and in the winner check.
Winner check
A player wins with three of the same mark in a line. Check three rows, three columns, and two diagonals. Skip a line whose first cell is still a space so three empties are not a win. Return the winning character, or a space if nobody has won yet.
Example
#include <stdio.h>
char winner(char b[3][3]) {
for (int i = 0; i < 3; i++) {
if (b[i][0] != ' ' && b[i][0] == b[i][1] && b[i][1] == b[i][2]) {
return b[i][0];
}
if (b[0][i] != ' ' && b[0][i] == b[1][i] && b[1][i] == b[2][i]) {
return b[0][i];
}
}
if (b[0][0] != ' ' && b[0][0] == b[1][1] && b[1][1] == b[2][2]) {
return b[0][0];
}
if (b[0][2] != ' ' && b[0][2] == b[1][1] && b[1][1] == b[2][0]) {
return b[0][2];
}
return ' ';
}
int main(void) {
char board[3][3] = {
{'X', 'O', ' '},
{'X', 'O', ' '},
{'X', ' ', ' '},
};
char w = winner(board);
if (w == ' ') {
printf("no winner yet\n");
} else {
printf("%c wins\n", w);
}
return 0;
}The left column is three X marks, so this prints X wins. Compile it at/c/try. Use the C editor, not the Python, HTML, or C++ one.
Play a fixed sequence
Store moves as row and column pairs. X starts. Alternate marks. After each legal move, print the board and test for a winner. This sequence fills the left column with X and the middle column with two O marks. X wins on move 5. The program then stops.
Example
#include <stdio.h>
void print_board(char b[3][3]) {
for (int r = 0; r < 3; r++) {
printf(" %c | %c | %c\n", b[r][0], b[r][1], b[r][2]);
if (r < 2) {
printf("---+---+---\n");
}
}
printf("\n");
}
char winner(char b[3][3]) {
for (int i = 0; i < 3; i++) {
if (b[i][0] != ' ' && b[i][0] == b[i][1] && b[i][1] == b[i][2]) {
return b[i][0];
}
if (b[0][i] != ' ' && b[0][i] == b[1][i] && b[1][i] == b[2][i]) {
return b[0][i];
}
}
if (b[0][0] != ' ' && b[0][0] == b[1][1] && b[1][1] == b[2][2]) {
return b[0][0];
}
if (b[0][2] != ' ' && b[0][2] == b[1][1] && b[1][1] == b[2][0]) {
return b[0][2];
}
return ' ';
}
int main(void) {
char board[3][3] = {
{' ', ' ', ' '},
{' ', ' ', ' '},
{' ', ' ', ' '},
};
int rows[] = {0, 0, 1, 1, 2};
int cols[] = {0, 1, 0, 1, 0};
int n = 5;
char mark = 'X';
for (int m = 0; m < n; m++) {
int r = rows[m];
int c = cols[m];
board[r][c] = mark;
printf("move %d: %c at %d,%d\n", m + 1, mark, r, c);
print_board(board);
char w = winner(board);
if (w != ' ') {
printf("%c wins\n", w);
return 0;
}
mark = (mark == 'X') ? 'O' : 'X';
}
printf("draw\n");
return 0;
}After five prints you should see X down the first column and the line X wins. If you change the last move to 2,2 instead of 2,0, the function should not declare a winner yet.
A scripted draw
Nine moves with no three-in-a-row should print draw. The board is full, winnerstill returns a space, and the loop ends. This second full program is a useful check that the diagonal tests do not fire on mixed marks.
Example
#include <stdio.h>
void print_board(char b[3][3]) {
for (int r = 0; r < 3; r++) {
printf(" %c | %c | %c\n", b[r][0], b[r][1], b[r][2]);
if (r < 2) {
printf("---+---+---\n");
}
}
printf("\n");
}
char winner(char b[3][3]) {
for (int i = 0; i < 3; i++) {
if (b[i][0] != ' ' && b[i][0] == b[i][1] && b[i][1] == b[i][2]) {
return b[i][0];
}
if (b[0][i] != ' ' && b[0][i] == b[1][i] && b[1][i] == b[2][i]) {
return b[0][i];
}
}
if (b[0][0] != ' ' && b[0][0] == b[1][1] && b[1][1] == b[2][2]) {
return b[0][0];
}
if (b[0][2] != ' ' && b[0][2] == b[1][1] && b[1][1] == b[2][0]) {
return b[0][2];
}
return ' ';
}
int main(void) {
char board[3][3];
for (int r = 0; r < 3; r++) {
for (int c = 0; c < 3; c++) {
board[r][c] = ' ';
}
}
int rows[] = {0, 0, 0, 1, 1, 2, 1, 2, 2};
int cols[] = {0, 1, 2, 2, 0, 0, 1, 2, 1};
int n = 9;
char mark = 'X';
for (int m = 0; m < n; m++) {
board[rows[m]][cols[m]] = mark;
printf("move %d: %c at %d,%d\n", m + 1, mark, rows[m], cols[m]);
print_board(board);
char w = winner(board);
if (w != ' ') {
printf("%c wins\n", w);
return 0;
}
mark = (mark == 'X') ? 'O' : 'X';
}
printf("draw\n");
return 0;
}Common mistakes
- Treating three spaces as a win because
' ' == ' ' == ' '. Guard withb[i][0] != ' 'first. - Mixing row and column when you place a mark. Print the coordinates beside each move until the grid looks right.
- Overwriting a filled cell. In a later interactive version, refuse a move when the cell is not a space.
- Checking for a winner only at the end. Print and test after every move so a five-move win stops the game.
Practice
- Refuse a move into an occupied cell and print an error, then continue with the rest of the script.
- Write a sequence where O wins on a diagonal and confirm the board prints after each of the seven moves.
- Count empty cells and use that count to detect a draw without requiring the move list to have length 9.