Back

Activity 5.6 — Milestone: Tic-Tac-Toe

divider

Introduction

Activity 5.6

Milestone Program: Tic-Tac-Toe

Why this milestone?

  • You now know arrays, traversal, and 2D arrays.
  • This game forces you to use grid thinking.
  • You'll practice methods, validation, and algorithms.

Board Model

  • We store the board as a 3×3 2D array.
  • '-' means empty.
  • 'X' and 'O' are player moves.
// Tic-Tac-Toe board as a 2D array (3 rows, 3 columns)
char[][] board =
{
{'-', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}
};
// board[row][col] is one cell

Coordinates

Players choose a row and col (0–2).

// Printing seeable coordinates
// (r,c) indices:
// (0,0) (0,1) (0,2)
// (1,0) (1,1) (1,2)
// (2,0) (2,1) (2,2)

Move Rules

  • Row and col must be within 0–2
  • The cell must be empty
  • If a move is invalid, we ask again
// Valid move rules
// 1) row and col must be 0..2
// 2) chosen cell must be empty ('-')

Game Loop (what repeats?)

  • Print the board
  • Ask current player for a move
  • Validate move
  • Place piece
  • Check win / tie
  • Switch players

Switching Players

After a valid move, switch turns.

// Turn switching
char currentPlayer = 'X';
// after a successful move:
if (currentPlayer == 'X')
{
currentPlayer = 'O';
}
else
{
currentPlayer = 'X';
}

Win Checking

A player wins by making 3 in a row.

// Win check idea: a player wins if 3 in a row match
// Rows: board[r][0], board[r][1], board[r][2]
// Cols: board[0][c], board[1][c], board[2][c]
// Diagonals: (0,0)(1,1)(2,2) and (0,2)(1,1)(2,0)

We check rows, columns, and diagonals.

Example: Row Win Check

One row check is just comparing three positions.

// Example row check (r = 1)
if (board[1][0] == player &&
board[1][1] == player &&
board[1][2] == player)
{
return true;
}

Tie Checking

  • If nobody won and the board has no empty cells, it's a tie.
  • This requires scanning the entire grid.
// Tie check idea
// If no '-' remain and nobody won -> tie

How you'll build it

  • Start with printing the board
  • Add one move + validation
  • Add win check + tie check as methods
  • Combine into the full game loop

'F' → Fullscreen

Objectives

  • icon Use a 2D array to represent a 3×3 game board.
  • icon Traverse and print a grid using nested loops.
  • icon Validate user input before updating the board.
  • icon Write methods to check win conditions and ties.
divider

Activity Tasks

  • icon Create a new project named Activity5_6_TicTacToe.
  • icon Complete Tasks 1–4 first, then build the full game in Task 5.
  • icon Use Allman braces in all code.
  • icon Keep your code neat: methods go below main.

Figures: Reference Methods

Figure 1 — printBoard(board)
// Figure 1 — A printable board method (Allman style)
public static void printBoard(char[][] board)
{
System.out.println();
System.out.println(" 0 1 2");
System.out.println(" -----------");
for (int r = 0; r < board.length; r++)
{
System.out.print(r + " |");
for (int c = 0; c < board[r].length; c++)
{
System.out.print(" " + board[r][c] + " |");
}
System.out.println();
System.out.println(" -----------");
}
System.out.println();
}
Figure 2 — isValidMove(board, row, col)
// Figure 2 — Move validation (Allman style)
public static boolean isValidMove(char[][] board, int row, int col)
{
if (row < 0 || row >= board.length)
{
return false;
}
if (col < 0 || col >= board[row].length)
{
return false;
}
if (board[row][col] != '-')
{
return false;
}
return true;
}
Figure 3 — checkWin(board, player)
// Figure 3 — Win check (Allman style)
public static boolean checkWin(char[][] board, char player)
{
// Rows
for (int r = 0; r < board.length; r++)
{
if (board[r][0] == player && board[r][1] == player && board[r][2] == player)
{
return true;
}
}
// Columns
for (int c = 0; c < board[0].length; c++)
{
if (board[0][c] == player && board[1][c] == player && board[2][c] == player)
{
return true;
}
}
// Diagonals
if (board[0][0] == player && board[1][1] == player && board[2][2] == player)
{
return true;
}
if (board[0][2] == player && board[1][1] == player && board[2][0] == player)
{
return true;
}
return false;
}
Figure 4 — isBoardFull(board)
// Figure 4 — Tie check (Allman style)
public static boolean isBoardFull(char[][] board)
{
for (int r = 0; r < board.length; r++)
{
for (int c = 0; c < board[r].length; c++)
{
if (board[r][c] == '-')
{
return false;
}
}
}
return true;
}

