Hangman Tutorial Lesson 4 – Hiding the Letters

Now that we have our app displaying a word at random, it would be nice to hide the letters and replace each letter with a “_”. However, there are some special letters that we don’t want to have this behavior. For spaces (and probably any punctuation), the player shouldn’t have to guess these letters, but they should begin as revealed. Because it will be more complicated than just displaying the letters, it would be good to create a class that encompasses this logic. Let’s create a HangmanLetter class first, and then create a HangmanPhrase class later that will be a collection of HangmanLetters.

Create a class called HangmanLetter and put it in com.famlinkup.hangman package. Here is what it’s going to look like:

package com.famlinkup.hangman;

public class HangmanLetter
{
    private static final String UNREVEALED_MARKER = "_";
    private static final String[] AUTOMATICALLY_REVEALED_LETTERS = { " ", ",","-" };
    private String letter;
    private boolean revealed = false;

    public HangmanLetter(String letter)
    {
        this.letter = letter.toUpperCase();
        for (String s : AUTOMATICALLY_REVEALED_LETTERS)
        {
            if (s.equals(letter))
            {
                revealed = true;
                break;
            }
        }
    }

    public String getDisplayString()
    {
        if (letter.equals(" "))
            return "   ";
        else
            return revealed ? letter : UNREVEALED_MARKER;
    }

    public boolean isRevealed()
    {
        return revealed;
    }

    public void reveal()
    {
        revealed = true;
    }

    public String getLetter()
    {
        return letter;
    }
}

We have a few constant variables. UNREVEALED_MARKER just specifies what letter we want to display to the user when the letter hasn’t been guessed yet. AUTOMATICALLY_REVEALED_LETTERS in an array of letters that we are going to automatically reveal when the game begins. You can adjust this to letters you care about, but I decided that space, comma, and dash should always be revealed by default.

A HangmanLetter needs to keep track of two things: what letter it represents, and whether it has been revealed yet. You could choose to use a char to represent the letter, but I chose a String to make all the methods that interact with it easier (so I don’t have to keep converting from char to String).

In the constructor, we change the letter to uppercase. Then we see if it’s one of our letters we want to automatically reveal. If it is, we reveal it.

We have some basic getter methods for both the revealed variable and the letter variable, isRevealed() and getLetter(). The getDisplayString() method returns what the letter would look like to the user, either an underscore or the actual letter. Also, to help out with the visual separation between words, if we encounter a space, we want to treat it as 3 spaces. Lastly, we have a method to reveal the letter.

That’s it for HangmanLetter. Now on to HangmanPhrase:

package com.famlinkup.hangman;

import java.util.ArrayList;
import java.util.List;

public class HangmanPhrase
{
    private List<HangmanLetter> letters;

    public HangmanPhrase(String phrase)
    {
        this.letters = new ArrayList();
        for (int i = 0; i < phrase.length(); i++)
            letters.add(new HangmanLetter(String.valueOf(phrase.charAt(i))));
    }

    /**
     * Returns false if you guessed wrong
     */
    public boolean guessLetter(String guessedLetter)
    {
        boolean hasMatch = false;
        for (HangmanLetter hangmanLetter : letters)
        {
            if (!hangmanLetter.isRevealed() && hangmanLetter.getLetter().equalsIgnoreCase(guessedLetter))
            {
                hangmanLetter.reveal();
                hasMatch = true;
            }
        }
        return hasMatch;
    }

    public boolean isAllRevealed()
    {
        for (HangmanLetter hangmanLetter : letters)
            if (!hangmanLetter.isRevealed())
                return false;

        return true;
    }

    public void revealAll()
    {
        for (HangmanLetter hangmanLetter : letters)
            hangmanLetter.reveal();
    }

    public String getDisplayString()
    {
        StringBuilder sb = new StringBuilder();
        for (HangmanLetter letter : letters)
        {
            sb.append(letter.getDisplayString()).append("\u00A0"); // no breaking space
        }
        return sb.toString();
    }
}

A HangmanPhrase is just a collection of HangmanLetters, so we create a member variable “letters” to represent that. Besides that, we just have a bunch of convenience methods to track the state of the phrase.

The constructor takes a String, and iterates over each letter in the phrase and creates a HangmanLetter from it.

The next method, guessLetter(), allows you to guess a letter, and update the phrase to reflect this guess. We loop over each HangmanLetter and if it matches the guessedLetter, we reveal the letter. We also track if we found any match, and if not we return false.

The isAllRevealed() method just loops over every HangmanLetter and sees if all of them have been revealed. This will be useful to determine if the player has finished the game.

Next is revealAll() method, which sets all the HangmanLetters to be revealed. This will be used if they run out of guesses, so they can see what the phrase is when they lose.

The last method does all the magic: getDisplayString(). This method is in charge of displaying the phrase to the user, with the letters blanked out (with an underscore) if they haven’t guessed that letter yet. One interesting thing to mention is that we append a special unicode character 00A0 (which is a non breaking space) after each letter. We want the user to be able to see how many blank letters there are, so we want to separate the underscores so it doesn’t look like one big long line. However, if we use a space to break it up, then when the word needs to wrap, it can wrap in the middle of the word. So we want a space, but we don’t want to break on it, hence the non breaking space. To see what I mean, you can replace this with just a regular space, and then try it out with really long words to see how it wraps it.

Alright, those are the 2 classes we need to correctly display the phrase, so let’s connect it into our code now. The easiest way to plug this into our code is to change the PhraseGenerator. Let’s have PhraseGenerator.nextPhrase() return a HangmanPhrase instead of a String:

public HangmanPhrase nextPhrase()
{
    return new HangmanPhrase(lines.get(random.nextInt(lines.size())));
}

This change breaks MainActivity, so let’s update the onCreateMethod to call the correct method now:

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    attachNextButton();

    phraseGenerator = new PhraseGenerator(this);
    TextView txtPhrase = (TextView)findViewById(R.id.txtPhrase);
    txtPhrase.setText(phraseGenerator.nextPhrase().getDisplayString());
}

Then also update newGame() to do the same thing:

private void newGame()
{
    TextView txtPhrase = (TextView)findViewById(R.id.txtPhrase);
    txtPhrase.setText(phraseGenerator.nextPhrase().getDisplayString());
}

Alright, let’s deploy the code and see what it looks like now. You should see something like this:

device-2014-03-27-220709

 

We now have the Hangman phrase displaying correctly. In our next lesson, we’ll work on allowing the player to guess letters.

Advertisements

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