Core Java

Java Tic Tac Toe Program

This post is about creating a simple elegant Java Tic Tac Toe game, with the ability to play human vs human and computer vs human. We will build the game incrementally with each feature added to the design.

1. Java Tic Tac Toe: Introduction

This entire program requires only Java and apart from it does not require any library etc. Listed below are some assumptions/prerequisites involved in developing this application.

  • There will only be two players in the game – Human vs Human or Human vs Computer
  • This is a console application and no fancy GUI involved

1.1 Main

This is the entry point of the project. This involves the starting and ending of the game.

Main.java

public class Main {

    public static final int GRID = 3;

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        Game game;
        loop:
        while (true) {
            System.out.println("Enter number of human players");
            try {
                String input = scanner.next();
                switch (Integer.parseInt(input)) {
                    case 1:
                        game = new Game("Player1", GRID);
                        break loop;
                    case 2:
                        game = new Game("Player1", "Player2", GRID);
                        break loop;
                }
            } catch (Exception e) {
            }
        }
        game.start();
        Player winner = game.getWinner();
        System.out.println(winner != null ? "Winner is " + winner.getName() : "Tied");
    }
  • GRID variable is created to specify the board size. Typically, it is 3×3 but flexibility is provided for bigger boards.
  • First step is creating a scanner to receive the number of human players from the console
    • Either it could be one – pitting human against AI or 2 enabling 2 people to play against each other
  • Based on the input, Game Class is instantiated with number of players
  • Invalid Input is handled via exception and the loop terminates only in case of valid input
  • After initialization, game starts and control comes back to main only after the game has ended.
  • Main displays the winner based on the getWinner method of Game class.

1.2 Game

This is the heart of this project. We will look at the construction of game class by inspecting its public behavior methods

Game.java(constructor)

public Game(String player1, String player2, int grid) {
        this(player1, grid);
        players[1] = new Player(player2, "O");
    }

    public Game(String player, int grid) {
        SIZE = grid == 0 ? 3 : grid;
        board = new String[SIZE][SIZE];
        players[0] = new Player(player, "X");
        currPlayer = players[0];
        clearBoard();
    }

    private void clearBoard() {
        for (int i = 0; i < SIZE; i++) {
            for (int j = 0; j < SIZE; j++) {
                board[i][j] = FILLER;
            }
        }
    }
  • Initialize two players with symbols X and O respectively.
  • Intialize the board with specified Grid size or default to 3.
  • Fill the entire board with empty space instead of the default null.
  • Player class will be discussed in the next subsection

Game.java(start)

public void start() {
        printBoard();
        currPlayer.playMove(this);
    }

private void printBoard() {
        String header = "  ";
        for (int j = 0; j < SIZE; j++) {
            header += "|" + (j + 1);
        }
        System.out.println(header);
        for (int j = 0; j < SIZE * 3; j++) {
            System.out.print("_");
        }
        System.out.println();
        for (int i = 0; i < SIZE; i++) {
            String row = (i + 1) + " ";
            for (int j = 0; j < SIZE; j++) {
                row += "|" + board[i][j];
            }
            System.out.println(row);
            for (int j = 0; j < SIZE * 3; j++) {
                System.out.print("_");
            }
            System.out.println();
        }
        System.out.println(currPlayer.getName() + " Turn now");
    }
  • Once the game is started, first step is printing the board.
  • The board is printed with the positions outlined for user to enter the appropriate values in which the symbol has to be filled.
  • It also indicates the player name who has to play the next turn.
  • After which the player is allowed to play the move by calling the playMove method.

Board display(start)

  |1|2|3
_________
1 | | | 
_________
2 | | | 
_________
3 | | | 
_________
Player1 Turn now

Game(move)

public void move(int xPosition, int yPosition) {
        if (!isValidMove(xPosition, yPosition))
            currPlayer.playMove(this);
        board[xPosition][yPosition] = currPlayer.getSymbol();
        changePlayer();
        printBoard();
        if (!isGameOver()) {
            currPlayer.playMove(this);
        }
    }

    private boolean isValidMove(int xPosition, int yPosition) {
        if (xPosition >= SIZE || yPosition >= SIZE || xPosition < 0 || yPosition < 0)
            return false;
        if (!board[xPosition][yPosition].equals(FILLER))
            return false;
        return true;
    }

 private void changePlayer() {
        currPlayer = currPlayer.equals(players[1]) ? players[0] : players[1];
    }
private boolean isGameOver() {
        return getWinner() != null || isNoMovesLeft();
    }
  • move method is used to update the player position on the board.
  • It checks whether the move position is valid on the board and also if the position or square is empty.
  • If valid, it updates the board and passes the turn to other player.
  • If invalid, the same player is requested for another move.
  • As part of the move, if there emerges a winner or there are no positions left, then the game halts.