Task 1: Create + Print the Board

  • icon Create a 3×3 board filled with '-'.
  • icon Print it using a nested-loop method.
  • icon Your board should include row/col labels.
Task 1 — printBoard
public class Program
{
public static void main(String[] args)
{
// Task 1 — Build the board + print it
//
// 1) Create a 3x3 char board filled with '-'
// 2) Call printBoard(board) to display it
//
// Expected: an empty 3x3 grid with row/col labels.
char[][] board =
{
{'-', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}
};
printBoard(board);
}
public static void printBoard(char[][] board)
{
System.out.println();
System.out.println(" 0 1 2");
System.out.println(" -----------");
for (int r = 0; r < board.length; r++)
{
System.out.print(r + " |");
for (int c = 0; c < board[r].length; c++)
{
System.out.print(" " + board[r][c] + " |");
}
System.out.println();
System.out.println(" -----------");
}
System.out.println();
}
}

Task 2: Place One Move (with Validation)

  • icon Ask the user for row and col.
  • icon Validate the move, then place an 'X'.
  • icon Print the board after the attempt.
Task 2 — One Turn
import java.util.Scanner;
public class Program
{
public static void main(String[] args)
{
// Task 2 — Place a move (single turn)
//
// 1) Start with an empty board
// 2) Ask the user for row and col (0-2)
// 3) If the move is valid, place 'X' there
// 4) Print the board
//
// Note: We'll build the full game loop later.
char[][] board =
{
{'-', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}
};
Scanner scanner = new Scanner(System.in);
printBoard(board);
System.out.print("Enter row (0-2): ");
int row = scanner.nextInt();
System.out.print("Enter col (0-2): ");
int col = scanner.nextInt();
if (isValidMove(board, row, col))
{
board[row][col] = 'X';
System.out.println("Move placed!");
}
else
{
System.out.println("Invalid move.");
}
printBoard(board);
scanner.close();
}
public static void printBoard(char[][] board)
{
System.out.println();
System.out.println(" 0 1 2");
System.out.println(" -----------");
for (int r = 0; r < board.length; r++)
{
System.out.print(r + " |");
for (int c = 0; c < board[r].length; c++)
{
System.out.print(" " + board[r][c] + " |");
}
System.out.println();
System.out.println(" -----------");
}
System.out.println();
}
public static boolean isValidMove(char[][] board, int row, int col)
{
if (row < 0 || row >= board.length)
{
return false;
}
if (col < 0 || col >= board[row].length)
{
return false;
}
if (board[row][col] != '-')
{
return false;
}
return true;
}
}

Task 3: Win Checking Method

  • icon Implement checkWin(board, player).
  • icon Test it using the provided winning board.
Task 3 — checkWin
public class Program
{
public static void main(String[] args)
{
// Task 3 — Implement checkWin(board, player)
//
// 1) Copy the board below
// 2) Call checkWin(board, 'X') and print the result
//
// The board below is a win for X on the top row.
char[][] board =
{
{'X', 'X', 'X'},
{'O', '-', 'O'},
{'-', '-', '-'}
};
boolean xWon = checkWin(board, 'X');
System.out.println("X won? " + xWon);
}
public static boolean checkWin(char[][] board, char player)
{
// Rows
for (int r = 0; r < board.length; r++)
{
if (board[r][0] == player && board[r][1] == player && board[r][2] == player)
{
return true;
}
}
// Columns
for (int c = 0; c < board[0].length; c++)
{
if (board[0][c] == player && board[1][c] == player && board[2][c] == player)
{
return true;
}
}
// Diagonals
if (board[0][0] == player && board[1][1] == player && board[2][2] == player)
{
return true;
}
if (board[0][2] == player && board[1][1] == player && board[2][0] == player)
{
return true;
}
return false;
}
}

Task 4: Tie Helper (Board Full)

  • icon Implement isBoardFull(board).
  • icon Test it using a full and not-full board.
Task 4 — isBoardFull
public class Program
{
public static void main(String[] args)
{
// Task 4 — Implement isBoardFull(board)
//
// 1) Create two boards:
// A) a full board (no '-')
// B) a not-full board (has '-')
// 2) Print isBoardFull(...) for each
char[][] fullBoard =
{
{'X', 'O', 'X'},
{'X', 'O', 'O'},
{'O', 'X', 'X'}
};
char[][] notFullBoard =
{
{'X', 'O', '-'},
{'X', '-', 'O'},
{'O', 'X', 'X'}
};
System.out.println("Full board? " + isBoardFull(fullBoard));
System.out.println("Full board? " + isBoardFull(notFullBoard));
}
public static boolean isBoardFull(char[][] board)
{
for (int r = 0; r < board.length; r++)
{
for (int c = 0; c < board[r].length; c++)
{
if (board[r][c] == '-')
{
return false;
}
}
}
return true;
}
}

