Challenge 29

Description

The purpose of this challenge is to write an all-encompassing program which will include various flow control structures, loops, file access, functions and object-oriented concepts.

Requirements

Acquire the file called enable1.txt. This file contains all the words that are considered valid in Words With Friends (in earlier versions)

Milestone 1

      1. Create a class called Words.
      2. In Words,
        1. Create two private int variables minlen, maxlen. Create an overloaded constructor to set these variables.
        2. Create a private int variable called count
        3. Create a private pointer to a string: string * choices 
        4. Create a private function int count_candidates(). This function will open the file enable1.txt and count all the words in the file whose lengths are between minlen and maxlen. This function will return the resulting count. If you use a string to load the words from the file, you can use its public function .length() to determine the word’s length.
        5. Create a private function void load_words().
          1. In this function, create a dynamic string array of size count. Have choices point to this newly created array.
          2. This function will open the file enable1.txt again. Similar to count_candidates(), this function will go through the entire file identifying all the words whose lengths are between minlen and maxlen. Add the qualifying words into the choices array as they are identified.
        6. Create a function string pick_word(). Check if count is 0; if it is zero, return an empty string. Otherwise, return a single random string out of the choices array, making sure to use a valid array index.
        7. In the constructor, call count_candidates() and load_words().
        8. You will need to dispose of choices in a destructor. Remember that choices points to an array and must be disposed of as an array.
      3. For this milestone, use main to instantiate an instance of Words. Ask the user for two integer values as min and max. Pass these two values into your Words instance’s constructor. Also, test to see if you can display a single word by calling pick_word(). This sample main allows you to test your Word class with any values of min and max. 

Sample main() for Milestone 1

#include <iostream>
#include <ctime>
#include <cstdlib>

int main()
{
  srand(time(NULL));  // needs <ctime> included
  int min, max;

  cout << "Enter min: ";
  cin >> min;

  cout << "Enter max; ";
  cin >> max;

  Words words(min, max);

  cout << words.pick_word() << endl;

}

Sample Interaction for Milestone 1

You should try different ranges (different values for min and max). The range 27 to 29 contains 3 words only.

[Run your program]
Enter min: 27
Enter max: 29
electroencephalographically

Milestone 2

For this milestone, add the necessary code into the same source file used for Milestone 1.

You can either comment out the main from Milestone 1 or delete it completely. Another sample main() has been provided for this milestone that you can use.

      1. Create a class called Hangman beneath the Words class. In this class,
        1. Create the following private variables: char word[40], progress[40], int word_length.
          1. word will be used to store the current word
          2. progress will be a dash representation of the the current word. It will be the same length as the current word. If word is set to ‘apple’, then progress should be “‑‑‑‑‑” at the beginning of the game. As the user guesses letters, progress will be updated with the correct guesses. For example, if the user guesses ‘p’ then progress will look like “‑pp‑‑”
        2. Create a private function void clear_progress(int length). This function will set progress to contain all dashes of length length. If this function is called as clear_progress(10), this will set progress to “‑‑‑‑‑‑‑‑‑‑”. Remember that progress is a character array; don’t forget your null terminator.
        3. Create the following protected data members: int matches, char last_guess, string chars_guessed, int wrong_guesses, int remaining.
        4. Create a public function void initialize(string start). This function will initialize chars_guessed to a blank string, wrong_guesses to 0. Set remaining to 6. Use strcpy() to set word to the starting word passed as start (Because start is a string and word is a character array, you have to use start.c_str() to convert it to a character array when using the strcpy() function). Set word_length to the length of word. Call clear_progress in this function.
            strcpy(word, start.c_str());
          
        5. Create a constructor which passes in a string parameter. Call initialize() from within the constructor
        6. Create getter functions for word, progress, and remaining. Because word and progress are character arrays, their getter functions should return char *. For example,
          char * get_progress()
          {
            return progress;
          }
          
        7. Create a function bool is_word_complete(). This function calls the strcmp() function to compare if progress equals word. 
        8. Create a protected function bool check(char user_guess). This function will accept a single character (this character is what the user will guess). This function will return true if user_guess is found in word and return false if the user_guess is not in word. This function will update progress with the correct letters updating the appropriate positions with all occurrences of user_guess. If user_guess is not found in the word, do not update progress. Also, be sure to update chars_guessed (use the += operator), wrong_guesses, and remaining. Make sure you detect within chars_guessed if user_guess has previously been entered, and make sure not to penalize the user for guessing the same non-matching letter multiple times. In other words, don’t update chars_guessed, wrong_guesses and remaining for repeated user guesses of the same letter. Only update chars_guessed with the letters not found in the word.
      2. Create a class HangmanConsole which publicly inherits from Hangman.
        1. Write an overloaded constructor passing in a single string variable start. This constructor will call Hangman’s overloaded constructor in its initializer list.
        2. Write a function void show_status(int stage). This function implements a switch statement. See the start of this below – you will have to fill in the rest of the cases. The case below draws the frame of the gallows and the base for the structure. You will have to create the remaining cases to show the progress of the game drawing out the image of the game character. If you use the backslash as one of his arms or legs, note that you will have to type them in pairs. This is because the backslash starts an escape sequence and C++ expects a character there and treat it differently (think \n \r \t etc)
          switch (stage)
          {
            case 0:
              cout << " +-----\n";
              cout << " |     \n";
              cout << " |     \n";
              cout << " |     \n";
              cout << " |     \n";
              cout << " ----- \n";
              break;
          
          // draw similar stages for cases 1 - 5 here
          // as the man gets arms and legs 
          
            case 6: 
              cout << " +-----\n"; 
              cout << " |  O  \n"; 
              cout << " | /|\\ \n";
              cout << " |  |   \n"; 
              cout << " | / \\ \n"; 
              cout << " ----- \n"; 
              break; 
          }
        3. Write a function void show_info(). This function will call get_progress(), show_status() and text showing:
          1. how many matches were found on the user’s last guessed character (last_guess and matches)
          2. how many wrong guesses were made (wrong_guesses)
          3. how many attempts are remaining (remaining)
          4. What letters have been guessed so far (chars_guessed)
        4. Overload the insertion operator (>>). In this overload, the user will be prompted to enter a single character; no need to display a message for a prompt (this operator simply receives the input). See sample main() below on how it’s used. Call the check() function for the HangmanConsole object passed as one of the parameters, passing in the character entered by the user
      3.  See the sample main below for Milestone 2. This sample main will replace the one from Milestone 1.

