GUI Tic-Tac-Toe in Java Revisited

In my post found here I discussed a tic-tac-toe game coded in Java which was a version using a Graphical User Interface. The code listing for the classes and interfaces can be found in the same post. In this post I will discuss a Graphical User Interface (GUI) version of the same application with some additional features. The GUI tic-tac-toe app is based on a console application version found here. I shall only provide the changes and additions to the code listings here. Note that I will use the NetBeans IDE.

We will primarily make changes to the AWTBoard class. After making these changes, the user will be asked whether he really wants to quit when a game is in progress. If he clicks Cancel, the game will continue. If he clicks OK, the game will be terminated. If the game is over or not yet begun, the user will be able to stop the game as usual. See the video below the code listing to understand how it works.

package com.tictactoe.components;

import java.awt.*;
import java.awt.event.*;
import javax.swing.JOptionPane;

/**
 *
 * @author Dell
 */
public class AWTBoard extends Board implements ActionListener{
    
    private Button[][] buttons = new Button[3][3];
    private Frame f;
    private Label l;
    
    private Move currMove;
    private Game myGame;
    
    public AWTBoard()
    {
        super();
        f = new Frame("Tic-Tac-Toe");
        f.setSize(300, 300);
        
        
        Panel topPanel = new Panel();
        
        topPanel.setLayout(new BorderLayout());
        
        Panel p = new Panel();
        
        GridLayout grd = new GridLayout(3,3);
        
        p.setLayout(grd);
        
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < 3; j++)
            {
                buttons[i][j] = new Button();
                
                buttons[i][j].addActionListener(this);
                
                p.add(buttons[i][j], i, j); //problem
                
            }
        }
        
        topPanel.add(p, BorderLayout.CENTER);
        
        l = new Label();
        
        topPanel.add(l, BorderLayout.SOUTH);
        
        f.add(topPanel);

        /****Begin changes****/
        f.addWindowListener(new WindowAdapter(){ 
            
            public void windowClosing(WindowEvent e){ 
                
                if(myGame.isGameOver() || Game.getMovesTillNow() == 0)
                {
                    f.dispose(); 
                    System.exit(0);
                }else{
                    int result = JOptionPane.showConfirmDialog((Component) null, "Game in progress. Are you sure you want to quit?",
        "Message", JOptionPane.OK_CANCEL_OPTION);
            
                    if(result == 0)
                    {
                        f.dispose(); 
                        System.exit(0);
                    }
            
                }
            } 
            
        });
        /****End changes****/        

        //f.pack();
        f.setBounds(200, 200, f.getWidth(), f.getHeight());
        
        f.setVisible(true);
    }
    
    public void setGame(Game game)
    {
        myGame = game;
    }
    
    public void display()
    {
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < 3; j++) 
            {  

                buttons[i][j].setLabel( ""+cells[i][j] );
               
            } 
             
        }  
    }
    
    public void markCell(Move move, char mark)
    {
        if(move.isIsValid())
        {
            int i = move.getRow(); 
            int j = move.getColumn(); 
            cells[i - 1][j - 1] = mark;
            
            buttons[i - 1][j - 1].setLabel(""+mark);
        }
        else
        {
            //System.out.println("INVALID MOVE!\n\n");
            l.setText("INVALID MOVE!");
        }
    }
    
    public Label getStatusLabel()
    {
        return l;
    }
    
    public void actionPerformed(ActionEvent e)
    {
        Button b = (Button)e.getSource();
        
        for(int i = 0 ; i < 3; i++ )
        {
            for(int j = 0; j < 3; j++)
            {
                if(b == buttons[i][j])
                {
                    currMove = new Move();
                    currMove.setRow(i + 1);
                    currMove.setColumn(j + 1);
                    currMove.setIsValid(true);
                }
            }
        }
        
        
        synchronized(this)
        {
            this.notify();
        }
        
    }
    
    public Move getCurrMove()
    {
        return currMove;
    }
}

 

Deploying Tic-Tac-Toe Java GUI app

In this post, we deploy our tic-tac-toe Java GUI app so that it can be invoked either by double-clicking it or through the command line. Note: These steps below were performed on a Windows system.

  1. First, assemble all the Java source files in a folder of your choice.
  2. Open your Command Prompt.
  3. Navigate to your chosen folder in the Command Prompt using the cd command. e.g. cd Java\progs\TicTacToeGUIDemo
  4. Compile all the Java source files with javac compiler. e.g. javac -d . *.java.
    * means all Java files in the current folder.
  5. After successful compilation, the root package of your app will appear as a folder. In our case it’s ‘com’.
  6. Now, we need to create a manifest file to help us create a java archive file or JAR file.
    This file can be used to launch the app just by double-clicking on it.
    It can also be invoked using the java command from Command Prompt. e.g. java -jar TicTacToe.jar
  7. Open Notepad and create a blank new file called manifest.mf
  8. In this file, type:Main-Class: com.tictactoe.gameplay.AWTTicTacToe. This is the main class name, qualified by the full package name.
  9. After step 8, be sure to add a blank new line in the text file manifest.mf
  10. Save the manifest file and close it.
  11. Go back to your Command Prompt.
    Type the following: jar cvmf manifest.mf  –jar file name–  –root package folder name–
    e.g. jar cvmf manifest.mf TicTacToe.jar com
  12. You will get some verbose output from the jar command. To open your jar file, you can follow either of the steps in step 6 above.

