Tic Tac Toe in Java

A step by step tutorial for implementing Tic Tac Toe via the console. The original article can still be found on Medium.
Author: Taylor McNeil
๐Ÿ“– 11 min read
๐Ÿ‘ 240 claps
๐Ÿ‘€ 75K+ views
๐Ÿ“… May 6, 2021

About This Tutorial

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.

What You'll Learn:

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.

๐Ÿ’ก Pro Tip
Always remember to test your code at the end of each section to make sure it works!

Tutorial Sections

Building the Board

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.

Game Board Visualization

_ | _ | _
_ | _ | _
  |   |  

The board uses a 3x5 character array with specific indices for game pieces

๐Ÿ“ Array Indexing

Valid game positions correspond to these array indices:

The vertical bars exist for visual formatting but aren't used in game logic.

TicTacToe.java - Building the Board
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();
        }
    }
}
๐Ÿ”„ Alternative Implementation

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.

Placing Pieces

Now that we have our board, let's establish the game rules and create a method to place pieces on the board.

Game Rules:

updateBoard() Method
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;
    }
}
๐Ÿงช Testing Your Code

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

Getting Player Input

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.

Player Input Implementation
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);
    }
}
๐Ÿ—๏ธ Static Scanner Explanation

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.

Validating Moves

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.

Valid Move Rules:

Enhanced Player Move with Validation
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;
    }
}

Simulating the Computer

To keep things simple, we'll simulate the computer using a random number generator. The computer will choose random valid moves.

Computer Player Implementation
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);
        }
    }
}
๐ŸŽฒ Random Number Generation

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.

Winning the Game

Now we need to determine when the game ends. Following traditional Tic Tac Toe rules, there are 8 winning conditions plus 1 tie condition.

All Possible Winning Combinations

Horizontal Wins
  • Top row: 1-2-3
  • Middle row: 4-5-6
  • Bottom row: 7-8-9
Vertical Wins
  • Left column: 1-4-7
  • Center column: 2-5-8
  • Right column: 3-6-9
Diagonal Wins
  • Top-left to bottom-right: 1-5-9
  • Top-right to bottom-left: 3-5-7
Game Over Detection
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;
}

Creating the Game Loop

Finally, we need to create the main game loop that allows players to play multiple games and handles the turn-based gameplay.

Complete Game Loop
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();
}