Challenge 67

 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.

This program will replicate some or all of the features of the Wordle game. The classic web-based game provides different color codes throughout the game’s progress. Since this program is being implemented in a text-based terminal, the colors are replaced by text annotations (explained in Milestone 2)

Requirements

Acquire the file called enable1.txt. This file contains more than 172K words that is typically used in various word games.

Milestone 1

      1. Include the following libraries: iostream, fstream, cstring, cstdlib and ctime. These are the minimum headers you will need. If you later find you need more, you can simply include those as well
      2. Create a class called Words
      3. In Words,
        1. Create two private int variables count, wordlen.
        2. Create a string pointer called matches
        3. Create an overloaded constructor to set count to 0, and matches to NULL. This constructor will pass a single int parameter that will be used to set the wordlen data member.
        4. Create a public function int count_matches(). This function will open the file called enable1.txt, read all the words from the file, and increment the count data member for each word whose length equals wordlen. This function will return count. Don’t forget what you need to do for count before incrementing.
        5. Create a private function void create_array(). This function will  first delete matches if matches is not NULL. Then, store the result of calling count_matches() into a variable called size. Use the size variable in dynamically creating a string array that will be assigned to matches. The overall goal of this function is to create and prepare a string array that can exactly store all the words with matching lengths.
        6. Create a function void get_words(). This function will first call create_array() — this ensures that the array is created and ready. Then, again open the file enable1.txt, and find all the words whose lengths are equal to wordlen. As each matching word is found, store it in the matches array, eventually filling up the entire matches array. Update the constructor by adding a call to get_words().
        7. Create a public getter function called get_count() that returns the value of count.
        8. Create a public function bool is_valid(string word). This function will search the entire matches array for the existence of word. If the word is found, return true. Otherwise, return false. 
        9. Create a destructor to delete matches. Remember that matches is a pointer that points to an array.
        10. Create a public function string pick_word(). This function will return a blank string if count is 0. Otherwise, it will return a random element out of the matches array. The built-in rand() function can be used as below to generate a random index
          int x = rand() % count;  // Use x as an index into matches[]
      4. For this milestone, use the sample main() given below. Understand that this main() function is only temporary and will only be used to test your Words class. You should also be able to get an idea of how the Words class can be used.

Sample main() for Milestone 1

#include <iostream>
#include <fstream>
#include <cstring>
#include <ctime>
#include <cstdlib>

int main()
{
  // this ensures randomization of words
  srand(time(NULL));  

  int wordlen;
  cout << "Enter the word length: ";
  cin >> wordlen;

  Words gamewords(wordlen);  // create an object of the class

  cout << "Number of words of length " << wordlen << ": " << gamewords.get_count() << endl;

  string word;
  cout << "Check a word: ";
  cin >> word;

  if (gamewords.is_valid(word))
    cout << "Found " << word << endl;
  else
    cout << "Not found" << endl;

  cout << "Random word: " << gamewords.pick_word() << endl;

  return 0;
}

Sample Interaction for Milestone 1

You should try different values for the word length. Also, check random words to see if they are in the list

[Run your program]
Enter the word length: 5
Number of words of length 5 : 8636
Check a word: tacos
Found tacos
Random word: baker

Milestone 2

  1. Create a class called Wordle
  2. In Wordle, create protected variables: char target[40], char lastguess[40], string evals[10], string guesses[10], int attempts, int max_attempts, bool onlyvalidtarget will contain the ‘secret’ word the player will attempt to guess. lastguess was the player’s last guess. guesses is an array that keeps track of each attempted guess. evals is an array that keeps track of how each letter in a guess is measures up to the word in target. See Step 4.
  3. Write a private function bool is_char_in_word(string word, char c). This function returns true if c is found in word. Otherwise, it returns false.
  4. Write a private function void evaluate_guess(char result[], const char guess[]). This function will:
    1. Copy guess to result
    2. Set each of the characters of result to a blank (‘ ‘). Remember that result is a C-string
    3. Using a loop of some sort, evaluate each character of guess and compare it to a character in the equivalent position of target. If the letters from both variables don’t match, then also compare if the character in guess is in target. If it is in target, then set result[i] to ‘*’. Otherwise, set it to a ‘!’. Remember that you wrote the function is_char_in_word() -what does this function do? 
    4. Create an overloaded constructor as below
        Wordle(string secret, int ma, bool ov)
        {
          attempts = 0;
          strcpy(target, secret.c_str());
          onlyvalid = ov;
          max_attempts = ma;
        }
      
    5. Write a public function bool guessed_it(). This function compares lastguess and target. If they are equal, return true. Otherwise, return false.
    6. Write a public function bool game_over(). This function will return true if attempts has reached or exceeded max_attempts, or if the user has guessed the word (See function in Step 5). Otherwise, it returns false. 
    7. Write a getter and a setter function for onlyvalid data member
    8. Write a setter for max_attempts data member
    9. Write a function bool submit_guess(string guess). This function will:
      1. Return true if attempts is less than max_attempts. Additionally, it will:
        1. Create a local variable char eval[40]
        2. Copy guess into lastguess. Remember that guess parameter is a string. It will need to be converted using its .c_str() member function
        3. Add guess into the guesses array
        4. Call the evaluate_guess() function passing in eval and guess. Basically, the string guess will be evaluated against target, and its resulting evaluation string will be stored in eval.
        5. Add eval into the evals array. Even though eval is a C-string, it can be stored in evals (which is a string array) via a simple assignment.
        6. Increment attempts.
      2. Otherwise, it will return false.
    10. Create a new class, WordleConsole that publicly inherits from Wordle. In class WordleConsole,
      1. Create a private data member bool showlegend
      2. Create a setter function for showlegend property
      3. Create a private function string displayed_guess(int guessindex). This function will return a string that represents the user’s guess along with each letter’s evaluation.
        // This function returns a string that 
        // combines guesses[i] with evals[i]
        
          string displayed_guess(int guessindex)
          {
            string result = "";
            string guess = guesses[guessindex];
            string eval = evals[guessindex];
            for (int i = 0; i < strlen(target); i++)
            {
              result = result + guess[i] + eval[i] + " ";
            }
            return result;
          }
        
    11. Create an overloaded constructor WordleConsole(string secret, int max_attempts, bool onlyvalid, bool legend = true). This constructor will set showlegend in its own function body, but will call its parent constructor via an initializer list passing in the appropriate parameters to the parent’s constructor.
    12. Create a function to overload the << operator. In this function, display a game legend (information to the user) if showlegend is true. The game legend could simply be text as below:
      GAME PLAY LEGEND
        * Letter is in the wrong spot
        ! Letter is not in the word
      

      Also, use the attempts property in a loop calling displayed_guess(i) through each iteration of the loop. This loop will display the game progress to the user.

    13. Create a global (non-class) function int get_wordlength(int min, int max). This function will prompt the user to enter a usable word length. This user’s number must be between min and max, and will display an error to the user if the entered number is outside of this range. This function will return a number that is valid within the range.
    14. See the Sample Main below on how the WordleConsole class can be implemented to run a round of the game. This main() will be changed again for Milestone 3.