View the video below to see it all in action.

Coding Tic-Tac-Toe with a Graphical User Interface

In my post found here I discussed a tic-tac-toe game coded in Java which was a console application.
The code listing for the classes and interfaces can be found in the same post. In this post I will discuss a Graphical User Interface (GUI) version of the same application. I shall only provide the changes and additions to the code listings here. Note that I will use the NetBeans IDE.

First I shall discuss the changes to the Board class. Since we shall use inheritance, we shall change the accessibility of the cells from private to protected. This is the only change required. See the listing below.

package com.tictactoe.components;  
/**
 *
 * @author pratyush
 */
public class Board { 
 
    protected static char[][] cells; //change to Board class for inheritance
    
    public Board()
    {
        cells = new char[3][3]; 
        cells[0] = new char[]{' ', ' ', ' '}; 
        cells[1] = new char[]{' ', ' ', ' '}; 
        cells[2] = new char[]{' ', ' ', ' '};
    }
    
    public void display()
    {
        for(int i = 0; i < 3; i++)
        {
            for(int j = 0; j < 3; j++){ 
        if(cells[i][j] == ' ') 
        { 
          System.out.print((i+1)+", "+(j+1)+"\t"); 
        } 
        else{
          System.out.print(cells[i][j]+"\t"); 
        } 
      } 
      
      System.out.print("\n\n"); 
    } 
  } 
  
  public Move checkAvailable(Move move){ 
  
    int i = move.getRow(); 
    int j = move.getColumn(); 
    boolean boundValid = ((i >= 1 && i<= 3) && ( j >= 1 && j <= 3));
    
        boolean charValid = false; 
        if(boundValid)
        {
            charValid = (cells[i - 1][j - 1] == ' ');
        } 
        boolean netValid = (boundValid && charValid); 
        move.setIsValid(netValid); 
        return move;
    }
    
    public void markCell(Move move, char mark)
    {
        if(move.isIsValid())
        {
            int i = move.getRow(); 
            int j = move.getColumn(); 
            cells[i - 1][j - 1] = mark;
        }
        else
        {
            System.out.println("INVALID MOVE!\n\n");
        }
    } 
}

 

AWTBoard

Among the additions, we shall make a new Board class called AWTBoard which shall inherit all the properties of the Board class. However it shall add the functionalities of GUI from the Java Abstract Windowing Toolkit or AWT packages. The listing of this new class is given below. The AWT version of the Board will be a frame with a 3 X 3 grid of buttons with a status label at the bottom to display all the messages which were being outputted to the console in the console version of the application. The players will play their turns by clicking buttons in the grid.

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.tictactoe.components;

import java.awt.*;
import java.awt.event.*;

/**
 *
 * @author Dell
 */
public class AWTBoard extends Board implements ActionListener{
    
    private Button[][] buttons = new Button[3][3];
    private Frame f;
    private Label l;
    
    private Move currMove;
    
    public AWTBoard()
    {
        super();
        f = new Frame("Tic-Tac-Toe");
        f.setSize(300, 300);
        
        
        Panel topPanel = new Panel();
        
        topPanel.setLayout(new BorderLayout());
        
        Panel p = new Panel();
        
        GridLayout grd = new GridLayout(3,3);
        
        p.setLayout(grd);
        
        for(int i = 0; i &lt; 3; i++)
        {
            for(int j = 0; j &lt; 3; j++)
            {
                buttons[i][j] = new Button();
                
                buttons[i][j].addActionListener(this);
                
                p.add(buttons[i][j], i, j);
                
            }
        }
        
        topPanel.add(p, BorderLayout.CENTER);
        
        l = new Label();
        
        topPanel.add(l, BorderLayout.SOUTH);
        
        f.add(topPanel);
        
        f.addWindowListener(new WindowAdapter(){ public void windowClosing(WindowEvent e){ f.dispose(); System.exit(0);} });
        
        
        f.setBounds(200, 200, f.getWidth(), f.getHeight());
        
        f.setVisible(true);
    }
    
    public void display()
    {
        for(int i = 0; i &lt; 3; i++)
        {
            for(int j = 0; j &lt; 3; j++) 
            {  

                buttons[i][j].setLabel( ""+cells[i][j] );
               
            } 
             
        }  
    }
    
    public void markCell(Move move, char mark)
    {
        if(move.isIsValid())
        {
            int i = move.getRow(); 
            int j = move.getColumn(); 
            cells[i - 1][j - 1] = mark;
            
            buttons[i - 1][j - 1].setLabel(""+mark);
        }
        else
        {
            
            l.setText("INVALID MOVE!");
        }
    }
    
