Java GUI Hangman Application

I hope you follow through the tutorial thoroughly and learn something new today. Currently, Eclipse is my preferred Java IDE. I am assuming that all of you are familiar with Hangman.

The final result

Java GUI Hangman Application

Download the files

Let's get started

Create a new project in Eclipse, and start by creating a new class file called MainWindow.java. This class will represent the view of our application.

public class MainWindow extends JFrame {
    
}

This class extends JFrame. This means that it will be inheriting all the methods and fields of JFrame, which essentially is a window with close, minimize and maximize buttons.

Now we create a few variables.

public class MainWindow extends JFrame {
    
    private int remainingGuesses;
    private String wrongGuesses;
    private String word;
    private String visible;

}

The variables are self-explanatory. Nevertheless, here are the details:

Now we create a constructor and initialise a few variables.

public class MainWindow extends JFrame {
    
    private int remainingGuesses;
    private String wrongGuesses;
    private String word;
    private String visible;

    public MainWindow(String toGuess) {
        remainingGuesses = 10;
        wrongGuesses = "";
        word = toGuess;
    }
}

Now that we know the word that the user will be guessing, we need to assign a value to the visible variable. So remember, if the word is cat, the value of visible will be _ _ _.

public class MainWindow extends JFrame {
    
    private int remainingGuesses;
    private String wrongGuesses;
    private String word;
    private String visible;

    public MainWindow(String toGuess) {
        remainingGuesses = 10;
        wrongGuesses = "";
        word = toGuess;

        visible = "";

        for(int i = 0; i < word.length(); ++i) {
            visible += "_ ";
        }
    }
}

Firstly, we have to initialise visible, which we did assigning it an empty String (visible = ""). Then we need to add underscores and spaces ("_ ") to it for each character in the word. We can accomplish this using a for loop. So for example if the word is cat, the loop will be repeated 3 times. So the value of visible will "_ _ _ ".

Now that we have initialised all the important variables, we can move onto the GUI part. We are going to be using BorderLayout a lot in this application. So it's very important you understand it.

JFrame and JPanel can have layouts. BorderLayout essentially separates the panel or frame into 5 parts. North, South, East, West, and Centre. Note: these parts will have 0 width when there aren't any components added to them. JPanel, JLabel, JTextField, JButton, and many more are examples of a few components.

So first, we make a centralised JPanel which holds all the components and add it to the JFrame. Remember: JFrame by default has its layout set to BorderLayout.

import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MainWindow extends JFrame {
    
    private int remainingGuesses;
    private String wrongGuesses;
    private String word;
    private String visible;

    public MainWindow(String toGuess) {
        remainingGuesses = 10;
        wrongGuesses = "";
        word = toGuess;

        visible = "";

        for(int i = 0; i < word.length(); ++i) {
            visible += "_ ";
        }

        JPanel corePanel = new JPanel();
        corePanel.setLayout(new BorderLayout());

        this.add(corePanel, BorderLayout.CENTER);
    }
}

corePanel is a new JPanel which is added to the center part of the JFrame. The layout of corePanel is also BorderLayout. The file imports are added automatically on Eclipse.

Now let's display the window, so that we can see what is actually happening.