Game(getWinner)

   private String rowCrossed() {
        for (int i = 0; i < SIZE; i++) {
            String check = board[i][0];
            for (int j = 1; j < SIZE; j++) {
                if (!check.equals(board[i][j])) {
                    check = FILLER;
                    break;
                }
            }
            if (!check.equals(FILLER)) {
                return check;
            }
        }
        return FILLER;
    }

    private String columnCrossed() {
        for (int i = 0; i < SIZE; i++) {
            String check = board[0][i];
            for (int j = 1; j < SIZE; j++) {
                if (!check.equals(board[j][i])) {
                    check = FILLER;
                    break;
                }
            }
            if (!check.equals(FILLER)) {
                return check;
            }
        }
        return FILLER;
    }

    private String diagonalCrossed() {
        String check = board[0][0];
        for (int i = 1; i < SIZE; i++) {
            if (!check.equals(board[i][i])) {
                check = FILLER;
                break;
            }
        }
        if (!check.equals(FILLER)) {
            return check;
        }
        check = board[0][2];
        for (int i = 1; i < SIZE; i++) {
            if (!check.equals(board[i][SIZE - 1 - i])) {
                check = FILLER;
                break;
            }
        }
        if (!check.equals(FILLER)) {
            return check;
        }
        return FILLER;
    }

     public Player getWinner() {
        String rowSymbol = rowCrossed();
        String columnSymbol = columnCrossed();
        String diagonalSymbol = diagonalCrossed();
        for (Player player : players) {
            if (player.getSymbol().equals(rowSymbol)) return player;
            if (player.getSymbol().equals(columnSymbol)) return player;
            if (player.getSymbol().equals(diagonalSymbol)) return player;
        }
        return null;
    }
  • This is used to identify if either of the players is a winner
  • A winning position in Tic Tac Toe comprises one of the 3 directions
    • It could be one of the rows of the entire grid or table
    • It could be one of the columns along which each is occupied by the same player
    • The last combination will be along the two diagonals passing through the grid
  • All the above combinations are checked for presence of a player symbol and winner is reported based on that.

1.3 Player

This involves creating a Human Player who is capable of playing the game. Player class has a constructor which takes in the name of the player and also the symbol associated with the player. It has functions for identifying if two players are same.

Player.java

    public Player(String name, String symbol) {
        this.name = name;
        this.symbol = symbol;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Player player = (Player) o;
        return Objects.equals(name, player.name) &&
                Objects.equals(symbol, player.symbol);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, symbol);
    }

The main logic of Player is playing each and every move

Player(playMove)

public void playMove(Game game) {
        System.out.println("Enter your x,y positions -> For first row and first column enter 1,1");
        Scanner scanner = new Scanner(System.in);
        String input = scanner.next();
        try {
            String[] moves = input.split(",");
            game.move(Integer.parseInt(moves[0]) - 1, Integer.parseInt(moves[1]) - 1);
        } catch (Exception e) {
            playMove(game);
        }
    }
  • Since the player is human player, program takes the input from user via the Scanner class
  • It expects the input to be in the form of Row, Column position Eg: 1,1 for first row and first column
  • If input is not of the correct format, it recursively calls itself until the format is correct.
  • Once the input is received, it calls the game class with the move to be played.

We will see the typical out of a two player game with various moves. For illustration, some of the moves are invalid.

A Tied Game of Tic Tac Toe

Enter number of human players
2
  |1|2|3
_________
1 | | | 
_________
2 | | | 
_________
3 | | | 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
2,2
  |1|2|3
_________
1 | | | 
_________
2 | |X| 
_________
3 | | | 
_________
Player2 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
2,2
Enter your x,y positions -> For first row and first column enter 1,1
1,1
  |1|2|3
_________
1 |O| | 
_________
2 | |X| 
_________
3 | | | 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
3,1
  |1|2|3
_________
1 |O| | 
_________
2 | |X| 
_________
3 |X| | 
_________
Player2 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
1,3
  |1|2|3
_________
1 |O| |O
_________
2 | |X| 
_________
3 |X| | 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
1,2
  |1|2|3
_________
1 |O|X|O
_________
2 | |X| 
_________
3 |X| | 
_________
Player2 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
3,2
  |1|2|3
_________
1 |O|X|O
_________
2 | |X| 
_________
3 |X|O| 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
2,1
  |1|2|3
_________
1 |O|X|O
_________
2 |X|X| 
_________
3 |X|O| 
_________
Player2 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
2,3
  |1|2|3
