Java Tutorials — Tic Tac Toe

Tic Tac Toe in Java

How to create a console Tic Tac Toe game in Java

Taylor McNeil
CodeX
Published in
11 min readMay 6, 2021

--

Tic Tac Toe Game (created by author)
Tic Tac Toe Game (created by author)

Tic Tac Toe is a classic programming problem that many developers encounter on their programming journey. Deceptively simple, Tic Tac Toe can teach developers about:

  • Arrays
  • Booleans
  • Conditionals
  • Functions
  • Looping
  • Variables
  • Data Structures

In this tutorial, we will create a console tic tac toe game that relies on a random number generator to control the computer. Where applicable, I will comment on how the code can be improved. This tutorial is going to use the most basic solution to make this accessible to all skill levels. Always remember to test your code at the end of each section to make sure it works! Let’s begin.

Note: You can enjoy this entire tutorial in a video format on YouTube:

Youtube link to the tutorial

If you get confused at any time, the full code can be viewed at any time here.

Building the Board

For simplicity, this entire tutorial will be built in one class and feature several functions.

The first step in building our game is to create the board. We are going to use a 2D character array full of dashes [ - ], vertical bars [ | ], and spaces [“ “] to create our board. Let’s create an array called gameBoard and fill it. We will also create a function called printBoard() to allow us to visualize the board.

A visualization of the gameboard (created by the author)
A visualization of the gameboard (created by the author)

A quick explanation of this code snippet. The vertical bars underscores, and spaces represent the grid for the tic tac toe board. You can think of the board as spaces 1–9. The first space is [0][0]. The second space is [0][2]. The third space is [0][4], etc.

All the indices are noted in the comments. The vertical bars exist; however, we are not counting them in our game logic.

printBoard() takes in a 2D array and prints the array contents to the console. It does this by using a nested for loop. The first loop tells the computer, for each character row within gameBoard, do the second loop.

The second loop is asking, for each character within the row, print the character out. At the end of each second loop, we create a new line to keep all the characters from being printed on the same line.

/**Note: Printing the board could be accomplished using the tradition for loop. I think the foreach loop is easier to read. **/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();
}

With this function complete, we have finished building the board.

Placing Pieces

Now that we have built our board, let’s dive into the game logic. We need a way of determining what character represents the player and the computer; and a way of placing these characters on the board. So let’s create some rules:

  • The player will be represented by the X character and the number 1
  • The computer will be represented by the O character and the number 2
  • The valid indices for placing pieces are [0][0] , [0][2] , [0][4], [1][0] , [1][2] , [1][4], [2][0] , [2][2] , [2][4]

Using all of these rules, we can create a method called updateBoard. It will allow us to update a position in the gameBoard, based on whose turn it is and what position they pick.

Note: This is the class from before; we have just added the updateBoard() function under the printBoard() function.

updateBoard() takes in the location in which the piece will be placed, whose turn it is, and the state of the gameBoard.

Using our rules from earlier, the player is equal to 1, and their character is X; the computer is equal to 2, and their character is O. An if-else statement was used to determine whose turn it is and what is the correct piece to place.

After we determined whose turn it is, we need to determine where to place the piece. A switch statement is used to separate the different cases. Case 1 represents if the player or computer chose the first position. (Which is the top left corner) Depending on who chose that position, the corresponding piece is placed in that location, and we break out of the switch statement.

/** Change the position and player to see the pieces move around the board. Insert this function call in the main method and play around with the numbers to see the pieces appear. **/updateBoard(5, 1, gameBoard);
updateBoard(1, 2, gameBoard);
updateBoard(7, 1, gameBoard);

At this point, you should be able to: add pieces to the board for both the computer, and the player. Be sure to test this feature out by switching from the player to the computer.

Getting Player Input

Now that we can see the board and place pieces on the board, we want the player to tell us where the pieces should go. To do this, we are going to create a new function called playerMove(). Inside of playerMove(), we are going to utilize the built-in Scanner class to retrieve player input and include some user validation to make sure the user inputs a valid response.

Note: This is the class from before; we have just added the playerMove() function under updateBoard(). We have also imported the Scanner library and created a new Scanner object on line #5. I have left the main method in for readability.

/** We have created a static Scanner here because we will utilize the player input in other areas. If we create only one instance to hold the player response, we do not have to worry about deleted or having multiple Scanner objects. **/

The playerMove() function itself is simple. We ask the player what position they would like to move to, save that response, and feed it into our updateBoard() function.

/** Line #15 has code for testing this function. Play around with feeding different values into the function. Do you notice that something is off? **/

If you played around with adding different values into the code, you might have noticed that something was off. That is, you can actually place characters on top of spaces that already contain characters. This is not allowed in Tic Tac Toe, and we need to fix this. To do this, we will create a function to validate the player’s move before we place the piece.

Validating Moves

To validate if the player made a valid move, we need to establish what a valid move is. In our world, we are building a game made of dashes [ — ], vertical bars [ | ], and spaces [“ “]. The dashes and spaces represent blank areas on the board.

/**         _ | _ | _     Helpful indices:[0][0] , [0][2] , [0][4]
_ | _ | _ [1][0] , [1][2] , [1][4]
| | [2][0] , [2][2] , [2][4]
**/

Thus, if a player chooses a space with anything other than a dash or space, that position is either invalid or occupied. For example, [0][1] is a vertical bar, and that is an invalid space for our game.

Note: If you look back in the notes or at the diagram, you can see what values coordinate to the valid spaces.

Note: We have updated the playerMove() function and added isValidMove() function underneath that.