import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class MainWindow extends JFrame {
    
    private int remainingGuesses;
    private String wrongGuesses;
    private String word;
    private String visible;

    public MainWindow(String toGuess) {
        remainingGuesses = 10;
        wrongGuesses = "";
        word = toGuess;

        visible = "";

        for(int i = 0; i < word.length(); ++i) {
            visible += "_ ";
        }

        JPanel corePanel = new JPanel();
        corePanel.setLayout(new BorderLayout());

        this.add(corePanel, BorderLayout.CENTER);

        this.pack();
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    public static void main(String[] args) {
        new MainWindow("cat");
    }
}

Now let's create some JLabels and a JTextField to display texts and an input box.

import java.awt.BorderLayout;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;

public class MainWindow extends JFrame {
    
    private int remainingGuesses;
    private String wrongGuesses;
    private String word;
    private String visible;

    public MainWindow(String toGuess) {
        remainingGuesses = 10;
        wrongGuesses = "";
        word = toGuess;

        visible = "";

        for(int i = 0; i < word.length(); ++i) {
            visible += "_ ";
        }

        JPanel corePanel = new JPanel();
        corePanel.setLayout(new BorderLayout());

        JLabel status = new JLabel("You have "+remainingGuesses+" remaining", SwingConstants.CENTER);
        JLabel wrong = new JLabel("Wrong guesses so far: "+wrongGuesses);
        JLabel visibleLabel = new JLabel(visible, SwingConstants.CENTER);
        JTextField input = new JTextField(); 
        
        JPanel southPanel = new JPanel(new GridLayout(4, 1));
        southPanel.add(status);
        southPanel.add(visibleLabel);
        southPanel.add(input);
        southPanel.add(wrong);
        
        corePanel.add(southPanel, BorderLayout.SOUTH);

        this.add(corePanel, BorderLayout.CENTER);

        this.pack();
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    public static void main(String[] args) {
        new MainWindow("cat");
    }
}

The components are added to the southPanel's GridLayout's rows in order. Then, the southPanel is added to the corePanel's south component. The reason for this is that the center component of corePanel will be taken by the hangman figure.

Now comes the second best part, the hangman figure. Before that, you must understand a few basic functionalities.

Now let's create a new class called HangmanFigure.java.

import javax.swing.JPanel;

public class HangmanFigure extends JPanel {
    
}

Now let's create necessary variables. Hint: our figure is going to depend on the number of guesses the user has remaining.

import javax.swing.JPanel;

public class HangmanFigure extends JPanel {
    
    private int guesses;

}

Now let's create a constructor.

import javax.swing.JPanel;
import java.awt.Dimension;

public class HangmanFigure extends JPanel {
    
    private int guesses;

    public HangmanFigure() {
        super();
        guesses = 0;
        setPreferredSize(new Dimension(300, 300));
        setOpaque(true);
    }

}

Now let's set the base for some graphics. Remember the paintComponent method I mentioned in JPanels.

import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.Graphics;

public class HangmanFigure extends JPanel {
    
    private int guesses;

    public HangmanFigure() {
        super();
        guesses = 10;
        setPreferredSize(new Dimension(300, 300));
        setOpaque(true);
    }

    public void paintComponent(Graphics g) {
        
    }

}

So basically, whatever graphics I create in this method will be added to the panel.

import java.awt.Color;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.Graphics;

public class HangmanFigure extends JPanel {
    
    private int guesses;

    public HangmanFigure() {
        super();
        guesses = 10;
        setPreferredSize(new Dimension(300, 300));
        setOpaque(true);
    }

    public void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);

        // base
        if(guesses > 0) {
            g.drawLine(1, 299, 299, 299);
        }
    }

}

Let's continue:

import java.awt.Color;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.Graphics;

public class HangmanFigure extends JPanel {
    
    private int guesses;

    public HangmanFigure() {
        super();
        guesses = 10;
        setPreferredSize(new Dimension(300, 300));
        setOpaque(true);
    }

    public void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);

        // base
        if(guesses > 0) {
            g.drawLine(1, 299, 299, 299);
        }
        
        // right wall
        if(guesses > 1) {
            g.drawLine(299, 299, 299, 1);
        }
        
        // top line
        if(guesses > 2) {
            g.drawLine(150, 1, 299, 1);
        }
        
        // hanging line
        if(guesses > 3) {
            g.drawLine(150, 1, 150, 70);
        }
        
        // face
        if(guesses > 4) {
            g.drawOval(125, 70, 50, 50);
        }
        
        // body
        if(guesses > 5) {
            g.drawLine(150, 120, 150, 200);
        }
        
        // left hand
        if(guesses > 6) {
            g.drawLine(150, 150, 110, 140);
        }
        
        // right hand
        if(guesses > 7) {
            g.drawLine(150, 150, 190, 140);
        }
        
        // left leg
        if(guesses > 8) {
            g.drawLine(150, 200, 120, 250);
        }
        
        // right leg
        if(guesses > 9) {
            g.drawLine(150, 200, 180, 250);
        }
    }

}

That will produce a splendid hangman figure. Now we basically need a method which we can call everytime a user guesses wrongly so that the hangman figure gets updated.

import java.awt.Color;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.Graphics;

public class HangmanFigure extends JPanel {
    
    private int guesses;

    public HangmanFigure() {
        super();
        guesses = 10;
        setPreferredSize(new Dimension(300, 300));
        setOpaque(true);
    }

