Assignment 12

due Tuesday 3 May at 01:00 AM

‘Hangman’ is a word-guessing game. The computer will choose a word from a list, and the player must guess one letter a time. If the letter you guess occurs in the word, then all occurrences are revealed. If the letter does not appear, then one piece of the hangman is drawn. If all six pieces are drawn before you guess the entire word, you lose.

This is a somewhat complex program; it uses arrays, loops, if, switch, and functions, and so it brings together everything we have learned. I suggest proceeding according to the steps below, and getting each to work in turn.

1 Drawing the figure

Write a function

void draw_figurine(int tries_remaining);

that will draw the hangman figure. Of course, the figure is slightly different depending on the value of tries_remaining, which could range from zero (complete figure) up to six (just the scaffolding).

To do this, I recommend a switch on tries_remaining, and then in each case, you just write the printf statements that will generate the correct figure. Remember that if you want to use a backslash character (used for the noose and one leg), you must double it up in the string:

printf("| \\\n");  // prints: BAR SPACE BACKSLASH NEWLINE

You can test your function all by itself using a main program like this:

const int MAX_TRIES = 6;
int main()
{
    int i;
    for(i = MAX_TRIES; i >= 0; i--)
    {
        draw_figurine(i);
    }
}

The output should be as follows.

 _
| \
|_____
 _
| \
|  o
|_____
 _
| \
|  o
|_____
 _
| \
|  o
| -|
|_____
 _
| \
|  o
| -|-
|_____
 _
| \
|  o
| -|-
| /
|_____
 _
| \
|  o
| -|-
| / \
|_____

2 Revealing letters

This is the function responsible for revealing a guessed letter within the secret word. It should return true if the letter is found, or false if it is not in the word.

bool reveal_letter(char guess, const char* word, char buffer[]);

This function takes three parameters:

  • guess is a single character that the user has guessed
  • word is a string (character array) containing the complete secret word. We haven't discussed this const char* type. For now, it's similar to an array of characters, except that we cannot modify them.
  • buffer is a character array that we can modify. It will be the same size as word, but it contains the underscore character in any locations that have not been revealed yet.

Below is a table with some examples of how the function should behave. In example 1, the secret word is “mississippi”. It appears from the buffer that ‘p’ has already been guessed. The user's guess (at this time) is ‘s’. So the function will modify the buffer to reveal the occurrences of ‘s’. It found some, so it well return true.

exampleguesswordinitial buffermodified bufferreturn value
1's'"mississippi""________pp_""__ss_ss_pp_"true
2'd'"android""_____i_""__d__id"true
3'e'"android""__d__id""__d__id"false
4'm'"smile""sm__e""sm__e"true

In example 3, the secret word is “android”. The user guesses ‘e’, which does not appear in the word. So the buffer is not modified and the function should return false. In example 4, the user guess was a character that appears to have been guessed already. Don't worry about that; just reveal the ‘m’ in the buffer (again) and return true. We'll check for duplicate guesses somewhere else.

The basic strategy here is a loop through the word. We can keep iterating until we hit the null character:

for(i = 0; word[i] != '\0'; i++)

In this loop, whenever you encounter the character guess in the word, set the element at the same position of buffer to the character guess. You may want to keep a boolean variable found to keep track of whether you have done this, so at the end of the loop we can return found.

Here is a way you can test this function in isolation:

int main()
{
    const char* secret = "mississippi";
    char revealed[]    = "___________";
    bool ok;
    ok = reveal_letter('p', secret, revealed);
    printf("%d %s\n", ok, revealed);        // 1 ________pp_
    ok = reveal_letter('s', secret, revealed);
    printf("%d %s\n", ok, revealed);        // 1 __ss_ss_pp_
    ok = reveal_letter('e', secret, revealed);
    printf("%d %s\n", ok, revealed);        // 0 __ss_ss_pp_
    ok = reveal_letter('i', secret, revealed);
    printf("%d %s\n", ok, revealed);        // 1 _ississippi
}

3 Tracking all guesses

It's convenient for the player to be able to see what letters have already been guessed. To do this, we'll use an array of 26 booleans:

const int ALPHA_SIZE = 26;
bool guesses[ALPHA_SIZE] = { false };

All 26 elements are set to false, initially. Then, if a user guesses an ‘a’, we set guesses[0] to true. The element at guesses[1] will represent ‘b’ and guesses[2] for ‘c’, and so on.

To convert a character c to an index, we subtract the character 'a' from it, like this:

