9. Week 5: Making a Wordle Clone
≪ 8. Week 4 Thursday: Algae Simulator | Table of ContentsSince this class is rather fast-paced and we didn’t have a whole lot of time to sit with the material, I thought it would be good to take a week to implement a relatively ambitious project. This will give us a better picture of the process of programming when the programs we want to make are too large to fit into our working memories (unlike many of the problems we’ve seen thus far) in addition to encountering applications of concepts we haven’t been able to touch on very much in discussion.
We’re going to try to recreate the word game Wordle, which became very popular just a little while ago. It’s a singleplayer game: there’s a hidden 5-letter word, and you’re given 6 attempts to try and guess it. Every time you guess, letters are marked one of three ways:
- Incorrect, i.e. they don’t even appear in the target word;
- Partially correct, i.e. they appear in a different spot in the target word, or
- Correct, i.e. the letter appears in the correct spot.
For instance,
if the target word was CHIPS and I guessed BITES,
the B, T, and E would be marked incorrect,
the I would be marked partially correct,
and the S would be marked correct.
Usually this is done with colours,
but we’ll have to think of a different way to do this.
Let’s start by giving extremely broad pseudocode that describes roughly how the program will run. Here’s what I have in mind:
- Pick a random five-letter word.
- Repeat 6 times:
- Ask the user for a guess.
- Verify that the user has guessed a real 5-letter word.
- Show which letters are incorrect, partially correct, and correct.
- If all five letters are correct, the user wins!
- If the user used all six guesses and hasn’t won, then the user has lost.
Of course, there are two issues: first, how do we get random numbers? Second, how do we verify that the user has guessed a “real word”? To ameliorate these problems, I have written some starter code:
1#include <cstdlib> // needed for rand, srand
2#include <ctime> // helps with srand
3#include <iostream> // needed for cout, cin
4#include <string> // needed for string, getline
5
6using namespace std;
7
8string getWordList();
9
10int main() {
11 // this command sets up the random number generator
12 // so we can use rand() later.
13 // rand() just spits out a random nonnegative integer.
14 srand(time(0));
15
16 // this string will hold a bunch of 5-letter
17 // you'll learn about functions in the very near future.
18 // I've set this up so that the code is cleaner.
19 const string WORD_LIST = getWordList();
20
21 return 0;
22}
23
24string getWordList() {
25 /* OMITTED TO SAVE SPACE */
26}
Here’s the code file itself.
The string WORD_LIST contains 4502 common 5-letter English words,
in all lowercase letters and separated by spaces.
You can see the string itself in the starter code, more or less
(it just takes up a lot of space).
We’ll now write in the pseudocode into the comments,
marking which steps still need to be filled in.
Some editors even highlight these TODO’s
1// 1. Pick a random five-letter word. TODO
2
3// 2. Repeat 6 times... TODO
4// * Ask the user for a guess TODO
5// * Verify their guess TODO
6// * Show which letters are TODO
7// incorrect, partially correct,
8// and/or correct.
9// * If all five letters are TODO
10// correct, the user wins.
11
12// 3. If all six guesses are used up, TODO
13// the user loses.
Build and run the code to make sure nothing got screwed up! Nothing should happen.
The big idea we’re going to apply is called incremental development. We’ll flesh out and code up one small step at a time, ensuring that our code works properly at each stage as we go. If we run into any issues along the way, we may need to adjust our pseudocode slightly, and that’s okay! In catastrophic situations, we may have to start from scratch, but I think this project isn’t at that kind of scale.
Let’s start with the random selection of a word.
We’ll use the function rand(),
which generates a random nonnegative integer for us.
Problem 1.
Fill in step 1 of the pseudocode:
1// 1. Pick a random five-letter word. TODO
2string word;
3// YOUR CODE GOES HERE...
4cout << word << endl;
The above should print a random 5-letter word every time you run the program.
Solution
The first word in WORD_LIST
can be obtained by running WORD_LIST.substr(0, 5);.
Likewise, the second word can be obtained
with the command WORD_LIST.substr(6, 5);.
Beware the spaces!
In general,
the nth word in WORD_LIST will be given
by the command WORD_LIST.substr(6 * n - 6, 5);.
Note the off-by-one indexing scheme coming in…
So, we just need to pick a number n
between 1 and the number of words in the list randomly
and feed it into the formula.
The number of words in WORD_LIST
is given by WORD_LIST.length() / 6.
Let’s put this in a variable:
1int numWords = WORD_LIST.length() / 6;
Then rand() % numWords will be between 0 and numWords-1,
inclusive, so we can set
Problem 2.
Write up a for loop to wrap around step 2,
a short snippet to prompt the user for input,
and fill in step 3.
Hopefully these three steps were easy to knock out. You have some freedom in how you want to treat the user if they end up failing to guess the hidden word…
Let’s build and run our program again. It’s not going to do much! We’ll be able to enter whatever we want 6 times, then the game will insult our word-guessing ability. But soon it will ripen…
The remaining three steps constitute the logical meat of the program. I personally like to approach them in the order of increasing difficulty. So let’s start by checking if the user has guessed the word.
Our code should say: “If the user’s input and the hidden word are equal, congratulate the user and end the program.” The if statement and printing out a congratulatory statement should be pretty straightforward by now. But how do we “end the program”? There are two ways forward:
- We can use the
breakcommand to exit the loop. However, this will cause step 3 to be run, insulting the user right after congratulating them. - We can write the code
return 0;, which immediately ends the main function and hence the program! However, if we want to implement multiple rounds of gameplay, this will not be a good choice.
I’m going to go with the second choice, bringing our “step 2” to the following stage:
1// 2. Repeat 6 times...
2for(int i = 0; i < 6; i++) {
3 // * Ask the user for a guess
4 string guess;
5 cout << "Enter your guess: ";
6 cin >> guess;
7
8 // * Verify their guess TODO
9 // * Show which letters are TODO
10 // incorrect, partially correct,
11 // and/or correct.
12
13 // * If all five letters are
14 // correct, the user wins.
15 if(guess == word) {
16 cout << "You guessed the word correctly!" << endl;
17 cout << "You are the smartest person to ever live." << endl;
18 return 0; // ends the program now.
19 }
20}
We can test this by running cout << word << endl;
before the for loop begins
and entering that as our guess.
Let’s now move onto the guess verification step.
Problem 3.
Fill in the Verify their guess step in the pseudocode.
You may want to look at the
string library reference
to see if there are any helpful functions.
Solution
On one hand, you can manually try to scan each word in WORD_LIST
with a for loop and some smart indexing.
Alternatively, you can use the
find() function for strings.
The reference page says that e.g.
WORD_LIST.find(guess) will return the index
of the first occurrence of guess in WORD_LIST,
or it will return string::npos if the word was not found.
In the case that the word wasn’t found, i.e. the user’s input was invalid, we’ll have to disregard this turn and ask for another input. We can handle this in two ways:
- We can wrap the prompting and obtaining input in a loop of some sort, or
- we can use
continue, taking care to also change the counteriso as to not skip any turns unintentionally.
It is tempting to type
1// if the guess isn't in the word list
2if(WORD_LIST.find(guess) == string::npos) {
3 // print an error message
4 cout << "Invalid guess! Try again. " << endl;
5
6 // update the for loop counter and repeat this iteration
7 i--;
8 continue;
9}
However, if the user’s guess is a,
this will definitely be found in the word list!
We also need to check that the length of the input
is five characters long.
(I’ll let you write that in yourself…)
I think this is a good stopping point for the first day… I’ll update this post when I see how this goes!