EX03 - Structured Wordle


In this exercise you will use the concepts learned in the Lessons 12 through 14, as well as those of the previous units. Please complete all lessons before attempting to begin this exercise.

The next step on our journey to implementing Wordle is to produce a program that gives the player six guesses at your program’s secret word, just like the real game. Additionally, you will implement this program in a structured fashion using function definitions.

In this exercise, you will prompt the user for a word that matches the length of your secret word. We’ll use "codes" for the purposes of our examples and autograding. Just like in the previous exercise, your program will be able to work with a secret word of any length, though! After submitting to autograding for full credit, you should change your word and let your friends play your new and improved game.

You should follow the steps below for implementing the program function at a time. To get a sense of where you are going, here are two examples of the final game:

    $ python -m exercises.ex03_wordle
    === Turn 1/6 ===
    Enter a 5 character word: dukes
    🟨⬜⬜🟩🟩
    === Turn 2/6 ===
    Enter a 5 character word: gonna
    ⬜🟩⬜⬜⬜
    === Turn 3/6 ===
    Enter a 5 character word: lose
    That wasn't 5 chars! Try again: loser
    ⬜🟩🟨🟩⬜
    === Turn 4/6 ===
    Enter a 5 character word: tounc
    ⬜🟩⬜⬜🟨
    === Turn 5/6 ===
    Enter a 5 character word: saturday
    That wasn't 5 chars! Try again: sat 
    That wasn't 5 chars! Try again: stday
    🟨⬜🟩⬜⬜
    === Turn 6/6 ===
    Enter a 5 character word: biscuits
    That wasn't 5 chars! Try again: bscts
    ⬜🟨🟨⬜🟩
    X/6 - Sorry, try again tomorrow!

    $ python -m exercises.ex03_wordle
    === Turn 1/6 ===
    Enter a 5 character word: ideas
    ⬜🟨🟨⬜🟩
    === Turn 2/6 ===
    Enter a 5 character word: doges
    🟨🟩⬜🟩🟩
    === Turn 3/6 ===
    Enter a 5 character word: codes
    🟩🟩🟩🟩🟩
    You won in 3/6 turns!

Permitted Constructs

We expect you to implement this exercise using only the concepts covered in COMP110. If you have prior programming experience, restrict your implementation to only the concepts covered. While there are many ways to implement this program with additional concepts beyond those we have covered, you should not attempt to do so until after submitting this exercise for full credit once the autograder is posted. Gaining additional practice with the fundamentals may feel clunky, but will help ensure you have full command over the concepts we expect you to know. Additionally, it is good practice for working in other programming environments which are more constrained and require creativity to overcome restrictions. For this exercise, you will be penalized for using any kind of loop construct other than a while loop. Additionally, the in operator, the break operator, and string methods (such as .count and .format) are not permitted.

Overview

In this program you will implement a main function that contains Wordle’s “game loop”. The game loop is what controls the overall game logic:

The main flow of the game works as follows:

  1. You have up to six turns
  2. Each turn the player gets to input_guess of the same length of each word
    1. If the guess is a different length, you get to make additional guesses
  3. The guess is compared with the secret and emojified / “codified” boxes are output
    1. White for letters that don’t exist in the secret
    2. Green for letters that match the secret at the same position
    3. Yellow for each guessed letter the secret contains_char, but at a different position
  4. If the guess was correct, the game is over and the player wins
  5. If the guess was incorrect, the game loop goes back to step 2 and continues with next turn

Each of the four monospace font-face words above (main, input_guess, emojified, and contains_char) will be implemented as a function definition to more logically structure the process of your program into simpler abstractions that can be reused.

You will start by building the smaller, simpler building-block functions first (contains_char which helps you build emojified, then input_guess), before finally bringing together emojified and input_guess for use in main’s game loop. This bottoms-up process helps us break down the problem into more manageable steps. Along the way, you will be refashioning the same algorithmic ideas you have already implemented in EX02, but in a program structured with functions.

Part 0. Setting up the Python Program

In Visual Studio Code, be sure your workspace is open. (Reminder: File > Open Recent > comp110-YYS-workspace is a quick way to reopen it! Where YY is the current year and S is the semeseter: S for Spring, F for Fall.)

