Hangman
- 7 Devlogs
- 17 Total hours
A terminal hangman game in Python. Demo: https://hangman.epin.to
A terminal hangman game in Python. Demo: https://hangman.epin.to
After much work, it is time to ship my project! Updated demo at hangman.epin.to.
This project has taught me a lot about making working projects in Python. I only started learning Python at the beginning of this year, and this is the largest coding project that I have completed so far. It may not seem like a lot, but I put many hours into this code. Much of that time was fun, spent coding the main flow of the code. But eventually, the last few hours of this project went into debugging, trying to catch edge cases, and improved user messaging.
So what have I worked on since my last devlog? Mostly implementing a way to disable the counter to play just for fun with infinite guesses. It is accessed by putting an exclamation mark before the mode input (like !m for manual or !r for random). Because no counter is going up, no hangman is displayed.
I also changed some player messaging to make things clearer. When a correct letter is guessed, the 1-based location(s) in the word is printed rather than the 0-based indexes that Python uses for its lists.
Finally, ‘!’ by itself is rejected as a guess. If it is input, the user will get a message saying that ‘!’ alone is not an acceptable input.
Live demo hosted on Nest at hangman.epin.to.
I made lots of progress, as you can probably tell with the amount of time it took. I added functionality for guessing entire words, which took about half of my four logged hours. Also, when the word is guessed correctly, the sprite changes to the man falling off of the gallows and saying ‘thanks’.
To guess entire words, you put an exclamation mark at the beginning of your answer. Otherwise, only single letters are accepted. For example, to guess ‘stardance’ all at once rather than one letter at a time, you would enter ‘!stardance’. If a word guess is incorrect, the counter for tries left goes down one, just like it would for guessing an incorrect letter.
I also updated some of the user prompts to make them more clear, as well as printing out all already guessed answers (letters and words) after any letter input (correct or incorrect) or incorrect word guesses so that the player can better guess correct words/letters.
The reason that this project, despite its apparent simplicity is taking so long is the little features. For example, I added different messages based on different conditions (Have letters and/or words been guessed? If only one has, change the already guessed printed message to match.)
TODO: Add the ability to turn off the counter so that the game can be played without a guess limit.
Sprites added. Sprites are saved as a list of multi-line strings in ascii_sprites.py. As the name implies, the sprites are completely ascii and printable to the terminal.
Additionally, now if the word has spaces, the printed characters will contain a space in the given spot. For example, ‘hack club’ will be ____ ____ instead of nine unbroken underscores.
Letters that have been guessed already are printed out when an incorrect answer is chosen. I might change it to always tell you regardless of an incorrect answer to make it easier to guess.
TODO: I need to add the ability to guess an entire word. I would also like to add a sprite change if the player guesses correctly. Maybe the guy would be released from the gallows or something.
Working prototype completed!
At this point, all text-related functionality is present. You can enter characters that show up more than once in a word, and it won’t throw errors.
I spent the better part of a half hour trying to find out why most times I would enter a letter, it would say that there were none of that letter in the word. This was clearly a bug, as it happened even when entering a letter for a manually chosen word (showing that I wasn’t just an abysmal letter guesser).
It turned out that the lack of two characters caused my problem. In my check_letter() function, it creates different letter location lists depending on whether there is one or more instances of that letter in the word. The code is like this.
def check_letter(word: str, letter: str) -> list:
if isinstance(letter, str) and len(letter) == 1:
locations = []
if word.count(letter) == 1:
locations = [word.find(letter)]
elif word.count(letter) > 1: # BUG found here! elif was an if statement
for index, character in enumerate(word):
if character == letter:
locations.append(index)
else:
locations = [-1]
return locations # If letter is not in the word, will result in index of -1
The comment that starts with ‘BUG’ shows where the problem originated from. Any word with a single instance of a letter returned [-1] because
if word.count(letter) > 1
would always override an intended single element list.
Now, I need to add hangman sprites that will progress as you choose correct or incorrect answers, probably using ASCII characters.
Last day of school, and even more coding!
I have a mostly working prototype that can be seen below. There is no hangman sprite yet, but most of the functionality is here.
Main.py has been expanded greatly. Now you can choose the game mode and enter characters. It also displays blank, unknown characters as ‘_’ and shows known characters as their letters, printing again after every input.
A major bug that remains is that if a word has two identical letters (like ‘a’ in Stardance), only one can be input and uncovered. This is due to code that was meant to prevent people from doing the same wrong answer twice, but it also prevents entering the same right answer twice. This is the culprit code.
In the looping code (counter does not go up in the current version, it is mostly a placeholder):
while counter < 10:
user_input = input("Enter another letter: ")
if user_input.strip().lower() in already_guessed:
print("You already guessed that letter.")
continue # Goes to next loop
In the ask_for_letter() function, only a single instance of a letter is uncovered, even if the letter appears more than once.
index = check_letter(word, letter)
if index != -1:
already_guessed.append(letter)
message = f"{letter} is in the word at index {index}."
uncovered_indicies.append(index)
return index, message
check_letter() only returns a single index, which then only uncovers a single letter in the word. I need to fix this by giving the functions the ability to take and return multiple indexes.
The image below shows an example of the current code. The word is ‘cryptic’, but the last ‘c’ cannot be selected.
Assuming the word doesn’t have two of the same letter, when the word is guessed the following message is printed:
You guessed the word: {secret_word}. Congratulations! 🎉
It is the second to last day of school, and that means a lot of free time to code!
I worked on more functions that I will use, and I added docstrings for everything. I have an ask_for_letter() function that will do the heavy lifting for the hangman logic to keep a clean main.py.
So far in main.py, I have logic to choose the word. I will expand main.py, but most of the logic will be kept in functions.
I got a great start to this project. I wrote some functions that I will need to call in main.py, specifically one to ask the user for manual or random word choice, one to check the location of a letter in a word, and to choose a random word.
I am trying to follow some best practices for Python, like functions in a separate file, docstrings, return type annotations, and descriptive errors.
Next, I will finish up some of the major functions and start on main.py.