Code a tic-tac-toe game in Java

Learning how to code can be frustrating, but if you gamify the experience the frustration can become a little bit easier to bare.

One way to gamify learning is to actually code some games. Some of the most popular games to program when you’re learning include the following:

  • A number guesser.
  • Rock-paper-scissors.
  • Snake or centipede.
  • Space Invaders.
  • Checkers or chess.

Falling somewhere in the middle of the difficulty scale is the tic-tac-toe game. If you can code a number-guessing game or a rock-paper-scissors game, you should be able to code a Java tic-tac-toe game, although it’s much more challenging.

A Java tic-tac-toe game isn’t as difficult to code as checkers or chess, but many of the lessons learned about multidimensional arrays, exception handling and flow control will serve you well when you try to implement a more complex board game.

How to code a Java tic-tac-toe game

To code the Java tic-tac-toe program, we will follow these eight steps that enable us to incrementally move toward the goal of completing the game:

  1. Create a runnable Java class called TicTacToe.
  2. Declare all the required variables.
  3. Print a rudimentary tic-tac-toe board.
  4. Obtain user input from the two players.
  5. Check for a winning play.
  6. Switch players if the game is not won.
  7. End the game if all squares are taken.
  8. Run the Java tic-tac-toe app.

Step 1: Create the TicTacToe Java class

To start, create a class named TicTacToe that has a runnable main method, as shown below.

All of the code in this example goes inside the TicTacToe class’ main method, or inside methods declared as part of this class. Optionally, you can run this example in JShell, which I always recommend for learning Java and quick prototyping.

 

/* A Tic Tac Toe Game coded in Java */
public class TicTacToe {

  public static void main(String[] args) 
  {

  }

}

Step 2: Declare the required variables

A tic-tac-toe board has nine squares. In our game, we display the number of each unselected square and ask the player which square to mark.

java tic tac toe multi-dimensional array

Each element of the board[] array represents a spot on the game board.

char[] board = { '1','2','3',
                 '4','5','6',
                 '7','8','9'  };

We also need to keep track of whose turn it is, and how many squares have been played. The game ends in a tie if all nine squares have been played with no winner.

var numberOfSquaresPlayed = 0;
var whoseTurnItIs = 'x';
To do this, we start with a char array that holds the numbers 1-9, as so:

Step 3: Print out the Tic-Tac-Toe board

Every time a square is selected, we redraw the board.

Since this is a simple console game, the display of the board isn’t sophisticated. It simply looks like the image below:

java tic tac toe app board

During gameplay, the player picks the number of an empty spot.

We input the logic to generate and print out the Java tic-tac-toe gameboard in a method named printTheBoard() :

private static void printTheBoard(char[] board) 
{
  System.out.println( board[0] + " | " +  board[1] + " | " + board[2]);
  System.out.println( " - + - + - " );
  System.out.println( board[3] + " | " +  board[4] + " | " + board[5]);
  System.out.println( " - + - + - " );
  System.out.println( board[6] + " | " +  board[7] + " | " + board[8]);
}

When the board is first drawn, it displays only numbers:

1 | 2 | 3
- + - + -
4 | 5 | 6
- + - + -
7 | 8 | 9

As the game proceeds, the board updates with x’s and o’s. A completed tic-tac-toe Java battle looks like this:

X | O | X
- + - + -
X | 5 | O
- + - + -
X | O | 9

Step 4: Obtain user input

So long as the numberOfSquaresPlayed is less than 9, we show the player the current state of the board and prompt them to choose a square, leveraging Java’s Scanner class for user input:

while (numberOfSquaresPlayed < 9) 
{
  printTheBoard(board);
  System.out.printf("Choose a square player %s:", whoseTurnItIs);

  var scanner = new java.util.Scanner(System.in);
  var input = scanner.nextInt();

  board[input-1] = whoseTurnItIs;
}

Note how the char array updates with either an X or an O depending on whose turn it is.

board[input-1] = whoseTurnItIs;

Since arrays are zero-based, we subtract one from the number provided by the user. If the user chooses the square for the number 5, that corresponds to index 4 in our array.

Step 5: Check for a winner

Each time a user selects a square, we check if they completed a line and won the game.

There are eight ways to win Tic Tac Toe:

  • Three horizontal lines.
  • Three vertical lines.
  • Two lines that go across the square.

The conditional logic can get ugly in this situation, as each square in a potentially winning row must be checked against the current player’s letter.

A check on the first horizontal line might look like this:

if ((board[0] == whoseTurnItIs) && 
    (board[1] == whoseTurnItIs) && 
    (board[2] == whoseTurnItIs)) 
{
   System.out.println("You won!!! Congratulations!");
   break;
}  

Eight of these checks are needed to check if the game has been won, which could get ugly.

Another option, which is slightly less verbose, involves using a little-known feature of the char type.

The char is an integer type

Few people know this, but a Java char is actually an integer type. You can perform math operations on it just like you can with an int, a double or a float.

That means you can add the three charsin a given row and see if the result equals the ‘x’ char multiplied by itself three times. If it does, you know you have three ‘x’ chars in a row. Do the same for the ‘o’ char if it’s the second player’s turn.

The following logic uses the integer-based nature of the char to see if a given row, column or cross has been completed:

if (  (board[0] + board[1] + board[2] == (whoseTurnItIs * 3)) // first row 
   || (board[3] + board[4] + board[5] == (whoseTurnItIs * 3)) // second row
   || (board[6] + board[7] + board[8] == (whoseTurnItIs * 3)) // third row
   || (board[0] + board[3] + board[6] == (whoseTurnItIs * 3)) // first column
   || (board[1] + board[4] + board[7] == (whoseTurnItIs * 3)) // second column
   || (board[2] + board[5] + board[8] == (whoseTurnItIs * 3)) // third column
   || (board[0] + board[4] + board[8] == (whoseTurnItIs * 3)) // first diagonal
   || (board[2] + board[4] + board[6] == (whoseTurnItIs * 3)) // second diagonal
) 
{
  printTheBoard(board);
  System.out.println("You won!!! Congratulations!");
  break;
} else {
  numberOfSquaresPlayed++;
  whoseTurnItIs = (whoseTurnItIs == 'x') ? 'o' : 'x';
}

Bask in the glow of successfully performing math operations on a char. You might go the rest of your entire career and never see a Java char used like this again.

Step 6: Switch turns

If our conditional checks show a line has been completed, we congratulate the player and break out of the loop, as we see from the code above:

printTheBoard(board);
System.out.println("You won!!! Congratulations!");
break;

If a row isn’t completed, we increase the number of squares played and change turns. We do this with the ternary operator.

numberOfSquaresPlayed++;
whoseTurnItIs = (whoseTurnItIs == 'x') ? 'o' : 'x';

Java’s conditional operator

The ternary operator is a shortcut that simplifies an assignment based on the outcome of a conditional statement.

We could have simply said:

if (whoseTurnItIs == 'x') 
{
  whoseTurnItIs = 'o';
} else {
  whoseTurnItIs = 'x';
}

However, we instead use the ternary operator to make the syntax more precise.

whoseTurnItIs = (whoseTurnItIs == 'x') ? 'o' : 'x';

As a new developer, I always disliked the cryptic syntax of the ternary operator. As an experienced Java developer, I still dislike it, but I appreciate it more.

Step 7: Handle the tie

If the game is played nine times and there is no winner, the loop exits.

At this point in time, we declare the game a tie and congratulate both sides:

if (numberOfSquaresPlayed == 9) 
{
  printTheBoard(board);
  System.out.println("A tough battle fought to a draw!");
}

Step 8: Run the tic-tac-toe Java game

Pull all of this code together and save your changes, and run the Java tic-tac-toe game.

As long as you provide valid input and follow the happy path, the game will execute flawlessly.

Here is the full, runnable TicTacToe Java class coded up until this point:

package com.mcnz.vector;

public class TicTacTest {

  public static void main(String[] args) 
  {
    char[] board = { '1', '2', '3', 
                     '4', '5', '6', 
                     '7', '8', '9' };

    var numberOfSquaresPlayed = 0;
    var whoseTurnItIs = 'x';

    while (numberOfSquaresPlayed < 9) 
    {
      printTheBoard(board);
      System.out.printf("Choose a square player %s:", whoseTurnItIs);
      var scanner = new java.util.Scanner(System.in);
      var input = scanner.nextInt();
      board[input - 1] = whoseTurnItIs;

      if (  (board[0] + board[1] + board[2] == (whoseTurnItIs * 3)) // first row 
         || (board[3] + board[4] + board[5] == (whoseTurnItIs * 3)) // second row
         || (board[6] + board[7] + board[8] == (whoseTurnItIs * 3)) // third row
         || (board[0] + board[3] + board[6] == (whoseTurnItIs * 3)) // first column
         || (board[1] + board[4] + board[7] == (whoseTurnItIs * 3)) // second column
         || (board[2] + board[5] + board[8] == (whoseTurnItIs * 3)) // third column
         || (board[0] + board[4] + board[8] == (whoseTurnItIs * 3)) // first diagonal
         || (board[2] + board[4] + board[6] == (whoseTurnItIs * 3)) // second diagonal
      ) 
      {
         printTheBoard(board);
         System.out.println("You won!!! Congratulations!");
         break;
      } else {
         numberOfSquaresPlayed++;
         whoseTurnItIs = (whoseTurnItIs == 'x') ? 'o' : 'x';
      }
    }
  }

