Trying to implement text-based Hangman game in Java

Solution 1:

Here's a brief amount of code that is a complete hangman program:

static int MAX_MISSES = 5;
static String[] WORDS = { "QUICK", "BROWN", "JUMPS" }; // etc

public static void main(String[] args) throws Exception {
    String word = WORDS[new Random().nextInt(WORDS.length)], guesses = " ";
    int misses = -1;
    for (Scanner in = new Scanner(System.in); !word.matches("[" + guesses + "]+") & (misses += word.contains(guesses.substring(0, 1)) ? 0 : 1) <= MAX_MISSES; guesses = in.nextLine().toUpperCase().charAt(0) + guesses)
        System.out.println(word.replaceAll("(?<=.)", " ").replaceAll("[^" + guesses + "]", "_"));
    System.out.println(word + (misses > MAX_MISSES ? " not" : "") + " solved with " + misses + " incorrect guesses");
}

Some notes:

  • Regex is used to both check for solution and display the current guess status
  • & is used instead of && in the loop termination to force both sides of the logic to be executed - thus always updating the misses correctly
  • Various other tricks have been used to deliberately increase code density

There is no error checking for input. If more than one letter is input by the user, only the first is used. If no letters are input, it will explode. Error checking could be added without adding much more code, but the for line would be a whopper - I leave that as an exercise for the reader.

Also, there is no checking for erroneously guessing the same letter multiple times. With this code, if you guess a correct letter twice, nothing bad happens, but if you guess a wrong letter twice, it counts as a separate "miss", so you'll burn one of your chances.

Solution 2:

My other answer

I had a little too much fun with this.

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:

   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));
   }

And demo usage, which takes advantage of the below HangmanGuess class.

Note that this does not accept user-input. Hence the above guess 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());

   }
}

Guess 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();
   }
}

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

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();
   }
}