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, along with any pointers on how to neaten up the code.
Also, any feedback regarding the use of JavaDoc style comments is welcome.
Main - the class 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();
}
}
}