guesses[c-'a']

I'll provide you with the get_user_char function, which asks the user for their guess, validates the response, and updates the guesses array.

char get_user_char(bool guesses[])
{
    printf("Your guess: ");
    char c;
    scanf("%c", &c);            // Read user's guess
    getchar();                  // Ignore the newline
    while(c < 'a' || c > 'z' || guesses[c-'a'])
    {
        if(c < 'a' || c > 'z') {
            printf("Error: must be a-z\n");
        }
        else {
            printf("You already guessed that!\n");
        }
        printf("Your guess: ");
        scanf("%c", &c);        // Read user's guess
        getchar();              // Ignore the newline
    }
    guesses[c-'a'] = true;      // Remember this guess
    return c;
}

There's no additional code to write in this section.

4 Displaying the game state

Each time the user makes a move, we show

  1. The hangman figure
  2. The number of tries remaining
  3. The guesses made so far
  4. The partially-revealed word.

Updating this display is the job of this function: void showgamestate(int tries, bool guesses[], char buffer[]); It can call draw_figurine from above to do part 1. The most difficult is part 3, the guesses made so far. The guesses parameter is the array described in the previous section. You have to loop through that array, and wherever the element is true, we print out the corresponding letter. Just like we subtracted 'a' to convert from a character to an index, now we can add 'a' to the index to get back a letter.

5 Have we won yet?

The final function will determine whether the user has guessed all the necessary letters of the secret word:

bool finished(char buffer[]);

It should return false if any of the elements of buffer are underscore characters, or true if they are all letters. You are not given the size of the buffer, but it will be terminated with a null character just like a regular string. So you can loop as long as:

buffer[i] != '\0'

6 Putting it all together

I decided to provide you with the main program that binds all these pieces together.

int main()
{
    const int MAX_WORD_LENGTH = 50;
    const int NUM_WORDS = 20;
    const char* wordList[NUM_WORDS] = {
        "array", "boolean", "character", "double",
        "element", "float", "graphics", "header",
        "integer", "javascript", "kindle", "long",
        "module", "network", "ordinal", "pointer",
        "queue", "register", "string", "tree"
    };
    char buffer[MAX_WORD_LENGTH];
    bool guesses[ALPHA_SIZE] = { false };
    int tries = MAX_TRIES;
    const char* word;
    int i;
    srand(time(NULL));          // Initialize random numbers
    // Choose a random word.
    word = wordList[rand() % NUM_WORDS];
    // Fill the buffer with underscores
    for(i = 0; word[i]; i++)
    {
        buffer[i] = '_';
    }
    buffer[i] = '\0';
    // Output initial game state
    show_game_state(tries, guesses, buffer);
    // Keep playing until user wins or no tries remain.
    while(tries > 0 && !finished(buffer))
    {
        char guess = get_user_char(guesses);
        bool ok = reveal_letter(guess, word, buffer);
        if(!ok) tries--;
        show_game_state(tries, guesses, buffer);
    }
    // Game over. Print the results.
    if(finished(buffer)) {
        printf("You won!\n");
    }
    else {
        draw_figurine(tries);
        printf("You lose!\n");
        printf("The word was: %s\n", word);
    }
    return 0;
}

7 Sample runs

7.1 First run

 _    
| \   
|      
| 
|      
|_____
Tries remaining: 6
  _  _  _  _  _  _  _  _  _  _
Your guess: n
 _    
| \   
|  o  
| 
| 
|_____
Tries remaining: 5
You have guessed: n 
  _  _  _  _  _  _  _  _  _  _
Your guess: e
 _    
| \   
|  o  
|  | 
| 
|_____
Tries remaining: 4
You have guessed: e n 
  _  _  _  _  _  _  _  _  _  _
Your guess: a
 _    
| \   
|  o  
|  | 
| 
|_____
Tries remaining: 4
You have guessed: a e n 
  _  a  _  a  _  _  _  _  _  _
Your guess: i
 _    
| \   
|  o  
|  | 
| 
|_____
Tries remaining: 4
You have guessed: a e i n 
  _  a  _  a  _  _  _  i  _  _
Your guess: u
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: a e i n u 
  _  a  _  a  _  _  _  i  _  _
Your guess: r
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: a e i n r u 
  _  a  _  a  _  _  r  i  _  _
Your guess: s
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: a e i n r s u 
  _  a  _  a  s  _  r  i  _  _