    public void paintComponent(Graphics g) {
        g.setColor(Color.BLACK);

        // base
        if(guesses > 0) {
            g.drawLine(1, 299, 299, 299);
        }
        
        // right wall
        if(guesses > 1) {
            g.drawLine(299, 299, 299, 1);
        }
        
        // top line
        if(guesses > 2) {
            g.drawLine(150, 1, 299, 1);
        }
        
        // hanging line
        if(guesses > 3) {
            g.drawLine(150, 1, 150, 70);
        }
        
        // face
        if(guesses > 4) {
            g.drawOval(125, 70, 50, 50);
        }
        
        // body
        if(guesses > 5) {
            g.drawLine(150, 120, 150, 200);
        }
        
        // left hand
        if(guesses > 6) {
            g.drawLine(150, 150, 110, 140);
        }
        
        // right hand
        if(guesses > 7) {
            g.drawLine(150, 150, 190, 140);
        }
        
        // left leg
        if(guesses > 8) {
            g.drawLine(150, 200, 120, 250);
        }
        
        // right leg
        if(guesses > 9) {
            g.drawLine(150, 200, 180, 250);
        }
    }

    public void set() {
        guesses++;
        paintComponent(this.getGraphics());
    }

}

This method will update the hangman figure by incrementing the guesses. getGraphics() is pre-built in JPanel.

Now we can add the hangman figure to our main window.

import java.awt.BorderLayout;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;

public class MainWindow extends JFrame {
    
    private int remainingGuesses;
    private String wrongGuesses;
    private String word;
    private String visible;

    public MainWindow(String toGuess) {
        remainingGuesses = 10;
        wrongGuesses = "";
        word = toGuess;

        visible = "";

        for(int i = 0; i < word.length(); ++i) {
            visible += "_ ";
        }

        JPanel corePanel = new JPanel();
        corePanel.setLayout(new BorderLayout());

        JLabel status = new JLabel("You have "+remainingGuesses+" remaining", SwingConstants.CENTER);
        JLabel wrong = new JLabel("Wrong guesses so far: "+wrongGuesses);
        JLabel visibleLabel = new JLabel(visible, SwingConstants.CENTER);
        JTextField input = new JTextField(); 
        
        JPanel southPanel = new JPanel(new GridLayout(4, 1));
        southPanel.add(status);
        southPanel.add(visibleLabel);
        southPanel.add(input);
        southPanel.add(wrong);
        
        corePanel.add(southPanel, BorderLayout.SOUTH);

        HangmanFigure hf = new HangmanFigure();
        corePanel.add(hf, BorderLayout.CENTER);

        this.add(corePanel, BorderLayout.CENTER);

        this.pack();
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    public static void main(String[] args) {
        new MainWindow("cat");
    }
}

We create a new hangman figure instance and add it to the center compartment of the corePanel.

That's our GUI completed! Now we can focus on what happens when a user input's a character in the input text box and presses enter. These are called ActionListeners. We need to create one that only executes once the user presses enter while focusing on the text box.

import java.awt.BorderLayout;
import java.awt.GridLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.SwingConstants;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class MainWindow extends JFrame {
    
    private int remainingGuesses;
    private String wrongGuesses;
    private String word;
    private String visible;

    public MainWindow(String toGuess) {
        remainingGuesses = 10;
        wrongGuesses = "";
        word = toGuess;

        visible = "";

        for(int i = 0; i < word.length(); ++i) {
            visible += "_ ";
        }

        JPanel corePanel = new JPanel();
        corePanel.setLayout(new BorderLayout());

        JLabel status = new JLabel("You have "+remainingGuesses+" remaining", SwingConstants.CENTER);
        JLabel wrong = new JLabel("Wrong guesses so far: "+wrongGuesses);
        JLabel visibleLabel = new JLabel(visible, SwingConstants.CENTER);
        JTextField input = new JTextField(); 
        
        JPanel southPanel = new JPanel(new GridLayout(4, 1));
        southPanel.add(status);
        southPanel.add(visibleLabel);
        southPanel.add(input);
        southPanel.add(wrong);
        
        corePanel.add(southPanel, BorderLayout.SOUTH);

        HangmanFigure hf = new HangmanFigure();
        corePanel.add(hf, BorderLayout.CENTER);

        this.add(corePanel, BorderLayout.CENTER);

        input.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                // TODO Auto-generated method stub
                
            }
            
        });

        this.pack();
        this.setLocationRelativeTo(null);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setVisible(true);
    }

    public static void main(String[] args) {
        new MainWindow("cat");
    }
}

So any code written in actionPerformed method will be executed when the user presses enter while focusing on the textbox.

So firstly in our listener, we need to check a few things:

input.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
        String text = input.getText();
        if(text.length() == 1 && text.matches("[a-z]")) {
            // continue here
        }
        else {
            System.out.println("Invalid input!");
        }

        input.setText("");
    }
    
});

I added a keyword final before the JTextField input. This is because if you want to you the text field inside an action listener, it must be declared final. getText() and setText() methods access or mutate the text of the input box.