_________
1 |O|X|O
_________
2 |X|X|O
_________
3 |X|O| 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
3,3
  |1|2|3
_________
1 |O|X|O
_________
2 |X|X|O
_________
3 |X|O|X
_________
Player2 Turn now
  |1|2|3
_________
1 |O|X|O
_________
2 |X|O|O
_________
3 |X|O|X
_________
Player1 Turn now
Tied
  • Program displays the player name for the current turn
  • Unless valid input is provided, the same player is prompted for input
  • Once all the squares are filled, the game result is announced. Alternatively, if there emerges a winner game result is announced

A Winning Game of Tic Tac Toe

Enter number of human players
2
  |1|2|3
_________
1 | | | 
_________
2 | | | 
_________
3 | | | 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
1.0
Enter your x,y positions -> For first row and first column enter 1,1
1,1
  |1|2|3
_________
1 |X| | 
_________
2 | | | 
_________
3 | | | 
_________
Player2 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
3,3
  |1|2|3
_________
1 |X| | 
_________
2 | | | 
_________
3 | | |O
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
2,2
  |1|2|3
_________
1 |X| | 
_________
2 | |X| 
_________
3 | | |O
_________
Player2 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
2,3
  |1|2|3
_________
1 |X| | 
_________
2 | |X|O
_________
3 | | |O
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
1,3
  |1|2|3
_________
1 |X| |X
_________
2 | |X|O
_________
3 | | |O
_________
Player2 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
1,2
  |1|2|3
_________
1 |X|O|X
_________
2 | |X|O
_________
3 | | |O
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
3,1
  |1|2|3
_________
1 |X|O|X
_________
2 | |X|O
_________
3 |X| |O
_________
Player2 Turn now
Winner is Player1

1.4 AI Player

In the previous section, We saw a game between two human players. We will extend our Player to be a Computer Player. A modified version of minimax algorithm from Game Theory is used to implement it. We will look at the pseudo code and the implementation.

AiPlayer(playMove)

public void playMove(Game game) {

        String[][] board = game.getBoard();
        Move bestMove = new Move(-1, -1, Integer.MIN_VALUE);
        int gridSize = board.length;
        for (int i = 0; i < gridSize; i++) {
            for (int j = 0; j  bestMove.getScore()) {
                        bestMove = move;
                    }
                    board[i][j] = Game.FILLER;
                }
            }
        }
        game.move(bestMove.getRow(), bestMove.getColumn());

    }
  • To identify the best move to play, current board positions are obtained.
  • It identifies all the moves it can make and calculates the score for each move.
  • Among the calculated scores, move with the best score is played by the computer.

Minimax Algorithm

function minimax(board, depth, isMaximizingPlayer):

    if current board state is a terminal state :
        return value of the board
    
    if isMaximizingPlayer :
        bestVal = -INFINITY 
        for each move in board :
            value = minimax(board, depth+1, false)
            bestVal = max( bestVal, value) 
        return bestVal

    else :
        bestVal = +INFINITY 
        for each move in board :
            value = minimax(board, depth+1, true)
            bestVal = min( bestVal, value) 
        return bestVal
  • The Algorithm above is one of the ways to identify the score for each move.
  • Maximizer is the computer which is trying to win while minimizer is the human player whose victory is a loss for the computer.
  • Once computer plays a move, the algorithm is computed for the move.
  • After a single move, all possible moves by computer and human player are simulated as though they had played the game.
  • The best value outcome of the move is maximum among the possible combination of moves for a maximizer whereas the minimum value if the game is ending in a loss.

The above calls for high load of simulation for all the moves and computation will be high even in case of a 4×4 board. Our variation takes a slightly greedy approach to this algorithm and uses heuristics to predict win or loss.

AiPlayer(calculateMoveCost)