Sample main() for Milestone 2

int main()
{
    srand(time(NULL)); // needs <ctime> included
    Words words(7,10); // words between 7 and 10 chars long
  
    HangmanConsole game(words.pick_word());  

    cout << "HANGMAN" << endl << "-------" << endl << endl;
    cout << "Your word is: " << game.get_progress() << endl;

    while (!game.is_word_complete() && game.get_remaining() > 0)
    {
       cout << endl;
       cout << "Enter your guess: ";
       cin >> game;    // calls overloaded >> operator 

       system("clear"); // NON-PORTABLE, ONLY WORKS ON LINUX
       game.show_info();
    }

    return 0;
}

Milestone 3

  1. Using main from Milestone 2, you can make the following changes; you do not need to create a new main for this milestone. Make sure you have a call to srand() as shown in the sample main above. srand() needs to be called once in main(). Make sure that there is only one srand() call in the entire program.
  2. Modify the Words class and add a public function void reset(int min, int max). This function will set the value of Words’ minlen and maxlen using min and max values from the function’s parameters. reset() will also check to see if choices is NULL; if it is not NULL, then it will delete it (similar to your destructor). Then, this function will call count_candidates() and load_words(). This function will effectively allow you to change the range of the word lengths and reload the qualifying words. Depending on how you instantiate your Words object in main(), you may or may not actually need to call the reset() function. If you repeatedly create and recreate a Words object, you won’t need to call reset(). If you use one Words object for the entire life of the program’s scope, you will need to call reset() to have it pick a different range for the words.
  3. Create a global, non-class function int show_menu(). This will show a menu to the user as below (or something similar).
      1. Play the computer (auto select random range)
      2. Play the computer (min: 7, max: 12)
      3. Play the computer (user selects/changes range)
      4. Play the computer (use same range stored in 3)
      5. Two player mode (user1 enters the word, user2 guesses)
      6. Quit
    
      Enter choice:
  4. Show the menu within a loop in main() so that the user can choose between the 5 game options, only quitting the program when option 6 is chosen
  5. Make sure that the range generated randomly in Option 1 is within the range of 1 through 40, and that your max variable contains a larger value than your min variable
  6. Create a global non-class function void get_range_num(string prompt, int min, int max, int & number). This function will prompt the user to enter an integer. If the integer entered by the user falls outside of min and max, keep prompting the user. Values within the range of min and max will be accepted and returned via the reference parameter, number.
    1. Call this function twice for Option 3; once to enter the min, and once to enter the max.
      get_range_num("Enter min: ", 2, 40, min);
      get_range_num("Enter max: ", min, 40, max);
      
    2. What happens if menu option 4 is selected before menu option 3 is called? Should it also ask for user input first?
  7. You may need to call the reset() function you created in step 1 of Milestone 3 anytime you change the range of word lengths (depending on the scope of your Words object).
  8. Realize that Option 5 of the menu above does not use the Words class or any instantiated objects of the Words class.
  9. You should clear the screen after Player 1 enters the word in Option 5 so that Player 2 doesn’t see the word as it was entered
  10. Create as many functions and variables as you need for Milestone 3. Not all of them have been specified above — you can create your own functions as you see fit. Remember, functions should be small and do simple and specific tasks.
  11. This milestone allows you to be as creative as you want in the presentation of, and the interaction with the game.

Takeaways

  1. Classes Words and Hangman have no relationship. Hangman can be used without Words – you can simply pass into its constructor any string; it doesn’t even have to be an actual English word (yes you can use gibberish or Spanish words, for example)
  2. Hangman contains all the functionality to implement the game – taking a word, comparing user guesses, keeping count of attempts, etc without implementing where the word comes from, or where the user’s guessed character comes from or how the game’s progress is displayed to the user. The Hangman class is the engine of the game.
  3. HangmanConsole inheriting from Hangman gives it the playability of the game but also provides the input and output to make the game visibly playable from the console. In theory, you can create other child classes HangmanGUI, HangmanIOS, HangmanAndroid which will provide the functionality needed for those various platforms. Keeping the guts of the gameplay in the parent Hangman allows it to be usable in various implementations.

LEGEND
PROGRAM OUTPUT
USER INPUT
FROM INPUT

CATALOG ID: CPP-CHAL0029

Print Requirements