Open the Explorer pane (click the icon with two sheets of paper or to to View > Explorer) and expand the Workspace directory.

Right click on the exercises directory and select “New File”. Enter the following filename, being careful to match capitalization and punctuation:

  • ex03_wordle.py

Before beginning work on the program, you should add a docstring to the top of your Python module just as you have previously. Then, you should add a line with the special variable named __author__ assigned to be a string with your 9-digit student PID.

Part 1. contains_char - 10 points

Declare a function named contains_char. Its purpose is when given two strings, the first of any length, the second a single character, it will return True if the single character of the second string is found at any index of the first string, and return False otherwise. More specifically, declare your contains_char function such that:

  1. It has two parameters (name them descriptively)
    1. A string that is being searched through for the second parameter
    2. A string that is expected to be a single character in length and is the character being searched for
  2. A boolean return type
  3. A docstring describing the purpose of the function in your own words

Since the caller of this function can be expected to provide the correct arguments, specifically a second argument whose length is one, we will “assert” this assumption in our code such that an error is raised if it is not found to be true. As the first line of code in your function’s body, add the following assert statement and fill in the blank (_____) with your second parameter’s name:

assert len(_______) == 1

The algorithm of this function is very similar to the algorithm you implemented in EX02 to determine whether to concatenate a white box or a yellow box. Effectively, you should test each index of the first parameter string to see if it is equal to the second parameter. If so, return True. Otherwise, after searching all indices and not finding the character you were looking for, return False.

Once you have your best first attempt to implement this function, you can import it for use in the REPL to test it out. Save your work and then run the following commands:

    $ python                         
    Python 3.10.2 (v3.10.2:a58ebcc701, Jan 13 2022, 14:50:16) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.

    >>> from exercises.ex03_wordle import contains_char
    >>> print(contains_char("abc", "b"))
    True
    >>> print(contains_char("abc", "c"))
    True
    >>> print(contains_char("abc", "a"))
    True
    >>> print(contains_char("abc", "z"))
    False
    >>> print(contains_char("abc", "zz"))
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "......../exercises/ex03_wordle.py", line 48, in contains_char
        assert len(needle) == 1
    AssertionError
    >>> quit()

Notice, in the REPL, the line from exercises.ex03_wordle import contains_char will import your function definition from your exercises.ex03_wordle so that you can make use of your function in the REPL. You can then type out example function calls to test your implementation and be sure your function definition is returning the correct and expected values, as shown above. Your results must match exactly (except for the ellipses File details near the AssertionError).

Part 2. emojified - 20 points

Declare a function named emojified. Its purpose is given two strings of equal length, the first a guess and the second a secret, it will return a string of emoji whose color codifies the same as you previously implemented in EX02. You should reuse the named constants you setup in EX03 for the colored emoji boxes. The body of this function must make use of your contains_char function definition by calling it to test for yellow or white box codification. Using this description, write your function header and docstring.

Since you can reasonably expect anyone using this function to provide strings of equal length, you can add the following assertion as the first line of your function implementation following your docstring, replacing the capitalized words with your function’s parameter’s names:

assert len(REPLACE_THIS_WITH_YOUR_GUESS_PARAMETERS_NAME) == len(REPLACE_THIS_WITH_YOUR_SECRET_PARAMETERS_NAME)

Once you have implemented this function, you can import it for use in the REPL to test it out, just like above. Be sure to save your work each time before restarting the python REPL, then try the following:

    $ python
    Python 3.10.2 (v3.10.2:a58ebcc701, Jan 13 2022, 14:50:16) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from exercises.ex03_wordle import emojified
    >>> print(emojified("hello", "world"))
    ⬜⬜🟨🟩🟨
    >>> print(emojified("elloh", "hello"))
    🟨🟨🟩🟨🟨
    >>> print(emojified("python", "wohoo"))
    Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
    File "......../exercises/ex03_wordle.py", line 33, in emojified
        assert len(guess) == len(secret)
    AssertionError
    >>> print(emojified("python", "woohoo"))
    ⬜⬜⬜🟩🟩⬜
    >>> print(emojified("python", "python"))
    🟩🟩🟩🟩🟩🟩
    >>> print(emojified("yikyak", "tiktok"))
    ⬜🟩🟩⬜⬜🟩
    >>> quit()