The playerMove() function now checks whether the player is making a valid move before placing the piece and updating the board. We updated the playerMove() function to include a while loop and a boolean. The boolean is holding the result of the isValidMove() function. While the result of theisValidMove() is false, the program will keep looping endlessly until the player inputs a valid move.

Diving into the isValidMove() function, the function accepts the move input by the player and tests to see if the position that the player entered is empty. If the position is empty, then true is return, else return false because the position is occupied.

/** You could implement the validation differently. You could create a set for empty positions. You could check if the position was in the empty position set and return true or false based on that. You would need to fill the set with empty positions in the main method before running the game. **/import java.util.HashSet;static Set<Integer> emptyPositions = new HashSet<Integer>();public static void main(String [] args){  char [][] gameBoard = {{'_','|','_','|','_'},{'_', '|',   
'_','|','_'},{' ','|',' ','|',' '}};
for(int i =1; i<10; i++){
emptyPositions.add(i);
}

playerMove(gameBoard);
...omitting playerMove ...public static boolean isValidMove(int move, char[][] gameboard){if(!emptyPositions.contains(move)){
return false;
}else{
emptyPositions.remove(move);
return true;
}
/** You can test this out by calling playerMove() a few times in the main method. Check and see if your invalid moves from before are allowed now. **/

Simulating the Computer

To keep things simple, we are going to simulate the computer using a random number generator. The computer will choose a random number between 0–9 each turn. We will call the isValidMove() function inside the computerMove() function to ensure that our computer is also moving in valid spaces.

Note: This function is placed under the isValidMove() function.

The computerMove() function is very similar to the playerMove() function. The only difference is the introduction of the random Object. Don’t forget to import the Random library. The Random object will allow us to generate a number from 0 to 10.

/** You could stop the bound of the random number generator at 10.int move = rand.nextInt(10); 
int move = rand.nextInt(9)+1;
These two lines of code are equivalent. Something worth pointing out is that the random number generator begins with zero and ends with the bound number not being included. That zero could cause us problems; however, we already handled that in our switch case in isValidMove(). Our valid moves only include 1 - 9. Anything else outside that range is automatically considered an invalid move and returns a false value for isValidMove().
You can test this out by calling computerMove() a few times in the main method. Mix it up and call playerMove() too. You should start to see the beginnings of a game now. **/

Winning the Game

Our game is starting to take shape; only a few steps are left. We need to determine the winning condition or when the game is over. Following traditional tic tac toe rules, the winning conditions are as follows:

  • A diagonal on the left axis
  • A diagonal on the right axis
  • Three across the top horizontal
  • Three across the center horizontal
  • Three across the bottom horizontal
  • Three down the right side
  • Three down the center
  • Three down the left side
Visual of all possible winning combinations (created by author)
Visual of all possible winning combinations (created by author)

Those are the winning conditions; however, the game could also be in a tied state, in which all the spaces are filled, and none of the winning conditions have been met. Following that logic, we want to check if any of the winning conditions have been met each time the player or computer moves.

/** Technically, we do not have to check after every move. We can start checking after five moves, but we will check after every move to keep it simple. **/

The simplest way to check if a winning condition has been met is to check whether all the spaces in a combination are the same. For example, if [0][0], [1][0], and [2][0] are filled, then the player or computer has three down the left side vertically and has won. Let’s create a new function called isGameOver().

Note: This function is placed under the computerMove() function.

This function is a giant expansion of the logic used earlier. There are eight possible win conditions and one tie condition. This function checks to see if any of the win conditions have been met by either the computer or the player. If a win condition or tie has been met, then the game is over, and a true result will be returned, else a false result will be returned.

/** There are many ways to check to see if the player or computer has won. You could:- Convert all the rows and diagonals into sets, and check the length. A set can only contain unique values. Thus a winning set will only have a length of 1.-Make the current snippet smaller and check to see if the space is equal to the space next to it. For example is [0][0] == [0][2] == [0][4] and just determine what symbol is inside of the spaces.-Use a for loop for checking instead of using a giant if statement. You could use a loop for the horizontal conditions, a loop for the vertical, and a loop for the diagonals.These are just a few ways. There are more ways than these. **/

Play Again?

It might not be apparent where to place this function, and that is because we haven't created the game loop. A game loop will allow the player to play again once a winning condition or tie has been met. To accomplish this, we are going to wrap the main method in two while loops. The first will be checking to see if the player wants to play again, and the second will be checking to see if the game is over. We will create two booleans, gameOver, and playAgain.

Note: This is the main function of our game. All the other functions are written outside of this function.

playAgain is the main while loop. While playAgain is true, the entire game will continue. We start the game by welcoming the player and allowing them to make the first move. The game will continue by calling the playerMove() function and go through all the methods we created earlier. Then we will check the state of the board on line #12 using the isGameOver() function. If the game is over, we break out of the inner while loop and ask the player if they want to play again.

If the game is not over, it will become the computer's turn, and the sequence will repeat again and again until a win condition or tie has been met. Once isGameOver() returns false, we will print out the score and ask the player if they want to play again. We will reuse the scanner from before the save the player's input. Based on the input of the player, we will end the game or restart the game.

If you are looking closely, line # 34 contains a function that we have not written yet called resetBoard(). That is because it was not relevant until now. This function sets the board back to its beginning state. You could add a point system to keep the score if you wanted.

Fin

We are done. You now have a complete Tic Tac Toe console game written in Java. The world is your oyster. Continue to sharpen your coding skills by creating other games and never stop learning. Snake might be a fun game to take on. Once again here is the final code in all its glory, if you need a reference. You can find me on twitter thinking of new articles and talking about games.

--

--

Taylor McNeil
CodeX
Writer for

I write and make videos about programming and game development.