    public Label getStatusLabel()
    {
        return l;
    }
    
    public void actionPerformed(ActionEvent e)
    {
        Button b = (Button)e.getSource();
        
        for(int i = 0 ; i &lt; 3; i++ )
        {
            for(int j = 0; j &lt; 3; j++)
            {
                if(b == buttons[i][j])
                {
                    currMove = new Move();
                    currMove.setRow(i + 1);
                    currMove.setColumn(j + 1);
                    currMove.setIsValid(true);
                }
            }
        }
        
        
        synchronized(this)
        {
            this.notify();
        }
        
    }
    
    public Move getCurrMove()
    {
        return currMove;
    }
}

 

First, let us see the constructor of the new Board class AWTBoard. We create a Frame which is basically a window. We then create a Panel and add it to the content pane of the window created previously. We create a Border Layout for the previously added Panel with a top part and a bottom part. To the bottom part, we add a label to display any status messages needed to be displayed. To the top part of the Panel, we create a Panel with a Grid Layout to add a 3 X 3 Grid of Buttons and then add this panel to the main Panel. The Players play the game by taking turns to click the buttons in this grid.

We override the display method of the Board class to mark the buttons of the grid instead of outputting them to the console. Likewise, we override all those other methods which output to the console. The new method added is the actionPerformed method which is basically an event handler that tells Java what to do when the buttons in the 3 x 3 grid are clicked. getStatusLabel and getCurrMove are helper methods to enable passing data in and out of GUI Board.

AWTTicTacToe

Apart from the GUI Board, we need to introduce another class called AWTTicTacToe which will contain changes to invoke the GUI Board instead of the console.

package com.tictactoe.gameplay;

import com.tictactoe.components.*;

/**
 *
 * @author pratyush
 */
public class AWTTicTacToe {  /**
    * @param args the command line arguments
    */
    public static void main(String[] args) {
        // TODO code application logic here 
        Game game = new Game(); 
        Player player1 = new Player(); 
        player1.setPlayerNum(1); 
        player1.setPlayerMark('X'); 
        player1.setGame(game); 
        Player player2 = new Player(); 
        player2.setPlayerNum(2); 
        player2.setPlayerMark('0'); 
        player2.setGame(game);
        
        //
        AWTBoard board = new AWTBoard();
        board.display();
        
        /*******/ 
        game.setWhoseTurn(player1); 
        game.setGameOver(false); 
        boolean status = game.isGameOver(); 
        // 
        do
        {
            int movesNow = Game.getMovesTillNow(); 
            if(movesNow == 9)
            {
                if(game.getWinner() == null)
                {
                    //
                    board.getStatusLabel().setText("TIE!\n");
                    break;
                }
            } 
            int turn = game.getWhoseTurn(); 
            //
            
            board.getStatusLabel().setText("PLAYER "+turn+" click a button\n"); 
            
            //
            try{
                synchronized(board)
                {
                    board.wait();
                }
            }
            catch(InterruptedException ie)
            {
                ie.printStackTrace();
            }
            
            Move move = board.getCurrMove();
            
            switch(turn)
            {
                case 1:
                    player1.makeMove(board, move); 
                    if(game.checkWinningMoves(player1))
                    {
                        game.setWinner(player1); 
                        //
                        board.getStatusLabel().setText("PLAYER 1 WINS!");
                        game.setGameOver(true);
                    }
                    else
                    {
                        if(move.isIsValid())
                        {
                            movesNow = Game.getMovesTillNow(); 
                            //
                            
                            board.getStatusLabel().setText("MOVE NUMBER "+movesNow+"\n");
                            game.setWhoseTurn(player2);
                        }
                    }
                break;
                case 2:
                    player2.makeMove(board, move); 
                    if(game.checkWinningMoves(player2))
                    {
                        game.setWinner(player2); 
                        //
                        
                        board.getStatusLabel().setText("PLAYER 2 WINS!");
                        game.setGameOver(true);
                    }
                    else
                    {
                        if(move.isIsValid())
                        {
                            movesNow = Game.getMovesTillNow(); 
                            // 
                            board.getStatusLabel().setText("MOVE NUMBER "+movesNow+"\n");
                            game.setWhoseTurn(player1);
                        }
                    }
                break;
                default:
                    //
                    board.getStatusLabel().setText("ERROR!\n");
                break;
            } 
            status = game.isGameOver();
        }
        while(!status); 
    } 
}

 

AWTTicTacToe is the main class containing the main method. It invokes the AWTBoard class instead of the Board class. It will not contain any statements to output to the console, although it may do so for debugging. To see the results of the invocation of this main class see the video below.

 

This GUI implementation of tic-tac-toe in Java is not perfect by any means. There is scope for improvement and new features can be added. Meanwhile, you can run this code and enjoy!