Task 5: Full Game (Milestone)

  • icon Combine everything into a full Tic-Tac-Toe loop.
  • icon Players alternate between X and O.
  • icon Stop the game on win or tie.
  • icon Print helpful messages after each move.
Task 5 — Full Tic-Tac-Toe
import java.util.Scanner;
public class Program
{
public static void main(String[] args)
{
// Task 5 — Milestone: Full Tic-Tac-Toe Game
//
// Features:
// - 2D char board
// - player turns (X then O)
// - input validation (row/col in range, cell empty)
// - win detection
// - tie detection
//
// Suggested flow:
// while (true)
// print board
// prompt move for current player
// if invalid -> message and continue
// place piece
// if win -> print board + winner and break
// if tie -> print board + tie message and break
// switch player
char[][] board =
{
{'-', '-', '-'},
{'-', '-', '-'},
{'-', '-', '-'}
};
Scanner scanner = new Scanner(System.in);
char currentPlayer = 'X';
while (true)
{
printBoard(board);
System.out.println("Player " + currentPlayer + "'s turn.");
System.out.print("Enter row (0-2): ");
int row = scanner.nextInt();
System.out.print("Enter col (0-2): ");
int col = scanner.nextInt();
if (!isValidMove(board, row, col))
{
System.out.println("Invalid move. Try again.");
continue;
}
board[row][col] = currentPlayer;
if (checkWin(board, currentPlayer))
{
printBoard(board);
System.out.println("Player " + currentPlayer + " wins!");
break;
}
if (isBoardFull(board))
{
printBoard(board);
System.out.println("It's a tie!");
break;
}
// switch player
if (currentPlayer == 'X')
{
currentPlayer = 'O';
}
else
{
currentPlayer = 'X';
}
}
scanner.close();
}
public static void printBoard(char[][] board)
{
System.out.println();
System.out.println(" 0 1 2");
System.out.println(" -----------");
for (int r = 0; r < board.length; r++)
{
System.out.print(r + " |");
for (int c = 0; c < board[r].length; c++)
{
System.out.print(" " + board[r][c] + " |");
}
System.out.println();
System.out.println(" -----------");
}
System.out.println();
}
public static boolean isValidMove(char[][] board, int row, int col)
{
if (row < 0 || row >= board.length)
{
return false;
}
if (col < 0 || col >= board[row].length)
{
return false;
}
if (board[row][col] != '-')
{
return false;
}
return true;
}
public static boolean checkWin(char[][] board, char player)
{
// Rows
for (int r = 0; r < board.length; r++)
{
if (board[r][0] == player && board[r][1] == player && board[r][2] == player)
{
return true;
}
}
// Columns
for (int c = 0; c < board[0].length; c++)
{
if (board[0][c] == player && board[1][c] == player && board[2][c] == player)
{
return true;
}
}
// Diagonals
if (board[0][0] == player && board[1][1] == player && board[2][2] == player)
{
return true;
}
if (board[0][2] == player && board[1][1] == player && board[2][0] == player)
{
return true;
}
return false;
}
public static boolean isBoardFull(char[][] board)
{
for (int r = 0; r < board.length; r++)
{
for (int c = 0; c < board[r].length; c++)
{
if (board[r][c] == '-')
{
return false;
}
}
}
return true;
}
}
divider

Sample Output

Your program output should something similar to the sample output below.

Sample Output
0 1 2
-----------
0 | - | - | - |
-----------
1 | - | - | - |
-----------
2 | - | - | - |
-----------
Player X's turn.
Enter row (0-2): 1
Enter col (0-2): 1
0 1 2
-----------
0 | - | - | - |
-----------
1 | - | X | - |
-----------
2 | - | - | - |
-----------
Player O's turn.
Enter row (0-2): 0
Enter col (0-2): 0
... (game continues) ...
Player X wins!
divider

Reflection Questions

You may write your reflection answers as comments at the bottom of your code.

  1. Why is a 2D array a good choice to represent the Tic-Tac-Toe board?
  2. What two checks must be true for a move to be valid?
  3. Explain (in words) how your checkWin method works.
  4. Why is it helpful to move win checking and printing into separate methods?
  5. What was the hardest bug you ran into while building the full game loop?
divider

Submission

Submit your activity and reflection answers to the appropriate dropbox.

Activity Complete