Now you have a function that, given a guess and a secret of the same length, will Wordle emojify the results of the guess! Notice how your emojified function makes use of the simpler contains_char function to build up more complex behavior. This is the beauty of abstraction! You no longer need the nested while loop that’s as complex to read because the details of testing for the existence of a character are “abstracted away” to the contains_char function.

Once your emojified function is working correctly, as shown above, continue on.

Part 3. input_guess – 10 Points

Declare a function named input_guess. Its purpose is given an integer “expected length” of a guess as a parameter, it will prompt the user for a guess and continue prompting them until they provide a guess of the expected length. This function must then return the user’s guess of the correct length to the caller. The implementation of this function will behave very similarly to your logic in part 1 of EX02.

Once you have implemented this function, you can import it for use in the REPL just like the examples above. Be sure to save your work before restarting the python REPL, then try the following:

    $ python
    Python 3.10.2 (v3.10.2:a58ebcc701, Jan 13 2022, 14:50:16) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from exercises.ex03_wordle import input_guess
    >>> print(input_guess(5))
    Enter a 5 character word: gthd
    That wasn't 5 chars! Try again: gdtbath
    That wasn't 5 chars! Try again: gounc
    gounc
    >>> print(input_guess(6))
    Enter a 6 character word: gthd
    That wasn't 6 chars! Try again: gdtbath
    That wasn't 6 chars! Try again: goheel
    goheel
    >>> print(input_guess(7))
    Enter a 7 character word: goheels
    goheels
    >>> quit()

Part 4. main – 30 Points

Now it’s time to pull together your functions, which are building blocks, into a main function that implements the high-level logic of the Wordle game loop. The purpose of the main function is to establish what the secret word is as a variable (choose "codes" for autograding purposes), keep track of how many turns the user has spent, whether the user has won the game, and control the flow of the game.

The declaration of your main function is unlike the functions above because it will not have any parameters and it will return None. You can decare your main function as follows:

def main() -> None:
    """The entrypoint of the program and main game loop."""
    # Your code will go here

The “state” of a game refers to the variables you need to keep track of in memory in order to run the game. What variables do you need to keep track of? Define those inside of main’s body first.

Then, begin the game loop while the user still has turns left to play and has not yet won, you will want to do the following:

  1. Print the current turn number in a format such as === Turn 1/6 ===
  2. Prompt the user for a guess, relying on your function input_guess to obtain a guess of the correct length.
  3. Codify the emoji results of the user’s guess versus the secret by making use of your emojified function. Print the resulting codified string.
  4. If the user’s guess is the secret, the user has won!
  5. Otherwise, move on to the next turn.

After the game loop, if the user won, print You won in N/6 turns! where N is replaced with the number of guesses it took to get the word. Otherwise, when the user does not guess the word in 6 or fewer guesses, print the following message: X/6 - Sorry, try again tomorrow! where X is literally the character X.

As you are working on main, you can save your work and import the main function just like the others to try calling it:

    $ python
    Python 3.10.2 (v3.10.2:a58ebcc701, Jan 13 2022, 14:50:16) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from exercises.ex03_wordle import main
    >>> main()
    === Turn 1/6 ===
    Enter a 5 character word: ideas
    ⬜🟨🟨⬜🟩
    === Turn 2/6 ===
    Enter a 5 character word: codes
    🟩🟩🟩🟩🟩
    You won in 2/6 turns!

    >>> main()
    === Turn 1/6 ===
    Enter a 5 character word: wohoo
    ⬜🟩⬜🟨🟨
    === Turn 2/6 ===
    Enter a 5 character word: wohoo
    ⬜🟩⬜🟨🟨
    === Turn 3/6 ===
    Enter a 5 character word: wohoo
    ⬜🟩⬜🟨🟨
    === Turn 4/6 ===
    Enter a 5 character word: wohoo
    ⬜🟩⬜🟨🟨
    === Turn 5/6 ===
    Enter a 5 character word: wohoo
    ⬜🟩⬜🟨🟨
    === Turn 6/6 ===
    Enter a 5 character word: wohoo
    ⬜🟩⬜🟨🟨
    X/6 - Sorry, try again tomorrow!

    >>> quit()