text.matches("[a-z]") is using Regular Expression to make sure the input is anything in between a to z (inclusive).

Now, that we have verified the input, we should make sure the guess is correct.

input.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
        String text = input.getText();
        if(text.length() == 1 && text.matches("[a-z]")) {
            
            boolean guessFound = false;

            for(int i = 0; i < word.length(); ++i) {
                if(text.charAt(0) == word.charAt(i)) {
                    guessFound = true;
                }
            }

            if(!guessFound) {
                // code to execute if the guess if not found
            }

        }
        else {
            System.out.println("Invalid input!");
        }

        input.setText("");
    }
    
});

Now we need to decide what happens if the guess is not found. So if the user has no more guesses remaining, the program should say you have lose, the word was "cat". It should also disable the text box from capturing anymore inputs.

input.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
        String text = input.getText();
        if(text.length() == 1 && text.matches("[a-z]")) {
            
            boolean guessFound = false;

            for(int i = 0; i < word.length(); ++i) {
                if(text.charAt(0) == word.charAt(i)) {
                    guessFound = true;
                }
            }

            if(!guessFound) {
                if(--remainingGuesses >= 0) {
                    status.setText("You have "+remainingGuesses+" guesses remaining");
                    wrongGuesses += text+" ";
                    wrong.setText("Wrong guesses so far: "+wrongGuesses);
                    hf.set();
                }
                else {
                    status.setText("You lost: the word was "+word);
                    input.setEnabled(false);
                }
            }

        }
        else {
            System.out.println("Invalid input!");
        }

        input.setText("");
    }
    
});

Now we need to do the other side of the story. So what if the guess if found. Firstly, when the guess is found, we need to update the value of visible (i.e.: replace the underscores with the letters that have been found in those positions).

input.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
        String text = input.getText();
        if(text.length() == 1 && text.matches("[a-z]")) {
            
            boolean guessFound = false;

            for(int i = 0; i < word.length(); ++i) {
                if(text.charAt(0) == word.charAt(i)) {
                    guessFound = true;

                    String newVisible = "";
                    for(int j = 0; j < visible.length(); ++j) {
                        if(j == (i*2)) {
                            newVisible += word.charAt(i);
                        }
                        else {
                            newVisible += visible.charAt(j);
                        }
                    }
                    visible = newVisible;
                    visibleLabel.setText(visible);
                }
            }

            if(!guessFound) {
                if(--remainingGuesses >= 0) {
                    status.setText("You have "+remainingGuesses+" guesses remaining");
                    wrongGuesses += text+" ";
                    wrong.setText("Wrong guesses so far: "+wrongGuesses);
                    hf.set();
                }
                else {
                    status.setText("You lost: the word was "+word);
                    input.setEnabled(false);
                }
            }

        }
        else {
            System.out.println("Invalid input!");
        }

        input.setText("");
    }
    
});

Now that we have taken care of updating the main text when the guess if found, let's focus on the last bit. So if the guess is found and the whole word is guessed correctly, show the user that they have won.

input.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
        String text = input.getText();
        if(text.length() == 1 && text.matches("[a-z]")) {
            
            boolean guessFound = false;

            for(int i = 0; i < word.length(); ++i) {
                if(text.charAt(0) == word.charAt(i)) {
                    guessFound = true;

                    String newVisible = "";
                    for(int j = 0; j < visible.length(); ++j) {
                        if(j == (i*2)) {
                            newVisible += word.charAt(i);
                        }
                        else {
                            newVisible += visible.charAt(j);
                        }
                    }
                    visible = newVisible;
                    visibleLabel.setText(visible);
                }
            }

            if(!guessFound) {
                if(--remainingGuesses >= 0) {
                    status.setText("You have "+remainingGuesses+" guesses remaining");
                    wrongGuesses += text+" ";
                    wrong.setText("Wrong guesses so far: "+wrongGuesses);
                    hf.set();
                }
                else {
                    status.setText("You lost: the word was "+word);
                    input.setEnabled(false);
                }
            }
            else {
                String actualVisible = "";
                for(int i = 0; i < visible.length(); i+=2) {
                    actualVisible += visible.charAt(i);
                }
                
                if(actualVisible.equals(word)) {
                    status.setText("Congratulations, you have won!");
                    input.setEnabled(false);
                }
            }

        }
        else {
            System.out.println("Invalid input!");
        }

        input.setText("");
    }
    
});

And that's it! You can set your own word through new MainWindow("cat"). All the code written is available to download in a zip file.

Thank you so much for going through the tutorial. If you have any questions or suggestions, please post them in the comment section below. I hope I have helped you learn something new today.