I’ve been getting used to Java and I’ve written a simple hangman game using event-driven programming in Java 8. Please let me know of any improvements regarding the readability and design of the program.


**Main - used to start the game**

    package Hangman;
    
    public class Main {
        public static void main(String[] args){
            new Logic().go();
        }
    }


**Logic - contains all the game logic**

    package Hangman;
    
    import java.awt.Toolkit;
    import java.util.ArrayList;
    import java.util.List;
    
    /** Logic - Deals with the logic aspect of Hangman */
    public class Logic {
        private GUI gui;
        private Information information;
        private WordReader wordReader;
    
    
        /** Logic constructor */
        public Logic(){
            wordReader = new WordReader();
            information = new Information(wordReader);
            gui = new GUI(this, information);
        }
    
    
        /** checkUserGuess - runs the code for when a user guesses a letter that's in the secret word */
        void checkUserGuess(String guess){
            if(isGuessValid(guess)) {
                updateSecretWord(guess);
                gui.drawSecretWord();
    
                if(information.getLettersGuessed().size() != 0) {      // No letters have been guessed by the user at the beginning
                    gui.drawLettersGuessed();
                }
                // Checks if the user has won or lost
                if (isGameWon()) {
                    winSequence();
                }else if (information.getGuessesRemaining() == 0) {
                    loseSequence();
                }
            }
        }
    
        void go(){
            information.createAlphabetSet();
            wordReader.readFile("words.txt");
            gui.build();
            setUpGame();
        }
    
        /** isGameWon - sees if the user has won the game */
        private boolean isGameWon(){
            for(boolean isLetterShown : information.getLettersRevealed()){
                if(!isLetterShown) {
                    return false;
                }
            }
            return true;
        }
    
        /** isGuessValid - get input from the user & checks to see if it's a valid guess */
        private boolean isGuessValid(String guessedLetter){
            if(guessedLetter.length() == 1 && !information.getLettersGuessed().contains(guessedLetter.charAt(0))
                    && information.getAlphabet().contains(guessedLetter.charAt(0))) {
                gui.setText(null);
                information.addGuessedLetter(guessedLetter.charAt(0));
                return true;
            }else{
                Toolkit.getDefaultToolkit().beep();
                gui.getTextField().requestFocusInWindow();
            }
            return false;
        }
    
        /** loseSequence - when the the user runs out of guesses */
        private void loseSequence(){
            for(int i = 0; i < information.getLettersRevealed().length; i++){
                information.revealLetter(i);
            }
            gui.drawSecretWord();
            playAgain("Tough luck. The secret word was " + information.getSecretWord() +
                    ".\nWould you like to play another game of hangman?");
        }
    
        /** playAgain - Allows the user to play another game of hangman */
        private void playAgain(String message){
            if(gui.playAgainDialog(message)){
                setUpGame();
                gui.updateDrawing();
            }else{
                System.exit(0);
            }
        }
    
        /** setUpGame - sets up the variables appropriately */
        void setUpGame(){
            information.resetGameData();
            // Redraws the JLabels when playing another game of hangman
            gui.drawSecretWord();
            gui.drawLettersGuessed();
            gui.drawGuessesRemaining();
        }
    
        /** updateSecretWord - updates which letters of the secret word have been revealed */
        private void updateSecretWord(String guessedLetter){
            List<Integer> changeBool = new ArrayList<>();
            if(information.getSecretWord().contains(guessedLetter)){
                // Searches through secretWord & notes down all letters that equal the user's guess
                for(int i=0; i<information.getSecretWord().length(); i++){
                    if(information.getSecretWord().charAt(i) == guessedLetter.charAt(0)) {
                        changeBool.add(i);
                    }
                }
                // Changes the boolean value for those letters @ their corresponding indexes
                for(Integer i : changeBool) {
                    information.revealLetter(i);
                }
            }else{
                information.setGuessesRemaining(information.getGuessesRemaining() - 1);
                gui.drawGuessesRemaining();
                gui.updateDrawing();
            }
        }
    
        /** winSequence - when the user has correctly guessed the secret word */
        private void winSequence(){
            playAgain("Well done! You guessed " + information.getSecretWord() + " with " + information.getGuessesRemaining() +
                            " guesses left!\nWould you like to play another game of hangman?"
            );
        }
    }

