4
\$\begingroup\$

I have the following code in Java:

HangmanFunctions class:

import java.util.ArrayList;
import java.util.Random;

public class HangmanFunctions {
    private static final String[] WORDS = {"jazz", "buzz", "hajj", "fuzz", "jinx",
                                           "jazzy", "fuzzy", "faffs", "fizzy", "jiffs",
                                           "jazzed", "buzzed", "jazzes", "faffed", "fizzed",
                                           "jazzing", "buzzing", "jazzier", "faffing", "fuzzing"};
    private String word;
    private ArrayList<Character> hiddenWord = new ArrayList<Character>();
    private ArrayList<Character> lettersGuessed = new ArrayList<Character>();

    public HangmanFunctions() {
        setHiddenWord();
    }

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

    private void setWord() {
        word = WORDS[new Random().nextInt(19)];
    }

    public String getWord() {
        return word;
    }

    private void setHiddenWord() {
        setWord();
        for (char letter: word.toCharArray()) {
            hiddenWord.add('-');
        }
    }

    public ArrayList<Character> getHiddenWord() {
        return hiddenWord;
    }

    private ArrayList<Integer> getIndexesOf(char letter) {
        ArrayList<Integer> instances = new ArrayList<Integer>();
        for (int i = word.indexOf(letter); i >= 0; i = word.indexOf(letter, i + 1)) {
            instances.add(i);
        }
        return instances;
    }

    public void revealLetter(char letter) {
        for (int i: getIndexesOf(letter)) {
            hiddenWord.set(i, word.charAt(i));
        }
    }

    public void addGuess(char letter) {
        lettersGuessed.add(letter);
    }

    private boolean isGuessed(char letter) {
        return lettersGuessed.indexOf(letter) != -1;
    }

    public Results getResult(char letter) {
        if (!isGuessed(letter)) {
            if (getIndexesOf(letter).size() > 0) {
                return Results.CORRECT;
            }
            else {
                return Results.INCORRECT;
            }
        }
        else {
            return Results.ALREADY_GUESSED;
        }
    }
}

HangmanPanel class:

import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;

import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class HangmanPanel extends JPanel {
    private HangmanFunctions functions;
    private JLabel word;
    private JLabel lettersGuessed;
    private JTextField inputBox;
    private Font largeFont = new Font("sansserif", Font.PLAIN, 50);
    private int hangmanPos = 0;

    public HangmanPanel(HangmanFunctions functions) {
        this.functions = functions;

        setLayout(new GridBagLayout());
        GridBagConstraints c = new GridBagConstraints();
        c.insets = new Insets(10, 10, 10, 350);

        word = new JLabel(toHiddenWord(functions.getHiddenWord()));
        word.setFont(largeFont);
        c.gridy = 0;
        add(word, c);

        lettersGuessed = new JLabel(getLettersGuessedString());
        c.gridy = 1;
        add(lettersGuessed, c);

        inputBox = new JTextField();
        inputBox.setPreferredSize(new Dimension(25, 25));
        inputBox.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                newGuess(e.getActionCommand().charAt(0));
            }
        });
        c.gridy = 2;
        add(inputBox, c);
    }

    private String toHiddenWord(ArrayList<Character> word) {
        String hiddenWord = " ";
        for (int i = 0; i < word.size(); i++) {
            hiddenWord += (word.get(i) + " "); 
        }
        return hiddenWord;
    }

    private String getLettersGuessedString() {
        String lettersGuessedString = "Letters Guessed: ";
        for (char i: functions.getLettersGuessed()) {
            lettersGuessedString += (i + ", ");
        }
        return lettersGuessedString;
    }

    private void newGuess(char letter) {
        Results result = functions.getResult(letter);
        if (result == Results.CORRECT) {
            JOptionPane.showMessageDialog(null, "Correct.", "Hangman", JOptionPane.PLAIN_MESSAGE);
            functions.addGuess(letter);
            functions.revealLetter(letter);
        }
        else if (result == Results.INCORRECT) {
            JOptionPane.showMessageDialog(null, "Incorrect.", "Hangman", JOptionPane.PLAIN_MESSAGE);
            functions.addGuess(letter);
            hangmanPos++;
            repaint();
        }
        else {
            JOptionPane.showMessageDialog(null, "Already Guessed.", "Hangman", JOptionPane.PLAIN_MESSAGE);
        }
        inputBox.setText("");
        lettersGuessed.setText(getLettersGuessedString());
        word.setText(toHiddenWord(functions.getHiddenWord()));
        if (functions.getHiddenWord().indexOf('-') == -1) {
            JOptionPane.showMessageDialog(null, "Congratulations, " + functions.getWord() + " was my word", "Hangman", JOptionPane.PLAIN_MESSAGE);
        }
    }

    @Override
    public void paintComponent(Graphics g) {
        g.drawLine(350, 0, 350, 500);
        switch (hangmanPos) {
        case 9:
            g.drawLine(500, 250, 525, 275);
            JOptionPane.showMessageDialog(null, "Oops! You've been hanged! My word was " + functions.getWord(), "Hangman", JOptionPane.PLAIN_MESSAGE);
        case 8:
            g.drawLine(500, 250, 475, 275);
        case 7:
            g.drawLine(500, 225, 525, 225);
        case 6:
            g.drawLine(500, 225, 475, 225);
        case 5:
            g.drawLine(500, 200, 500, 250);
        case 4:
            g.drawOval(475, 150, 50, 50);
        case 3:
            g.drawLine(500, 100, 500, 150);
        case 2:
            g.drawLine(600, 100, 500, 100);
        case 1:
            g.drawLine(600, 350, 600, 100);
        case 0:
            g.drawLine(450, 350, 600, 350);
        }
    }
}