Once you have your main function and game loop working, there’s only one bit of icing left to add to your delicious code cake. We will fully explain what is going on in the following code snippet soon, but for now note that this is an idiom common to Python programs like the one you have written. We will learn it does two things: 1. it makes it possible to run your Python program as a module (if you tried python -m exercises.ex03_wordle right now you would see nothing happens), and 2. it makes it possible for other modules to import your functions and reuse them. Add the following snippet of code as the last 2 lines of your program (notice, there are two underscores on both sides of the words name and main):

if __name__ == "__main__":
    main()

Now you can try running your game as a module and it should work: python -m exercises.ex03_wordle

Congratulations on writing your first structured program in COMP110!

Part 5. Style and Documentation Requirements – 20 Points (Manually Graded)

We will manually grade your code and are looking for good choices of meaningful variable names. Your variable names should be descriptive of their purposes. (Loop indexing variables can be short, such as i, or idx.) You should also use the Python snake_case convention where variable names are all lowercase and new words are separated by underscores.

You should add code comments in your own English words to describe what is happening at important stages of your program.

Your program should work regardless of the secret’s length. Thus, you should not have any hard-coded numbers (such as 6 for "python"). All numbers that appear in output and the boundaries of loops should be based on the len-gth of your secret word.

Part 6. Type Safety and Linting - 9 Points

The autograder uses industry standard tools for checking the type safety of your programs (e.g. being sure you’re using variables of the correct data types in valid ways) and linting style rules. If you have point deductions on Type Safety or Linting, read through the feedback the autograder gives and it should direct you to the line number in your program with the issue.

Make a Backup Checkpoint “Commit”

As you make progress on this exercise, making backups is encouraged.

  1. Open the Source Control panel (Command Palette: “Show SCM” or click the icon with three circles and lines on the activity panel).
  2. Notice the files listed under Changes. These are files you’ve made modifications to since your last backup.
  3. Move your mouse’s cursor over the word Changes and notice the + symbol that appears. Click that plus symbol to add all changes to the next backup. You will now see the files listed under “Staged Changes”.
    • If you do not want to backup all changed files, you can select them individually. For this course you’re encouraged to back everything up.
  4. In the Message box, give a brief description of what you’ve changed and are backing up. This will help you find a specific backup (called a “commit”) if needed. In this case a message such as, “Progress on Exercise N” will suffice, where N is the current exercise.
  5. Press the Check icon to make a Commit (a version) of your work.
  6. Finally, press the Ellipses icon (…), look for “Pull/Push” submenu, and select “Push to…”, and in the dropdown select your backup repository.

Submit to Gradescope for Grading

All that’s left now is to hand-in your work on Gradescope for grading!

Login to Gradescope and select the assignment named “EX03 - Structured Wordle”. You’ll see an area to upload a zip file. To produce a zip file for autograding, return back to Visual Studio Code.

If you do not see a Terminal at the bottom of your screen, open the Command Palette and search for “View: Toggle Integrated Terminal”.

Type the following command (all on a single line):

python -m tools.submission exercises/ex03_wordle.py

In the file explorer pane, look to find the zip file named “yy.mm.dd-hh.mm-exercises-ex01_chardle.py.zip”. The “yy”, “mm”, “dd”, and so on, are timestamps with the current year, month, day, hour, minute. If you right click on this file and select “Reveal in File Explorer” on Windows or “Reveal in Finder” on Mac, the zip file’s location on your computer will open. Upload this file to Gradescope to submit your work for this exercise.

Autograding will take a few moments to complete. For this exercise there will be 20 “human graded” points. Thus, you should expect a maximum autograding score of 80 possible points on this assignment. If there are issues reported, you are encouraged to try and resolve them and resubmit. If for any reason you aren’t receiving full credit and aren’t sure what to try next, come give us a visit in office hours!

Contributor(s): Kris Jordan