**GUI - contains everything related to the GUI**

    package Hangman;

    import java.awt.BorderLayout;
    import java.awt.Component;
    import java.awt.Dimension;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import javax.swing.BorderFactory;
    import javax.swing.BoxLayout;
    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JOptionPane;
    import javax.swing.JPanel;
    import javax.swing.JTextField;
    import static javax.swing.SwingUtilities.invokeLater;
    import javax.swing.WindowConstants;

    /** GUI - Deals with the GUI aspect of Hangman */
    public class GUI {
        private JFrame frame;
        private JPanel contentPane;
        private JPanel containerPanel;
        private JTextField textField;
        private JLabel guessesRemainingLabel;
        private JLabel lettersGuessedLabel;
        private JLabel secretWordLabel;

        private static final Dimension FRAME_SIZE = new Dimension(250, 350);

        private Information information;
        private Logic logic;


        /** GUI constructor */
        public GUI(Logic logic, Information information){
            this.logic = logic;
            this.information = information;
        }


        void build(){
            invokeLater(
                () -> {
                    buildFrame();
                    buildContentPane();
                    buildContainerPanel();
                    buildLabel();
                    buildInputPanel();
                    displayFrame();
                }
            );
        }

        private void buildContentPane(){
            contentPane = new DrawingPanel(information);
            contentPane.setLayout(new BorderLayout());
            contentPane.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
            frame.setContentPane(contentPane);
        }

        private void buildContainerPanel(){
            containerPanel = new JPanel(new BorderLayout());
            contentPane.add(BorderLayout.SOUTH, containerPanel);
        }

        private void buildFrame(){
            frame = new JFrame("Hangman");
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setMinimumSize(FRAME_SIZE);
        }

        private void buildInputPanel(){
            JButton checkButton = new JButton("Guess");
            GuessListener guessListener = new GuessListener();
            checkButton.addActionListener(guessListener);
            textField = new JTextField();
            textField.addActionListener(guessListener);

            JPanel inputPanel = new JPanel(new BorderLayout());
            inputPanel.add(BorderLayout.CENTER, textField);
            inputPanel.add(BorderLayout.EAST, checkButton);
            inputPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
            containerPanel.add(BorderLayout.SOUTH, inputPanel);
        }

        private void buildLabel(){
            JPanel labelPanel = new JPanel();
            labelPanel.setLayout(new BoxLayout(labelPanel, BoxLayout.PAGE_AXIS));
            guessesRemainingLabel = new JLabel("Guesses remaining: " + String.valueOf(Information.NUM_OF_GUESSES));
            lettersGuessedLabel = new JLabel("Already guessed: ");
            secretWordLabel = new JLabel();
            guessesRemainingLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
            lettersGuessedLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
            secretWordLabel.setAlignmentX(Component.LEFT_ALIGNMENT);
            labelPanel.add(guessesRemainingLabel);
            labelPanel.add(lettersGuessedLabel);
            labelPanel.add(secretWordLabel);
            containerPanel.add(BorderLayout.NORTH, labelPanel);
        }

        private void displayFrame(){
            frame.setSize(FRAME_SIZE);
            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }

        /** drawGuessesRemaining - Shows the guesses remaining that the user has left */
        void drawGuessesRemaining(){
            invokeLater(() -> guessesRemainingLabel.setText("Guesses remaining: " +
                    String.valueOf(information.getGuessesRemaining())));
        }

        /** drawLettersGuessed - Shows the letters guessed by the user */
        void drawLettersGuessed(){
            StringBuilder lettersBuilder = new StringBuilder();
            for (Character guessedLetter : information.getLettersGuessed()) {
                String s = guessedLetter + " ";
                lettersBuilder.append(s);
            }
            setLettersGuessedLabel(lettersBuilder.toString());
        }

        /** drawSecretWord - draws secretWord with dashes & letters that the user has correctly guessed so far */
        void drawSecretWord(){
            StringBuilder word = new StringBuilder();
            for(int i = 0; i < information.getLettersRevealed().length; i++){
                if (information.getLettersRevealed()[i]) {
                    String letter = information.getSecretWord().charAt(i) + " ";
                    word.append(letter);
                } else {
                    word.append("_ ");
                }
            }
            setSecretWordLabel(word.toString());
        }

        /** playAgainDialog - asks the user if they want to play another game of hangman */
        boolean playAgainDialog(String message){
            int optionChosen = JOptionPane.showConfirmDialog(frame, message, "Play again?", JOptionPane.YES_NO_OPTION,
                    JOptionPane.PLAIN_MESSAGE);
            return optionChosen == JOptionPane.YES_OPTION;
        }

        void updateDrawing(){
            contentPane.repaint();
        }


        // GETTERS
        JTextField getTextField(){
            return textField;
        }


        // SETTERS
        void setLettersGuessedLabel(String text){
            invokeLater(() -> lettersGuessedLabel.setText("Already guessed: " + text));
        }

        void setText(String text){
            invokeLater(() -> textField.setText(text));
        }

        void setSecretWordLabel(String text){
            invokeLater(() -> secretWordLabel.setText(text));
        }


        // LISTENERS
        /** GuessListener - deals with the user's guess */
        private class GuessListener implements ActionListener {
            @Override
            public void actionPerformed(ActionEvent ev){
                logic.checkUserGuess(getTextField().getText());
            }
        }
    }