Sample main() for Milestone 2

int main()
{
    srand(time(NULL)); 
    int wordlength;
    bool allowsubmit;
    int max_attempts = 6;
    bool onlyvalid = true;

    cout << "WORDLE is preparing word list" << endl;
    wordlength = get_wordlength(4, 8);

    Words gamewords(wordlength);
    string targetword = gamewords.pick_word();

    string guess;
    WordleConsole game(targetword, max_attempts, onlyvalid);
    cout << game << endl;

    while (!game.game_over())
    {
      cout << "Enter guess: ";
      cin >> guess;
      
      if (game.get_onlyvalid())
        allowsubmit = gamewords.is_valid(guess);
      else
        allowsubmit = true;

      if (allowsubmit)
      {
        if (guess.length() != targetword.length())
          cout << guess << " doesn't have enough characters" << endl;
        else
          game.submit_guess(guess);
      }  
      else
        cout << guess << " is not a valid word" << endl;

      cout << game << endl << endl;
    }

    cout << "GAME OVER -- YOU WON/LOST"; // display if the user won or lost
    cout << "The correct word was " << targetword << endl;
    return 0;
}

Milestone 3

  1. Add a public function to the Words class (from Milestone 1) as below:
      // This function could be useful, depending
      // on how you write your main() 
      void reload(int wl)
      {
        wordlen = wl;
        count = 0;
        get_words();
      }
    
  2. Display a menu to the user similar to below. To implement two-player mode below, one player will be prompted to provide the word and the second player will guess the word. In this mode, you will not need to use the Words class to generate a random word, as the word will be provided by one player. Since Player 1 can provide any word, you might also consider turning off the validation of the word and allowing the user to type in anything as the target/secret word:
      Let's Play WORDLE
      -----------------
       c - Classic Wordle (6 attempts)
       e - Easy Mode (10 attempts)
       h - Hard Mode (4 attempts)
       l - Toggle Displaying Legend
       2 - Two-Player Mode
       Q - Quit
    
  3. The idea in this milestone is to allow the user to play Wordle a few different ways, as well as provide some options to change the game behavior. Remember that several setters and getters have been written for the Wordle and the WordleConsole classes. Those functions can be helpful here.
  4. You are free to write main() in any manner you choose, implementing some of the ideas presented in main() from Milestone 2. You can also introduce other game features, as long as the basic menu is implemented as above in Step 2.
  5. Feel free to create as many additional variables and or global functions.
  6. In two-player mode, you should clear the screen so that Player 2 doesn’t know the secret word, by adding this line of code to clear the screen system(“clear”)

Takeaways

  1. Classes Words and Wordle have no relationship. Wordle 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. Wordle 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 how the game’s progress is displayed to the user. The Wordle class is the engine of the game.
  3. WordleConsole inheriting from Wordle gives it the playability of the game but also provides the output to make the game visibly playable from the console. In theory, you can create other child classes WordleGUI, WordleIOS, WordleAndroid which will provide the functionality needed for those various platforms. Keeping the guts of the gameplay in the parent Wordle allows it to be usable in various implementations.
  4. The final main() for Milestone 3 allows the user to play the game in various ways. This implementation is separated from the Wordle game engine. To add more functionality or more features, the Wordle and WordleConsole classes simply have to provide more private/protected properties and public functions.

LEGEND
PROGRAM OUTPUT
USER INPUT
FROM INPUT

CATALOG ID: CPP-CHAL0067

Print Requirements