Your guess: t
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: a e i n r s t u 
  _  a  _  a  s  _  r  i  _  t
Your guess: m
 _    
| \   
|  o  
| -|- 
| 
|_____
Tries remaining: 2
You have guessed: a e i m n r s t u 
  _  a  _  a  s  _  r  i  _  t
Your guess: g
 _    
| \   
|  o  
| -|- 
| /   
|_____
Tries remaining: 1
You have guessed: a e g i m n r s t u 
  _  a  _  a  s  _  r  i  _  t
Your guess: p
 _    
| \   
|  o  
| -|- 
| /   
|_____
Tries remaining: 1
You have guessed: a e g i m n p r s t u 
  _  a  _  a  s  _  r  i  p  t
Your guess: c
 _    
| \   
|  o  
| -|- 
| /   
|_____
Tries remaining: 1
You have guessed: a c e g i m n p r s t u 
  _  a  _  a  s  c  r  i  p  t
Your guess: v
 _    
| \   
|  o  
| -|- 
| /   
|_____
Tries remaining: 1
You have guessed: a c e g i m n p r s t u v 
  _  a  v  a  s  c  r  i  p  t
Your guess: h
 _    
| \   
|  o  
| -|- 
| / \ 
|_____
Tries remaining: 0
You have guessed: a c e g h i m n p r s t u v 
  _  a  v  a  s  c  r  i  p  t
 _    
| \   
|  o  
| -|- 
| / \ 
|_____
You lose!
The word was: javascript

7.2 Second run

 _    
| \   
| 
| 
| 
|_____
Tries remaining: 6
  _  _  _  _  _  _  _  _  _
Your guess: e
 _    
| \   
| 
| 
| 
|_____
Tries remaining: 6
  _  _  _  _  _  _  _  e  _
Your guess: i
 _    
| \   
|  o  
| 
| 
|_____
Tries remaining: 5
You have guessed: e i 
  _  _  _  _  _  _  _  e  _
Your guess: o
 _    
| \   
|  o  
|  | 
| 
|_____
Tries remaining: 4
You have guessed: e i o 
  _  _  _  _  _  _  _  e  _
Your guess: u
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: e i o u 
  _  _  _  _  _  _  _  e  _
Your guess: a
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: a e i o u 
  _  _  a  _  a  _  _  e  _
Your guess: r
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: a e i o r u 
  _  _  a  r  a  _  _  e  r
Your guess: c
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: a c e i o r u 
  c  _  a  r  a  c  _  e  r
Your guess: h
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: a c e h i o r u 
  c  h  a  r  a  c  _  e  r
Your guess: t
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: a c e h i o r t u 
  c  h  a  r  a  c  t  e  r
You won!

7.3 Third run

 _    
| \   
| 
| 
| 
|_____
Tries remaining: 6
  _  _  _  _
Your guess: 8
Error: must be a-z
Your guess: *
Error: must be a-z
Your guess: c
 _    
| \   
|  o  
| 
| 
|_____
Tries remaining: 5
You have guessed: c 
  _  _  _  _
Your guess: n
 _    
| \   
|  o  
| 
| 
|_____
Tries remaining: 5
You have guessed: c n 
  _  _  n  _
Your guess: t
 _    
| \   
|  o  
|  | 
| 
|_____
Tries remaining: 4
You have guessed: c n t 
  _  _  n  _
Your guess: e
 _    
| \   
|  o  
| -| 
| 
|_____
Tries remaining: 3
You have guessed: c e n t 
  _  _  n  _
Your guess: c
You already guessed that!
Your guess: n
You already guessed that!
Your guess: e
You already guessed that!
Your guess: h
 _    
| \   
|  o  
| -|- 
| 
|_____
Tries remaining: 2
You have guessed: c e h n t 
  _  _  n  _
Your guess: g
 _    
| \   
|  o  
| -|- 
| 
|_____
Tries remaining: 2
You have guessed: c e g h n t 
  _  _  n  g
Your guess: l
 _    
| \   
|  o  
| -|- 
| 
|_____
Tries remaining: 2
You have guessed: c e g h l n t 
  l  _  n  g
Your guess: i
 _    
| \   
|  o  
| -|- 
| /   
|_____
Tries remaining: 1
You have guessed: c e g h i l n t 
  l  _  n  g
Your guess: o
 _    
| \   
|  o  
| -|- 
| /   
|_____
Tries remaining: 1
You have guessed: c e g h i l n o t 
  l  o  n  g
You won!