**DrawingPanel - used to draw the hangman figure**

    package Hangman;

    import java.awt.BasicStroke;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import javax.swing.JPanel;

    /** DrawingPanel - Panel used to draw the hangman figure onto the frame */
    public class DrawingPanel extends JPanel {
        // Constants to do with the hanging frame
        private static final int STARTING_X_POSITION = 55;
        private static final int STARTING_Y_POSITION = 30;
        private static final int BASE_Y_POSITION = STARTING_Y_POSITION + 200;
        private static final int WIRE_LENGTH = 40;

        // Constants to do with the hangman himself
        private static final int HEAD_RADIUS = 17;
        private static final int BODY_LENGTH = 50;
        private static final int BODY_X_POSITION = STARTING_X_POSITION + 120;
        private static final int ARM_LENGTH = 25;
        private static final int ARM_Y_POSITION = STARTING_Y_POSITION + WIRE_LENGTH + (2 * HEAD_RADIUS) + 20;
        private static final int LEG_LENGTH = 30;
        private static final int LEG_Y_POSITION = STARTING_Y_POSITION + WIRE_LENGTH + (2 * HEAD_RADIUS) + 50;

        private Information information;    // Solely used to get the number of guesses remaining


        /** DrawingPanel constructor */
        public DrawingPanel(Information information){
            this.information = information;
        }


        /** drawHead - draws the head onto the panel */
        private void drawHead(int xPosition, int yPosition, Graphics g){
            g.drawOval(xPosition, yPosition, 2 * HEAD_RADIUS, 2 * HEAD_RADIUS);
        }

        /** drawBody - draws the body onto the panel */
        private void drawBody(int xPosition, int yPosition, Graphics g){
            g.drawLine(xPosition, yPosition, xPosition, yPosition + BODY_LENGTH);
        }

        /** drawLeftArm - draws the left arm onto the panel */
        private void drawLeftArm(int xPosition, int yPosition, Graphics g){
            g.drawLine(xPosition, yPosition, xPosition - ARM_LENGTH, yPosition);
        }

        /** drawLeftLeg - draws the left leg onto the panel */
        private void drawLeftLeg(int xPosition, int yPosition, Graphics g){
            g.drawLine(xPosition, yPosition, xPosition - LEG_LENGTH, yPosition + LEG_LENGTH);
        }

        /** drawRightArm - draws the right arm onto the panel */
        private void drawRightArm(int xPosition, int yPosition, Graphics g){
            g.drawLine(xPosition, yPosition, xPosition + ARM_LENGTH, yPosition);
        }

        /** drawRightLeg - draws the right leg onto the panel */
        private void drawRightLeg(int xPosition, int yPosition, Graphics g){
            g.drawLine(xPosition, yPosition, xPosition + LEG_LENGTH, yPosition + LEG_LENGTH);
        }

        /** setUpDrawing - draws the wire frame of the hangman drawing when the user hasn't lost a guess yet */
        private void setUpDrawing(int xPosition, int yPosition, Graphics g){
            // Base of the frame
            Graphics2D g2D = (Graphics2D) g;
            g2D.setStroke(new BasicStroke(2));
            g2D.drawLine(xPosition - 15, BASE_Y_POSITION, xPosition + 50, BASE_Y_POSITION);

            // Frame support
            g2D.setStroke(new BasicStroke(1));
            g2D.drawLine(xPosition, yPosition, xPosition, BASE_Y_POSITION);
            g2D.drawLine(xPosition, yPosition + 20, xPosition + 20, yPosition);
            g2D.drawLine(xPosition, yPosition, BODY_X_POSITION, yPosition);

            // Wire from which the person hangs from
            g2D.drawLine(BODY_X_POSITION, yPosition, BODY_X_POSITION, yPosition + WIRE_LENGTH);
        }

        /** paintComponent - used to draw the hangman drawing onto the panel */
        @Override
        protected void paintComponent(Graphics g){
            super.paintComponent(g);
            switch (information.getGuessesRemaining()) {
                case 0: drawLeftArm(BODY_X_POSITION, ARM_Y_POSITION, g);

                case 1: drawRightArm(BODY_X_POSITION, ARM_Y_POSITION, g);

                case 2: drawLeftLeg(BODY_X_POSITION, LEG_Y_POSITION, g);

                case 3: drawRightLeg(BODY_X_POSITION, LEG_Y_POSITION, g);

                case 4: drawBody(BODY_X_POSITION, STARTING_Y_POSITION + WIRE_LENGTH + (2 * HEAD_RADIUS), g);

                case 5: drawHead(BODY_X_POSITION - HEAD_RADIUS, STARTING_Y_POSITION + WIRE_LENGTH, g);

                case 6: setUpDrawing(STARTING_X_POSITION, STARTING_Y_POSITION, g);
            }
        }
    }


