Tic Tac Toe is a classic programming problem that many developers encounter on their programming journey. Deceptively simple, Tic Tac Toe can teach developers essential programming concepts while building a complete, functional game.
This tutorial uses the most basic solution to make it accessible to all skill levels. We'll build everything in one class with several functions, focusing on clarity and learning rather than advanced design patterns.
The first step in building our game is to create the board. We'll use a 2D character array filled with dashes [-
], vertical bars [|
], and spaces [" "
] to create our visual board.
The board uses a 3x5 character array with specific indices for game pieces
Valid game positions correspond to these array indices:
The vertical bars exist for visual formatting but aren't used in game logic.
public class TicTacToe {
public static void main(String[] args){
/** Notes:
* _ | _ | _
* _ | _ | _
* | |
* Helpful indices
* [0][0] , [0][2] , [0][4]
* [1][0] , [1][2] , [1][4]
* [2][0] , [2][2] , [2][4]
**/
char[][] gameBoard = {
{'_','|','_','|','_'},
{'_','|','_','|','_'},
{' ','|',' ','|',' '}
};
printBoard(gameBoard);
}
public static void printBoard(char[][] gameBoard){
for(char[] row : gameBoard){
for(char c : row){
System.out.print(c);
}
System.out.println();
}
}
}
The printBoard()
function could also be implemented using traditional for loops:
for (int row = 0; row < gameBoard.length; row++) {
for (int c = 0; c < gameBoard[0].length; c++) {
System.out.print(gameBoard[row][c]);
}
System.out.println();
}
The enhanced for-each loop is more readable for this use case.
Now that we have our board, let's establish the game rules and create a method to place pieces on the board.
public static void updateBoard(int position, int player, char[][] gameBoard) {
char character;
if (player == 1) {
character = 'X';
} else {
character = 'O';
}
switch (position) {
case 1:
gameBoard[0][0] = character;
printBoard(gameBoard);
break;
case 2:
gameBoard[0][2] = character;
printBoard(gameBoard);
break;
case 3:
gameBoard[0][4] = character;
printBoard(gameBoard);
break;
case 4:
gameBoard[1][0] = character;
printBoard(gameBoard);
break;
case 5:
gameBoard[1][2] = character;
printBoard(gameBoard);
break;
case 6:
gameBoard[1][4] = character;
printBoard(gameBoard);
break;
case 7:
gameBoard[2][0] = character;
printBoard(gameBoard);
break;
case 8:
gameBoard[2][2] = character;
printBoard(gameBoard);
break;
case 9:
gameBoard[2][4] = character;
printBoard(gameBoard);
break;
default:
break;
}
}
Try these function calls in your main method to see pieces move around the board:
updateBoard(5, 1, gameBoard); // Player X in center
updateBoard(1, 2, gameBoard); // Computer O in top-left
updateBoard(7, 1, gameBoard); // Player X in bottom-left
Now we need to allow the player to tell us where they want to place their pieces. We'll create a playerMove()
function that uses the Scanner class to retrieve user input.
import java.util.Scanner;
public class TicTacToe {
// Static Scanner for reuse across methods
static Scanner input = new Scanner(System.in);
public static void main(String[] args) {
char[][] gameBoard = {
{'_','|','_','|','_'},
{'_','|','_','|','_'},
{' ','|',' ','|',' '}
};
playerMove(gameBoard);
}
public static void playerMove(char[][] gameBoard){
System.out.println("Please make a move. (1-9)");
int move = input.nextInt();
updateBoard(move, 1, gameBoard);
}
}
We create a static Scanner object because we'll use player input in multiple methods. Having one instance prevents memory issues and simplifies our code structure.
If you test the current code, you'll notice players can place pieces on top of existing pieces. We need to validate moves before placing them on the board.
_
or ' '
)public static void playerMove(char[][] gameBoard){
boolean validMove = false;
while(!validMove){
System.out.println("Please make a move. (1-9)");
int move = input.nextInt();
validMove = isValidMove(move, gameBoard);
if(validMove){
updateBoard(move, 1, gameBoard);
}
}
}
public static boolean isValidMove(int move, char[][] gameBoard){
switch(move){
case 1:
return gameBoard[0][0] == '_';
case 2:
return gameBoard[0][2] == '_';
case 3:
return gameBoard[0][4] == '_';
case 4:
return gameBoard[1][0] == '_';
case 5:
return gameBoard[1][2] == '_';
case 6:
return gameBoard[1][4] == '_';
case 7:
return gameBoard[2][0] == ' ';
case 8:
return gameBoard[2][2] == ' ';
case 9:
return gameBoard[2][4] == ' ';
default:
return false;
}
}
To keep things simple, we'll simulate the computer using a random number generator. The computer will choose random valid moves.
import java.util.Random;
public static void computerMove(char[][] gameBoard){
Random rand = new Random();
boolean validMove = false;
while(!validMove){
int move = rand.nextInt(9) + 1; // Random number 1-9
validMove = isValidMove(move, gameBoard);
if(validMove){
System.out.println("Computer chose position: " + move);
updateBoard(move, 2, gameBoard);
}
}
}
The expression rand.nextInt(9) + 1
generates numbers 1-9. The nextInt(9)
method returns 0-8, so we add 1 to get our desired range. Our validation handles invalid moves (like 0) by returning false.
Now we need to determine when the game ends. Following traditional Tic Tac Toe rules, there are 8 winning conditions plus 1 tie condition.
public static boolean isGameOver(char[][] gameBoard){
// Check horizontal wins
if(gameBoard[0][0] == gameBoard[0][2] &&
gameBoard[0][2] == gameBoard[0][4] &&
gameBoard[0][0] != '_'){
printWinner(gameBoard[0][0]);
return true;
}
if(gameBoard[1][0] == gameBoard[1][2] &&
gameBoard[1][2] == gameBoard[1][4] &&
gameBoard[1][0] != '_'){
printWinner(gameBoard[1][0]);
return true;
}
if(gameBoard[2][0] == gameBoard[2][2] &&
gameBoard[2][2] == gameBoard[2][4] &&
gameBoard[2][0] != ' '){
printWinner(gameBoard[2][0]);
return true;
}
// Check vertical wins
if(gameBoard[0][0] == gameBoard[1][0] &&
gameBoard[1][0] == gameBoard[2][0] &&
gameBoard[0][0] != '_' && gameBoard[0][0] != ' '){
printWinner(gameBoard[0][0]);
return true;
}
if(gameBoard[0][2] == gameBoard[1][2] &&
gameBoard[1][2] == gameBoard[2][2] &&
gameBoard[0][2] != '_' && gameBoard[0][2] != ' '){
printWinner(gameBoard[0][2]);
return true;
}
if(gameBoard[0][4] == gameBoard[1][4] &&
gameBoard[1][4] == gameBoard[2][4] &&
gameBoard[0][4] != '_' && gameBoard[0][4] != ' '){
printWinner(gameBoard[0][4]);
return true;
}
// Check diagonal wins
if(gameBoard[0][0] == gameBoard[1][2] &&
gameBoard[1][2] == gameBoard[2][4] &&
gameBoard[0][0] != '_' && gameBoard[0][0] != ' '){
printWinner(gameBoard[0][0]);
return true;
}
if(gameBoard[0][4] == gameBoard[1][2] &&
gameBoard[1][2] == gameBoard[2][0] &&
gameBoard[0][4] != '_' && gameBoard[0][4] != ' '){
printWinner(gameBoard[0][4]);
return true;
}
// Check for tie (board full)
if(isBoardFull(gameBoard)){
System.out.println("It's a tie!");
return true;
}
return false;
}
public static void printWinner(char winner){
if(winner == 'X'){
System.out.println("Player wins!");
} else {
System.out.println("Computer wins!");
}
}
public static boolean isBoardFull(char[][] gameBoard){
for(int i = 0; i < gameBoard.length; i++){
for(int j = 0; j < gameBoard[i].length; j++){
if(gameBoard[i][j] == '_' || gameBoard[i][j] == ' '){
return false;
}
}
}
return true;
}
Finally, we need to create the main game loop that allows players to play multiple games and handles the turn-based gameplay.
public static void main(String[] args) {
boolean playAgain = true;
while(playAgain){
char[][] gameBoard = {
{'_','|','_','|','_'},
{'_','|','_','|','_'},
{' ','|',' ','|',' '}
};
System.out.println("Welcome to Tic Tac Toe!");
printBoard(gameBoard);
boolean gameOver = false;
while(!gameOver){
// Player's turn
playerMove(gameBoard);
gameOver = isGameOver(gameBoard);
if(gameOver) break;
// Computer's turn
computerMove(gameBoard);
gameOver = isGameOver(gameBoard);
}
System.out.println("Would you like to play again? (y/n)");
char response = input.next().charAt(0);
playAgain = (response == 'y' || response == 'Y');
}
System.out.println("Thanks for playing!");
input.close();
}