Implementing a text-based Hangman game in Java

This describes an implementation of a text-based Hangman game in Java, as asked in this question on stackoverflow.

I had a little too much fun with this.

Example output:

[C:\java_code\]java HangmanWord onomatopoeia
The secret word is: ------------. Guess (char idx idx idx ...): t 2 3 4
No characters guessed.
The secret word is: ------------. Guess (char idx idx idx ...): e 6 7 8 9
The secret word is: ---------e--. Guess (char idx idx idx ...): a 6 7 8
No characters guessed.
The secret word is: ---------e--. Guess (char idx idx idx ...): a 4
The secret word is: ----a----e--. Guess (char idx idx idx ...): o 0 3
The secret word is: o---a----e--. Guess (char idx idx idx ...): o 0 2 8 6
The secret word is: o-o-a-o-oe--. Guess (char idx idx idx ...): p 0 1 2 3 4 5 6 7 8 9 10 11
The secret word is: o-o-a-opoe--. Guess (char idx idx idx ...): n 1
The secret word is: ono-a-opoe--. Guess (char idx idx idx ...): t 4 5
The secret word is: ono-atopoe--. Guess (char idx idx idx ...): z 0
No characters guessed.
The secret word is: ono-atopoe--. Guess (char idx idx idx ...): m 3 4
The secret word is: onomatopoe--. Guess (char idx idx idx ...): i 10
The secret word is: onomatopoei-. Guess (char idx idx idx ...): a 11
SUCCESS: onomatopoeia

The main class:

public class HangmanWord  {
   private static final char MASK_CHAR = '-';
   private final String word;
   private final char[] maskedWordChars;
   private int totalMatched;
   public HangmanWord(String alphaNum_secretWord)  {
      int wordLen = -1;
      try  {
         wordLen = alphaNum_secretWord.length();
      }  catch(NullPointerException npx)  {
         throw  new NullPointerException("alphaNum_secretWord");
      }

      if(wordLen == 0)  {
         throw  new IllegalArgumentException("alphaNum_secretWord is non-null, but empty.");
      }

      //default is automatically false
      maskedWordChars = new char[wordLen];
      Arrays.fill(maskedWordChars, MASK_CHAR);
      totalMatched = 0;

      if(!Pattern.compile("^\\w+").matcher(alphaNum_secretWord).matches())  {
         throw  new IllegalArgumentException("alphaNum_secretWord (\"" + alphaNum_secretWord + "\") must contain only letters and digits.");
      }
      word = alphaNum_secretWord;
   }
   public int length()  {
      return  maskedWordChars.length;
   }
   public int getTotalMatchedCount()  {
      return  totalMatched;
   }
   public boolean didWin()  {
      return  (getTotalMatchedCount() == length());
   }

The main logic function in HangmanWord, which processes the guess, updates the letter-mask array, and returns the number of letters matched in this guess:

   public int getMatchedCountFromGuess(char guessChar, int... idxGuesses)  {
      if(idxGuesses.length == 0)  {
         throw  new IllegalArgumentException("idxGuesses.length is zero.");
      }
      int guessedCount = 0;
      for(int idx : idxGuesses)  {
         try  {
            if(maskedWordChars[idx] == MASK_CHAR  &&  word.charAt(idx) == guessChar)  {
               maskedWordChars[idx] = guessChar;
               guessedCount++;
            }
         }  catch(ArrayIndexOutOfBoundsException abx)  {
            throw  new IllegalArgumentException("Index " + idx + " is invalid, given length() is " + length(), abx);
         }
      }
      totalMatched += guessedCount;
      return  guessedCount;
   }
   public String getMasked()  {
      return  (new String(maskedWordChars));
   }

Demo use

The below main function takes advantage of the HangmanGuess class, which is below.

(Output is at the top of this post.)

Note that this does not accept user-input. Hence the below HangmanGuess class, in which I can store simulated input. There is an array of guess-objects below.

(Testing a significant program such as this with user input is a nightmare. I actually created the user-input code, but deleted by accident. I was going to put it in a separate function, so it could be optionally used, but got distracted before I pasted it back. It is so stinking cumbersome to enter all this data by hand every time you run it. I just don’t get why user-input is required so much in CS education/chosen by newbies, for use in anything but the most trivial programs.)

   public static final void main(String[] idx0_alphaNumSecretWord)  {
      HangmanWord hword = null;
      try  {
         hword = new HangmanWord(idx0_alphaNumSecretWord[0]);
      }  catch(ArrayIndexOutOfBoundsException abx)  {
         throw  new IllegalArgumentException("Missing one required parameter: The alpha-numeric secret word.");
      }

      //TESTING ONLY:
         //#=matches in guess
         HangmanGuess[] testGuesses = new HangmanGuess[]{ //012345678901
            new HangmanGuess('t', 2, 3, 4),               //onomatopoeia
            new HangmanGuess('e', 6, 7, 8, 9), //1
            new HangmanGuess('a', 6, 7, 8),
            new HangmanGuess('a', 4),          //1
            new HangmanGuess('o', 0, 3),       //1
            new HangmanGuess('o', 0, 2, 8 ,6), //3
            new HangmanGuess('p', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),  //1
            new HangmanGuess('n', 1),          //1
            new HangmanGuess('t', 4, 5),       //1
            new HangmanGuess('z', 0),
            new HangmanGuess('m', 3, 4),       //1
            new HangmanGuess('i', 10),         //1
            new HangmanGuess('a', 11)          //1
         };
         int testIdx = 0;

Main usage-logic:

      while(!hword.didWin())  {
         System.out.print("The secret word is: " + hword.getMasked() + ". Guess (char idx idx idx ...): ");

         //LIVE only:  (must create!)
         //  HangmanGuess hguess = getGuessFromUserInput();
         //Testing only...START
            HangmanGuess hguess = testGuesses[testIdx++];
            System.out.print(hguess);
         //Testing only...END

         int matchesThisGuess = hword.getMatchedCountFromGuess(hguess.chr, hguess.idxGuesses);

         System.out.println();

         if(matchesThisGuess == 0)  {
            System.out.println("No characters guessed.");
         }
      }
      System.out.println("SUCCESS: " + hword.getMasked());

   }
}