**Information - class that deals with all the information obtained from the user**

    package Hangman;

    import java.util.Arrays;
    import java.util.HashSet;
    import java.util.Set;

    /** Information - Deals with all the information that the user has inputted into one game of Hangman */
    public class Information {
        private static final int ALPHABET_SIZE = 26;
        static final int NUM_OF_GUESSES = 6;

        private String secretWord;
        private Set<Character> alphabet;
        private Set<Character> lettersGuessed;    // letters the user has guessed
        private boolean[] lettersRevealed;       // determines if the letter should be revealed or not
        private int guessesRemaining;

        private WordReader wordReader;


        /** Information constructor */
        public Information(WordReader wordReader){
            this.wordReader = wordReader;
        }


        /** addGuessedLetter - adds the guessed letter to lettersGuessed */
        void addGuessedLetter(char guessedLetter){
            lettersGuessed.add(guessedLetter);
        }

        /** createAlphabetSet - Creates the alphabet set that's used to ensure that the user's
         * guess is not a number nor a special character */
        void createAlphabetSet(){
            alphabet = new HashSet<>(ALPHABET_SIZE);
            for(Character c = 'a'; c<='z'; c++){
                alphabet.add(c);
            }
        }

        /** resetGameData - clears the information from the previous game of Hangman */
        void resetGameData(){
            guessesRemaining = NUM_OF_GUESSES;
            setSecretWord(wordReader.chooseSecretWord());
            lettersRevealed = new boolean[getSecretWord().length()];
            Arrays.fill(lettersRevealed, false);
            lettersGuessed = new HashSet<>(ALPHABET_SIZE);
        }

        /** revealLetter - causes a letter to be shown on the GUI (occurs only once the user has correctly guessed a letter
         * in secretWord) */
        void revealLetter(int index){
            lettersRevealed[index] = true;
        }


        // GETTERS
        public Set<Character> getAlphabet() {
            return alphabet;
        }

        public int getGuessesRemaining() {
            return guessesRemaining;
        }

        public Set<Character> getLettersGuessed() {
            return lettersGuessed;
        }

        public boolean[] getLettersRevealed() {
            return lettersRevealed;
        }

        String getSecretWord(){
            return secretWord;
        }

        // SETTERS
        public void setGuessesRemaining(int guessesRemaining) {
            this.guessesRemaining = guessesRemaining;
        }

        public void setSecretWord(String secretWord) {
            this.secretWord = secretWord;
        }
    }

**WordReader - class for loading and selecting a word for the user to guess**

    package Hangman;

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;

    /** WordReader - Deals with loading and selecting a word from a file containing a collection of words */
    public class WordReader {
        String[] wordList;

        /** chooseSecretWord - randomly selects a word from wordLst for the user to guess */
        String chooseSecretWord(){
            return wordList[(int)(Math.random() * wordList.length)];
        }

        /** readFile - read in the list of words from a specific location and saves the words in wordList */
        void readFile(String location){
            try(BufferedReader input = new BufferedReader(
                    new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream(location)))){
                wordList = input.readLine().split(" ");
            }catch(IOException ioException) {
                ioException.printStackTrace();
            }
        }
    }