Results enum:

public enum Results {
    CORRECT, INCORRECT, ALREADY_GUESSED
}

Frame class:

import javax.swing.JFrame;

public class Frame extends JFrame {

    public Frame() {
        super("Hangman");
        setSize(700, 500);
        setResizable(false);
        setVisible(true);
        add(new HangmanPanel(new HangmanFunctions()));
    }

    public static void main(String[] args) {
        new Frame();
    }
}

In theory, this code works, so I decided it was okay to post it here. However, when ran, it looks a bit like this:

Hangman in Java

So, I'm assuming there's a lot of bad practices used in my code. Can anyone identify them?

\$\endgroup\$
1
  • \$\begingroup\$ I started to work on the changes needed, but it's too much work. Basically, separate your one JPanel into two JPanels. One JPanel for the left side of your game, and another JPanel for the right side of your game. Your HangmanPanel is doing way too much work that should be a part of HangmanFunctions. \$\endgroup\$ Commented Jun 3, 2013 at 1:31

1 Answer 1

6
\$\begingroup\$

Overview

Separate the game's entry point, model, the view, and the controller into different classes:

  • HangmanGame - The main entry point to the application.
  • HangmanModel - Contains information about the word being guessed.
  • HangmanView - Displays the results of the model.
  • HangmanController - Updates the model based on events from the view.

Identify the responsibilities and behaviours of the classes.

HangmanGame

Responsibilities:

  • Creates instances of the required classes (see also Spring)
  • Exits the application.
  • Creates a new game.

HangmanModel

Responsibilities:

  • Load words.
  • Select a word to guess.
  • Retrieves positions that a letter is found (or throws an exception).
  • Tracks incorrect guesses.

HangmanView

Responsibilities:

  • Creates a new UI for the view.
  • Updates the drawing of the hangman.
  • Updates the letters being displayed.
  • Notifies listeners of guesses.
  • Notifies listeners of when the user requests to quit.
  • Animates the hanging.

HangmanController

Responsibilities:

  • Listens for events from the view.
  • Tells the view when the game is over.
  • Tells the view when another guess is made.
  • Tells the view where to put the letters.

Implementation

Once you've identified the classes, their responsibilities, and their collaborators, think about how you would implement the actions described above. For example:

public class HangmanModel {
  public void load( File dictionary ) { ... }
  public int[] guess( char ch ) { ... }
  public void badGuess() { ... }
  public void pickWord() { ... }
}

public class HangmanGame {
  public void initialize() { ... }
  public void quit() { ... }
  public void newGame() { ... }
}

public class HangmanView {
  public void initialize() { ... }
  public void drawHangman( HangmanStatus status ) { ... }
  public void drawWord( String word[] ) { ... }
  public void notifyGuess() { ... }
  public void notifyQuit() { ... }
  public void animateHanging() { ... }
}

Once you are satisfied that you have thought about most of the possible actions that each class will need to make the game, then consider the attributes each class needs and how the implementation will work.

Procedural Programming vs. OOP

The HangmanFunctions class is not object-oriented. When writing object-oriented code try to think in terms of the objects in the system. For example, you could further split HangmanView into two classes: HangmanUI and AnimatedHangman. The HangmanUI would represent the user interface (ASCII text in DOS vs. Swing vs. AWT) wherein the user interacts with the game. The AnimatedHangman would abstract drawing and animating the hangman figure in the game.

A set of functions is not an object-oriented class.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.