I’m going to go function by function and explain what I changed and why.
Imports I used:
from typing import List
def load_words() -> List(str):
"""
Returns a list of valid words. Words are strings of lowercase letters.
Depending on the size of the word list, this function may
take a while to finish.
"""
with open(WORDLIST_FILENAME, 'r') as file:
wordlist = (word.strip() for word in file.readlines())
return wordlist
I see that you have type hints above every variable. Luckily for you, you can do this inline with the exact same format.
variable: int = 10
Make sure you close files when you open them. If you don’t want to worry about that, use a with
statement, which will automatically close the file once you exit the context.
You can also use type hints to display what types of parameters you accept, if any, and what types of values you return, if any.
def choose_word(wordlist: List(str)) -> str:
"""
wordlist (list): list of words (strings)
Returns a word from wordlist at random
"""
return random.choice(wordlist)
Only thing I changed here was adding type hints. Now it’s clear wordlist
is a List of string, and the function returns a string.
def is_word_guessed(secret_word: str, letters_guessed: List(str)) -> bool:
'''
secret_word: string, the word the user is guessing; assumes all letters are
lowercase
letters_guessed: list (of letters), which letters have been guessed so far;
assumes that all letters are lowercase
returns: boolean, True if all the letters of secret_word are in letters_guessed;
False otherwise
'''
guess = ""
for char in secret_word:
for letter in letters_guessed:
if char == letter:
guess += char
else:
continue
return guess == secret_word
Personally, I would just use ""
when creating an empty string. Again, note the type hints.
Instead of return True else return False
, you can return the boolean condition that is being evaluated. It does the same exact thing, but it’s shorter and looks a lot nicer.
def get_guessed_word(secret_word: str, letters_guessed: List(str)) -> str:
'''
secret_word: string, the word the user is guessing
letters_guessed: list (of letters), which letters have been guessed so far
returns: string, comprised of letters, underscores (_), and spaces that represents
which letters in secret_word have been guessed so far.
'''
showbox_list = ('_ ' for _ in secret_word)
for letter in letters_guessed:
for count, char in enumerate(secret_word):
if letter == char:
showbox_list(count) = letter
else:
continue
return ''.join(showbox_list)
Instead of using a traditional for
loop, I would use list comprehension to construct your showbox list. The _
variable just indicates that the variable used in the loop should be ignored. Instead of creating a variable, then returning that variable, just return the value you would have assigned to that variable. Again, type hints.
def get_available_letters(letters_guessed: List(str)) -> str:
'''
letters_guessed: list (of letters), which letters have been guessed so far
returns: string (of letters), comprised of letters that represents which letters have not
yet been guessed.
'''
english_letters = (char for char in string.ascii_lowercase if char not in letters_guessed)
english_letters_str = ''.join(english_letters)
print('Available letters:', english_letters_str)
return english_letters_str
Again, you can use list comprehension to construct your initial list. This adds every character in the string.ascii_lowercase
list, but ONLY if the character is not in the letters_guessed
list. Obligatory type hints point.
def match_with_gaps(my_word: str, other_word: str) -> bool:
'''
my_word: string with _ characters, current guess of secret word
other_word: string, regular English word
returns: boolean, True if all the actual letters of my_word match the
corresponding letters of other_word, or the letter is the special symbol
_ , and my_word and other_word are of the same length;
False otherwise:
'''
my_word_stripped = my_word.replace(" ", "")
same_char = ()
blank_stripped = ()
if len(my_word_stripped) != len(other_word):
return False
for index, letter in enumerate(my_word_stripped):
if letter in string.ascii_lowercase:
same_char.append(index)
else:
blank_stripped.append(index)
mws = ''
ow = ''
for index_same in same_char:
for index_dif in blank_stripped:
if other_word(index_dif) == other_word(index_same):
return False
mws += my_word_stripped(index_same)
ow += other_word(index_same)
return mws == ow
The most important thing I want to stress here is guard clauses. These prevent you from having to indent into another context space. It makes your code look nicer, and is a good thing to practice.
You don’t need an else
if the if
returns from the function. Again, type hints and returning boolean expressions.
def show_possible_matches(my_word: str) -> str:
'''
my_word: string with _ characters, current guess of secret word
returns: nothing, but should print out every word in wordlist that matches my_word
Keep in mind that in hangman when a letter is guessed, all the positions
at which that letter occurs in the secret word are revealed.
Therefore, the hidden letter(_ ) cannot be one of the letters in the word
that has already been revealed.
'''
possible_matches = (i for i in wordlist if match_with_gaps(my_word, i))
return ' '.join(possible_matches)
Using list comprehension reduces the need for the for
loop, allowing you to accomplish the same task in one line. Again, type hints.
One more thing I’d like to add. You said that you initially didn’t want to post the entire program because “it was too long”. Most of the length of your program comes down to docstrings. Now, this isn’t inherently a bad thing. Looking through them, this looks like a problem set, and these are instructions. Thats fine. But keep in mind that docstrings should really only display what the function does, what parameters it accepts (if any), and what it returns. I personally think the hangman_with_hints
docstring goes a little over the top (25 lines!), but that’s just me.