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:
- Create a runnable Java class called
TicTacToe
. - Declare all the required variables.
- Print a rudimentary tic-tac-toe board.
- Obtain user input from the two players.
- Check for a winning play.
- Switch players if the game is not won.
- End the game if all squares are taken.
- 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.
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';
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:
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 chars
in 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 } }