**CS 111 w20 --- Lab 4: Fixing Hangman** *Due: Friday, February 7, at 9pm* In this homework you will practice your debugging skills by tracking down and fixing problems with an implementation of the word guessing game Hangman. You will need to following files: - [hangman.py](hangman.py): it doesn't crash immediately, but this Python code for Hangman has many logical errors. It will be the starting point for your debugging task. - [wordlist.txt](wordlist.txt): a text file with one word per line. Used to choose a random word for the user to try and guess. The [Hangman](lab4.md.html#hangman) section describes the Hangman game in more detail and lists the features we want our Hangman program to have. The [Bug Detective](lab4.md.html#bugdetective) section guides you through tracking down the various errors in the starting code. Make sure to read the [Advice](lab4.md.html#advice) section for a bit of background information. Start early and post questions in the [Moodle forum](https://moodle.carleton.edu/mod/forum/view.php?id=507509)! --- # Hangman Hangman is a word guessing game where the player tries to guess a word by guessing the letters in that word. The player is allowed a limited number of wrong guesses--they win if they can guess all the correct letters before they run out of guesses. For example, assume the secret word is `banana`. At the start of the game, the player might see
10 guesses left _ _ _ _ _ _ Letters you've already guessed:Each `_` represents a letter in the secret word. Let's say the player guesses `a`. They would then see
10 guesses left _ a _ a _ a Letters you've already guessed: aNote that every matching letter in the word was filled in. Also note that the player was not charged a guess--they only lose a guess for an incorrect letter. If the player next guessed the letter `s`, they would see
9 guesses left _ a _ a _ a Letters you've already guessed: a sWe want our Hangman program to have the following features: - Each round, the user is told the number of guesses they have left, their current progress on guessing the word, and the letters they've already guessed - The user is forced to enter a valid guess. The program should repeatedly ask them for a guess until they enter a single letter that hasn't already been guessed - The user can guess in either lowercase or uppercase letters (they are treated the same) - The user starts with 10 guesses and loses one for every incorrect guess - When the user runs out of guesses, the game ends - When the user has guessed all the letters in the word, the game ends - When the game ends, if the user has guessed all the letters, they win - Otherwise, they lose # Bug Detective To track down the bugs in `hangman.py` you'll be running the program and observing where it deviates from the desired behavior. In its current state, it doesn't crash immediately, but neither does it correctly play Hangman. **Producing a fully working version of `hangman.py` should require around six changes, where a *change* consists of adding, modifying, or *removing* one or two lines to address a particular issue.** These six changes do not include any `print`s or other lines you add to help in identifying the source of problems. ## `get_guess` If you run `hangman.py` in VS Code and try and play it, you'll quickly discover something is definitely wrong with how it's processing user input. A good place to start your debugging is the function that handles this, `get_guess`. There are three properties it tries to enforce about user input: 1. The new guess has not already been guessed 2. The new guess is only letters (`guess.isalpha()` returns `True` when the string `guess` has only letters and nothing else) 3. The new guess is only a single character From trying it out, we observe something is probably wrong with how the function is handling property 2 since if we guess `a`, we get a message `That's not a letter`. Try thinking through the Boolean logic (i.e., when things will be `True` and `False`) to identify what needs to be changed. The next thing to consider is that this function is supposed to repeatedly prompt the user for input until they enter something valid. Test out the program--does it do this? It seems it accepts whatever the user inputs the second time no matter what. If `get_guess` is going to **repeat something until a condition is met** it should use a loop. What should be turned into a loop and what kind of loop should it be? ## `play` For this part you might find it useful to print out `word` (the variable for the word the player is trying to guess) in the `play` function, so you can know when you are making a correct and incorrect guess. Now that we've fixed the glaring problems with `get_guess`, we can turn to the logic of the game itself in the `play` function. Look through the function and read the comments to get an idea of what the different variables represent. Run `hangman.py` and input a couple of correct guesses--what happens? The previous correct guess goes away! Find the place in the code where it handles a correct guess (the comments may be useful here). Try and find the line in this part where it would overwrite previously guessed letters. Think about what value `current` has at the start. Does it make sense to assign `"_"` to locations in that list? When you were fixing `get_guess` you saw that it used its parameter `guessed` to check if the user's input has been guessed before. Try running `hangman.py` and entering the same wrong guess multiple times. For some reason, our program accepts this. You might have also suspected a problem here since it never prints anything after `Letters you've already guessed:`. Look at where we call `get_guess` in the `play` function. We're passing it the variable `guessed`, which seems like the right thing to do. Reading the comments higher up in the `play` function, we see `guessed` is described as `# list of previously guessed letters`. Can you find anywhere in the `play` function that we update `guessed` with previously guessed letters? We probably want to add each letter the player guesses to the list `guessed`. ## The nitty-gritty At this point we're getting down to the final details of a fully correct Hangman game. Try playing a game where you run out of guesses--what happens? Before we get an `IndexError`, it prints out `-1 guesses left`. That seems fishy. Why doesn't the game end before `guesses` goes negative? There's code at the end of the `play` function that prints out a winning or losing message to the user. What controls whether our program gets to this code? We need to change the logic that controls when the game ends to account for running out of guesses. Now try playing a game where you guess all the letters. There seems to be one left over! Some invisible character at the end of every word we pick from `wordlist.txt`. Recall that `wordlist.txt` has one word per line and that there's a particular character that represents a new line. It might be helpful to look back at [`word_check_fixed.py`](lecture_notes/code/word_check_fixed.py) from lecture where we encountered a similar problem or consult the [documentation for string methods](https://docs.python.org/3/library/stdtypes.html#string-methods) to see if there's one that would be useful here. # Advice A really good rule of thumb when debugging is to only change one thing at a time. That way if the behavior of the program changes (or stays the same) you know exactly what change is responsible. The rest of the advice section is background on a new thing that comes up in `hangman.py`. You don't need to use it yourself to get it working, but understanding it may help you understand what the code is doing. Various places in `hangmany.py` use the string method `join` for convenience. This function takes a list of strings as its argument and returns those strings concatenated together. The useful thing is that it puts the string it's called with in between each concatenated string. For example ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python strings = ["hangman", "is", "fun"] nothing_in_between = "".join(strings) space_in_between = " ".join(strings) print(nothing_in_between) print(space_in_between) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ prints out `hangmanisfun` then `hangman is fun`. Whatever string comes before `.join` is what gets inserted between each element of the list. You could replicate `" ".join(strings)` like this (you can see why `join` is convenient) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Python strings = ["hangman", "is", "fun"] space_in_between = "" separator = " " for i in range(len(strings) - 1): space_in_between = space_in_between + strings[i] + separator space_in_between = space_in_between + strings[-1] print(space_in_between) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ # What to Turn In Submit the following files via the [Lab 4 Moodle page](https://moodle.carleton.edu/mod/assign/view.php?id=507496). You **do not** need to submit `wordlist.txt`. - Your fixed `hangman.py`. Make sure it has all the features list in the [Hangman](lab4.md.html#hangman) section. - A plain text file called `feedback.txt` with the following information (you get points for answering these questions!): - How many hours you spent outside of class on this homework. - The difficulty of this homework for this point in a 100-level course as: too easy, easy, moderate, challenging, or too hard. - What did you learn on this homework (very briefly)? Rate the educational value relative to the time invested from 1 (low) to 5 (high). # Grading This assignment is graded out of 30 points as follows: - Submitting `feedback.txt` -- 3 points - Input -- 10 points + Only allows letters -- 2 points + Rejects multiple characters -- 2 points + Rejects previously guessed letters -- 2 points + Displays correct messages -- 1 point + Prompts until valid input given -- 3 points - Gameplay -- 7 points + Correct initial state -- 1 point + Subtracts guess for incorrect letter -- 2 points + Tracks previously guessed letters -- 2 points + Tracks progress -- 2 points - Winning -- 4 points + Game ends appropriately -- 3 points + Prints appropriate message -- 1 point - Style -- 6 points + Still converts input to lowercase -- 1 point + No significant unnecessary code -- 5 points - -1 point per unneeded change or excessively convoluted fix - -1 point per duplication of existing variables or functionality with new code We will evaluate your submission by playing the Hangman game and checking that it has all the required working features. We will also read your code to assess the stylistic aspects of your fixes.