  private static void printTheBoard(char[] board) 
  {
    System.out.printf("%n %s | %s | %s %n", board[0], board[1], board[2]);
    System.out.println(" - + - + - ");
    System.out.printf(" %s | %s | %s %n", board[3], board[4], board[5]);
    System.out.println(" - + - + - "); 
    System.out.printf(" %s | %s | %s %n%n", board[6], board[7], board[8]);
  }
} /* End of the Java Tic Tac Toe Game */

Enhance the Java Tic-Tac-Toe app

With the core logic implemented, you can push your programming knowledge forward by enhancing the Java tic-tac-toe game.

Possible enhancements include the following:

  • Validate that user input is within the required range.
  • Perform exception handling to deal with runtime errors.
  • Refactor the core logic into smaller methods.
  • Allow the game to keep playing until the players decide to quit.
  • Add GUI components from the Swing package.
  • Use the autoclose feature of the Scanner class.
  • Use an enum to define whose turn it is.
  • Use Java string formatting functions for display functions.
  • Automate the server so the game supports a single-player mode.

Source code for Java tic-tac-toe game

The complete code for a slightly enhanced Java Tic-Tac-Toe game is as follows. Add your own enhancements and push your understanding of Java even further.

/* Java tic-tac-toe Game source code */
package com.mcnz.tictactoe;

public class TicTacToe {

  public static void main(String[] args) {

    char[] board = { '1', '2', '3',
                     '4', '5', '6',
                     '7', '8', '9' };

    var numberOfSquaresPlayed = 0;
    var whoseTurnItIs = 'x';
    var gameEndingMessage = "";

    while (numberOfSquaresPlayed < 9) {
      printTheBoard(board);
      getTheUserToSelectTheirSquare(board, whoseTurnItIs);
  
      if (theyWonTheGame(board, whoseTurnItIs)) {
        gameEndingMessage = "You won!!! Congratulations!";
        break;
      } else if (numberOfSquaresPlayed == 9) {
        gameEndingMessage = "A tough battle fought to a draw!";
      } else {
        numberOfSquaresPlayed++;
        whoseTurnItIs = (whoseTurnItIs == 'x') ? 'o' : 'x';
        continue;
      }
    }
    printTheBoard(board);
    System.out.println(gameEndingMessage);
  }

  private static void printTheBoard(char[] board) {
    System.out.printf("%n %s | %s | %s %n", board[0], board[1], board[2]);
    System.out.println(" - + - + - ");
    System.out.printf(" %s | %s | %s %n",   board[3], board[4], board[5]);
    System.out.println(" - + - + - ");
    System.out.printf(" %s | %s | %s %n%n", board[6], board[7], board[8]);
  }

  private static void getTheUserToSelectTheirSquare(char[] board, char whoseTurnItIs) {

    do {
      try {
        System.out.printf("Would player %s please choose an unchosen square:", whoseTurnItIs);
        var scanner = new java.util.Scanner(System.in);
        var input = scanner.nextInt();

        if (Character.isDigit(board[input - 1])) {
          board[input - 1] = whoseTurnItIs;
          break;
        } else {
          System.out.println("Sorry, that's taken.");
        }
      } catch (Exception e) {
        System.out.println("Something went wrong. Let's try that again.");
      }
      printTheBoard(board);
    } while (true);
  }

  private static boolean theyWonTheGame(char[] board, char whoseTurnItIs) {
    return (board[0] + board[1] + board[2] == (whoseTurnItIs * 3)) // first row  
    || (board[3] + board[4] + board[5] == (whoseTurnItIs * 3))     // second row
    || (board[6] + board[7] + board[8] == (whoseTurnItIs * 3))     // third row
    || (board[0] + board[3] + board[6] == (whoseTurnItIs * 3))     // first column
    || (board[1] + board[4] + board[7] == (whoseTurnItIs * 3))     // second column
    || (board[2] + board[5] + board[8] == (whoseTurnItIs * 3))     // third column
    || (board[0] + board[4] + board[8] == (whoseTurnItIs * 3))     // first cross
    || (board[2] + board[4] + board[6] == (whoseTurnItIs * 3));    // second cross
  }