private int calculateMoveCost(final String symbol, String[][] board) {
        int size = board.length;
        List scorerList = new ArrayList();
        for (int i = 0; i < size; i++) {
            Scorer rowScorer = new Scorer();
            Scorer colScorer = new Scorer();
            for (int j = 0; j < size; j++) {
                scoreBasedOnSymbol(symbol, board[i][j], rowScorer);
                scoreBasedOnSymbol(symbol, board[j][i], colScorer);
            }
            scorerList.add(rowScorer);
            scorerList.add(colScorer);
        }

        Scorer diagonal1Scorer = new Scorer();
        Scorer diagonal2Scorer = new Scorer();
        for (int i = 0; i < size; i++) {
            scoreBasedOnSymbol(symbol, board[i][i], diagonal1Scorer);
            scoreBasedOnSymbol(symbol, board[i][size - i - 1], diagonal2Scorer);
        }
        scorerList.add(diagonal1Scorer);
        scorerList.add(diagonal2Scorer);

        int score = 0;
        for (Scorer scorer : scorerList) {
            score += scorer.getScore(size);
        }

        return score;
    }


    private void scoreBasedOnSymbol(String symbol, String symbolToCompare, Scorer scorer) {
        if (symbol.equals(symbolToCompare))
            scorer.addWin();
        else if (Game.FILLER.equals(symbolToCompare))
            scorer.addTie();
        else
            scorer.addLoss();
    }
  • The winning condition is always a row match, column match or diagonal match.
  • For each of the combinations, a separate scorer is used.
  • For example in 3×3 board, there are 8 winning possibilities and 8 scorers one for each possibility.
  • It iterates through all the winning combinations and compares the current symbol in the board along with computer’s symbol.
  • If symbol matches, scorer increments a win or loss in case of opponent symbol while a tie if the position is empty.
  • Once the scores are initialized with the wins and losses, sum of all the scores returned by the scorers is used as the value of the move.
tic tac toe game in java

Scorer

public class Scorer {

    private int win;

    private int loss;

    private int moves;

    public int getScore(int grid) {
        if (win > 0 && loss == 0 && win + moves == grid) {
            return 10 * (grid - moves);
        }
        if (loss > 0 && win == 0 && loss + moves == grid) {
            return -10 * (grid - moves);
        }
        return 0;
    }

    public void addWin() {
        win += 1;
    }

    public void addLoss() {
        loss += 1;
    }

    public void addTie() {
        moves += 1;
    }
}
  • The scorer just increments the win or loss counter based on the state provided.
  • In case of tie, it increments the moves counter. This is because these slots are empty, and they can be either a win or loss depending on other positions.
  • getScore method is the heart of the logic. If there is not an exclusive possibility, neither win nor loss then score is zero.
  • In case of only win, the number of empty moves in the grid is multiplied by 10 whereas for loss is multiplied by -10.

This ensures that if there is a shorter winning move it would be prioritized over a longer winning move while a long loss is prioritized over short loss. The above can be seen with an example

A Human vs AI Game of Tic Tac Toe

Enter number of human players
1
  |1|2|3
_________
1 | | | 
_________
2 | | | 
_________
3 | | | 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
1,1
  |1|2|3
_________
1 |X| | 
_________
2 | | | 
_________
3 | | | 
_________
Computer2 Turn now
  |1|2|3
_________
1 |X| | 
_________
2 | |O| 
_________
3 | | | 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
3,1
  |1|2|3
_________
1 |X| | 
_________
2 | |O| 
_________
3 |X| | 
_________
Computer2 Turn now
  |1|2|3
_________
1 |X| | 
_________
2 |O|O| 
_________
3 |X| | 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
2,3
  |1|2|3
_________
1 |X| | 
_________
2 |O|O|X
_________
3 |X| | 
_________
Computer2 Turn now
  |1|2|3
_________
1 |X|O| 
_________
2 |O|O|X
_________
3 |X| | 
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
3,2
  |1|2|3
_________
1 |X|O| 
_________
2 |O|O|X
_________
3 |X|X| 
_________
Computer2 Turn now
  |1|2|3
_________
1 |X|O| 
_________
2 |O|O|X
_________
3 |X|X|O
_________
Player1 Turn now
Enter your x,y positions -> For first row and first column enter 1,1
1,3
  |1|2|3
_________
1 |X|O|X
_________
2 |O|O|X
_________
3 |X|X|O
_________
Computer2 Turn now
Tied
  • After the move 1,1 from human player, computer found the move 2,2 has the best score of 10 while all others either end in a loss or tie.
  • With the move 3,1 all moves end in a tie except for the move 2,1 which results in a win.
  • With the human player blocking the win, all moves possibly inch towards a tie and makes the first one among it.
  • When the human player makes the move 3,2 computer has possibility of 1,3 which can result in a loss or 3,3 which results in tie.

The tic tac toe game in java’s score above is sum of all the scorers. So a win and loss is compensated by the aggregation of scores. A move with loss reduces the score while a win increases the score.

In this article, We saw how to create a simple Java Tic Tac Toe game. We also discussed creating an automated intelligent player who optimizes for loss aversion.

2. Download the Source Code

Download
You can download the full source code of this example here: Java Tic Tac Toe Program

Rajagopal ParthaSarathi

Rajagopal works in software industry solving enterprise-scale problems for customers across geographies specializing in distributed platforms. He holds a masters in computer science with focus on cloud computing from Illinois Institute of Technology. His current interests include data science and distributed computing.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button