The guess test-class

class HangmanGuess  {
   public final char chr;
   public final int[] idxGuesses;
   public HangmanGuess(char chr, int... idxGuesses)  {
      this.chr = chr;
      this.idxGuesses = idxGuesses;
   }
   public String toString()  {
      StringBuilder bldr = (new StringBuilder()).append(chr).append(" ");
      for(int i : idxGuesses)  {
         bldr.append(i + " ");
      }
      return  bldr.toString();
   }
}

Full source-code

   import  java.util.Scanner;
   import  java.util.regex.Matcher;
   import  java.util.regex.Pattern;
   import  java.util.Arrays;
/**
   <P>{@code java HangmanWord onomatopoeia}</P>
 **/
public class HangmanWord  {
   private static final char MASK_CHAR = '-';
   private final String word;
   private final char[] maskedWordChars;
   private int totalMatched;
   public HangmanWord(String alphaNum_secretWord)  {
      int wordLen = -1;
      try  {
         wordLen = alphaNum_secretWord.length();
      }  catch(NullPointerException npx)  {
         throw  new NullPointerException("alphaNum_secretWord");
      }

      if(wordLen == 0)  {
         throw  new IllegalArgumentException("alphaNum_secretWord is non-null, but empty.");
      }

      //default is automatically false
      maskedWordChars = new char[wordLen];
      Arrays.fill(maskedWordChars, MASK_CHAR);
      totalMatched = 0;

      if(!Pattern.compile("^\\w+").matcher(alphaNum_secretWord).matches())  {
         throw  new IllegalArgumentException("alphaNum_secretWord (\"" + alphaNum_secretWord + "\") must contain only letters and digits.");
      }
      word = alphaNum_secretWord;
   }
   public int length()  {
      return  maskedWordChars.length;
   }
   public int getTotalMatchedCount()  {
      return  totalMatched;
   }
   public boolean didWin()  {
      return  (getTotalMatchedCount() == length());
   }
   public int getMatchedCountFromGuess(char guessChar, int... idxGuesses)  {
      if(idxGuesses.length == 0)  {
         throw  new IllegalArgumentException("idxGuesses.length is zero.");
      }
      int guessedCount = 0;
      for(int idx : idxGuesses)  {
         try  {
            if(maskedWordChars[idx] == MASK_CHAR  &&  word.charAt(idx) == guessChar)  {
               maskedWordChars[idx] = guessChar;
               guessedCount++;
            }
         }  catch(ArrayIndexOutOfBoundsException abx)  {
            throw  new IllegalArgumentException("Index " + idx + " is invalid, given length() is " + length(), abx);
         }
      }
      totalMatched += guessedCount;
      return  guessedCount;
   }
   public String getMasked()  {
      return  (new String(maskedWordChars));
   }

   public static final void main(String[] idx0_alphaNumSecretWord)  {
      HangmanWord hword = null;
      try  {
         hword = new HangmanWord(idx0_alphaNumSecretWord[0]);
      }  catch(ArrayIndexOutOfBoundsException abx)  {
         throw  new IllegalArgumentException("Missing one required parameter: The alpha-numeric secret word.");
      }

      //TESTING only
         //#=matches in guess
         HangmanGuess[] testGuesses = new HangmanGuess[]{ //012345678901
            new HangmanGuess('t', 2, 3, 4),               //onomatopoeia
            new HangmanGuess('e', 6, 7, 8, 9), //1
            new HangmanGuess('a', 6, 7, 8),
            new HangmanGuess('a', 4),          //1
            new HangmanGuess('o', 0, 3),       //1
            new HangmanGuess('o', 0, 2, 8 ,6), //3
            new HangmanGuess('p', 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11),  //1
            new HangmanGuess('n', 1),          //1
            new HangmanGuess('t', 4, 5),       //1
            new HangmanGuess('z', 0),
            new HangmanGuess('m', 3, 4),       //1
            new HangmanGuess('i', 10),         //1
            new HangmanGuess('a', 11)          //1
         };
         int testIdx = 0;

      while(!hword.didWin())  {
         System.out.print("The secret word is: " + hword.getMasked() + ". Guess (char idx idx idx ...): ");

         //LIVE only:  (must create!)
         //  HangmanGuess hguess = getGuessFromUserInput();
         //Testing only...START
            HangmanGuess hguess = testGuesses[testIdx++];
            System.out.print(hguess);
         //Testing only...END

         int matchesThisGuess = hword.getMatchedCountFromGuess(hguess.chr, hguess.idxGuesses);

         System.out.println();

         if(matchesThisGuess == 0)  {
            System.out.println("No characters guessed.");
         }
      }
      System.out.println("SUCCESS: " + hword.getMasked());

   }
}
class HangmanGuess  {
   public final char chr;
   public final int[] idxGuesses;
   public HangmanGuess(char chr, int... idxGuesses)  {
      this.chr = chr;
      this.idxGuesses = idxGuesses;
   }
   public String toString()  {
      StringBuilder bldr = (new StringBuilder()).append(chr).append(" ");
      for(int i : idxGuesses)  {
         bldr.append(i + " ");
      }
      return  bldr.toString();
   }
}
Advertisements

One thought on “Implementing a text-based Hangman game in Java

  1. Pingback: Hangman in Java — Post education version | aliteralmind — Computer Programming Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s