Now that you’ve created a basic Hangman game, let’s look at some ways you can extend it with new features. In this chapter, you’ll add multiple word sets for the computer to draw from and the ability to change the game’s difficulty level.
After you’ve played Hangman a few times, you might think that six guesses isn’t enough for the player to get many of the words. You can easily give them more guesses by adding more multiline strings to the HANGMAN_PICS list.
Save your hangman.py program as hangman2.py. Then add the following instructions on line 37 and after to extend the list that contains the hanging man ASCII art:
37. ===''', '''
38. +---+
39. [O |
40. /|\ |
41. / \ |
42. ===''', '''
43. +---+
44. [O] |
45. /|\ |
46. / \ |
47. ===''']
This code adds two new multiline strings to the HANGMAN_PICS list, one with the hanging man’s left ear drawn, and the other with both ears drawn. Because the program will tell the player they have lost based on len(missedLetters) == len(HANGMAN_PICS) - 1, this is the only change you need to make. The rest of the program works with the new HANGMAN_PICS list just fine.
In the first version of the Hangman program, we used an animal word list, but you could change the list of words on line 48. Instead of animals, you could have colors:
48. words = 'red orange yellow green blue indigo violet white black brown'
.split()
Or shapes:
48. words = 'square triangle rectangle circle ellipse rhombus trapezoid
chevron pentagon hexagon septagon octagon'.split()
Or fruits:
48. words = 'apple orange lemon lime pear watermelon grape grapefruit cherry
banana cantaloupe mango strawberry tomato'.split()
With some modification, you can even change the code so that the Hangman game uses sets of words, such as animals, colors, shapes, or fruits. The program can tell the player which set the secret word is from.
To make this change, you’ll need a new data type called a dictionary. A dictionary is a collection of values like a list. But instead of accessing the items in the dictionary with an integer index, you can access them with an index of any data type. For dictionaries, these indexes are called keys.
Dictionaries use { and } (curly brackets) instead of [ and ] (square brackets). Enter the following into the interactive shell:
>>> spam = {'hello':'Hello there, how are you?', 4:'bacon', 'eggs':9999 }
The values between the curly brackets are key-value pairs. The keys are on the left of the colon and the key’s values are on the right. You can access the values like items in lists by using the key. To see an example, enter the following into the interactive shell:
>>> spam = {'hello':'Hello there, how are you?', 4:'bacon', 'eggs':9999}
>>> spam['hello']
'Hello there, how are you?'
>>> spam[4]
'bacon'
>>> spam['eggs']
9999
Instead of putting an integer between the square brackets, you can use, say, a string key. In the spam dictionary, I used both the integer 4 and the string 'eggs' as keys.
You can get the number of key-value pairs in a dictionary with the len() function. For example, enter the following into the interactive shell:
>>> stuff = {'hello':'Hello there, how are you?', 4:'bacon', 'spam':9999}
>>> len(stuff)
3
The len() function will return an integer value for the number of key-value pairs, which in this case is 3.
One difference between dictionaries and lists is that dictionaries can have keys of any data type, as you’ve seen. But remember, because 0 and '0' are different values, they will be different keys. Enter this into the interactive shell:
>>> spam = {'0':'a string', 0:'an integer'}
>>> spam[0]
'an integer'
>>> spam['0']
'a string'
You can also loop over both lists and the keys in dictionaries using a for loop. To see how this works, enter the following into the interactive shell:
>>> favorites = {'fruit':'apples', 'animal':'cats', 'number':42}
>>> for k in favorites:
print(k)
fruit
number
animal
>>> for k in favorites:
print(favorites[k])
apples
42
cats
The keys and values may have printed in a different order for you because, unlike lists, dictionaries are unordered. The first item in a list named listStuff would be listStuff[0]. But there’s no first item in a dictionary, because dictionaries do not have any sort of order. In this code, Python just chooses an order based on how it stores the dictionary in memory, which is not guaranteed to always be the same.
Enter the following into the interactive shell:
>>> favorites1 = {'fruit':'apples', 'number':42, 'animal':'cats'}
>>> favorites2 = {'animal':'cats', 'number':42, 'fruit':'apples'}
>>> favorites1 == favorites2
True
The expression favorites1 == favorites2 evaluates to True because dictionaries are unordered and considered equal if they have the same key-value pairs in them. Meanwhile, lists are ordered, so two lists with the same values in a different order are not equal to each other. To see the difference, enter this into the interactive shell:
>>> listFavs1 = ['apples', 'cats', 42]
>>> listFavs2 = ['cats', 42, 'apples']
>>> listFavs1 == listFavs2
False
The expression listFavs1 == listFavs2 evaluates to False because the lists’ contents are ordered differently.
Dictionaries have two useful methods, keys() and values(). These will return values of a type called dict_keys and dict_values, respectively. Much like range objects, list forms of those data types are returned by list().
Enter the following into the interactive shell:
>>> favorites = {'fruit':'apples', 'animal':'cats', 'number':42}
>>> list(favorites.keys())
['fruit', 'number', 'animal']
>>> list(favorites.values())
['apples', 42, 'cats']
Using list() with the keys() or values() methods, you can get a list of just the keys or just the values of a dictionary.
Let’s change the code in the new Hangman game to support different sets of secret words. First, replace the value assigned to words with a dictionary whose keys are strings and values are lists of strings. The string method split() will return a list of strings with one word each.
48. words = {'Colors':'red orange yellow green blue indigo violet white black
brown'.split(),
49. 'Shapes':'square triangle rectangle circle ellipse rhombus trapezoid
chevron pentagon hexagon septagon octagon'.split(),
50. 'Fruits':'apple orange lemon lime pear watermelon grape grapefruit cherry
banana cantaloupe mango strawberry tomato'.split(),
51. 'Animals':'bat bear beaver cat cougar crab deer dog donkey duck eagle
fish frog goat leech lion lizard monkey moose mouse otter owl panda
python rabbit rat shark sheep skunk squid tiger turkey turtle weasel
whale wolf wombat zebra'.split()}
Lines 48 to 51 are still just one assignment statement. The instruction doesn’t end until the final curly bracket on line 51.
The choice() function in the random module takes a list argument and returns a random value from it. This is similar to what the previous getRandomWord() function did. You’ll use choice() in the new version of the getRandomWord() function.
To see how the choice() function works, enter the following into the interactive shell:
>>> import random
>>> random.choice(['cat', 'dog', 'mouse'])
'mouse'
>>> random.choice(['cat', 'dog', 'mouse'])
'cat'
Just as the randint() function returns a random integer each time, the choice() function returns a random value from the list.
Change the getRandomWord() function so that its parameter will be a dictionary of lists of strings, instead of just a list of strings. Here is what the function originally looked like:
40. def getRandomWord(wordList):
41. # This function returns a random string from the passed list of
strings.
42. wordIndex = random.randint(0, len(wordList) - 1)
43. return wordList[wordIndex]
Change the code in this function so that it looks like this:
53. def getRandomWord(wordDict):
54. # This function returns a random string from the passed dictionary of
lists of strings and its key.
55. # First, randomly select a key from the dictionary:
56. wordKey = random.choice(list(wordDict.keys()))
57.
58. # Second, randomly select a word from the key's list in the
dictionary:
59. wordIndex = random.randint(0, len(wordDict[wordKey]) - 1)
60.
61. return [wordDict[wordKey][wordIndex], wordKey]
We’ve changed the name of the wordList parameter to wordDict to be more descriptive. Now instead of choosing a random word from a list of strings, first the function chooses a random key in the wordDict dictionary by calling random.choice(). And instead of returning the string wordList[wordIndex], the function returns a list with two items. The first item is wordDict[wordKey][wordIndex]. The second item is wordKey.
The wordDict[wordKey][wordIndex] expression on line 61 may look complicated, but it’s just an expression you can evaluate one step at a time like anything else. First, imagine that wordKey has the value 'Fruits' and wordIndex has the value 5. Here is how wordDict[wordKey][wordIndex] would evaluate:
In this case, the item in the list this function returns would be the string 'watermelon'. (Remember that indexes start at 0, so [5] refers to the sixth item in the list, not the fifth.)
Because the getRandomWord() function now returns a list of two items instead of a string, secretWord will be assigned a list, not a string. You can assign these two items into two separate variables using multiple assignment, which we’ll cover in “Multiple Assignment” on page 118.
A del statement will delete an item at a certain index from a list. Because del is a statement, not a function or an operator, it doesn’t have parentheses or evaluate to a return value. To try it out, enter the following into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'antelope', 'albert']
>>> del animals[1]
>>> animals
['aardvark', 'antelope', 'albert']
Notice that when you deleted the item at index 1, the item that used to be at index 2 became the new value at index 1; the item that used to be at index 3 became the new value at index 2; and so on. Everything above the deleted item moved down one index.
You can type del animals[1] again and again to keep deleting items from the list:
>>> animals = ['aardvark', 'anteater', 'antelope', 'albert']
>>> del animals[1]
>>> animals
['aardvark', 'antelope', 'albert']
>>> del animals[1]
>>> animals
['aardvark', 'albert']
>>> del animals[1]
>>> animals
['aardvark']
The length of the HANGMAN_PICS list is also the number of guesses the player gets. By deleting strings from this list, you can reduce the number of guesses and make the game harder.
Add the following lines of code to your program between the lines print('H A N G M A N') and missedLetters = '':
103. print('H A N G M A N')
104.
105. difficulty = 'X'
106. while difficulty not in 'EMH':
107. print('Enter difficulty: E - Easy, M - Medium, H - Hard')
108. difficulty = input().upper()
109. if difficulty == 'M':
110. del HANGMAN_PICS[8]
111. del HANGMAN_PICS[7]
112. if difficulty == 'H':
113. del HANGMAN_PICS[8]
114. del HANGMAN_PICS[7]
115. del HANGMAN_PICS[5]
116. del HANGMAN_PICS[3]
117.
118. missedLetters = ''
This code deletes items from the HANGMAN_PICS list, making it shorter depending on the difficulty level selected. As the difficulty level increases, more items are deleted from the HANGMAN_PICS list, resulting in fewer guesses. The rest of the code in the Hangman game uses the length of this list to tell when the player has run out of guesses.
Multiple assignment is a shortcut to assign multiple variables in one line of code. To use multiple assignment, separate your variables with commas and assign them to a list of values. For example, enter the following into the interactive shell:
>>> spam, eggs, ham = ['apples', 'cats', 42]
>>> spam
'apples'
>>> eggs
'cats'
>>> ham
42
The preceding example is equivalent to the following assignment statements:
>>> spam = ['apples', 'cats', 42][0]
>>> eggs = ['apples', 'cats', 42][1]
>>> ham = ['apples', 'cats', 42][2]
You must put the same number of variables on the left side of the = assignment operator as there are items in the list on the right side. Python will automatically assign the value of the first item in the list to the first variable, the second item’s value to the second variable, and so on. If you don’t have the same number of variables and items, the Python interpreter will give you an error, like so:
>>> spam, eggs, ham, bacon = ['apples', 'cats', 42, 10, 'hello']
Traceback (most recent call last):
File "<pyshell#8>", line 1, in <module>
spam, eggs, ham, bacon = ['apples', 'cats', 42, 10, 'hello']
ValueError: too many values to unpack
>>> spam, eggs, ham, bacon = ['apples', 'cats']
Traceback (most recent call last):
File "<pyshell#9>", line 1, in <module>
spam, eggs, ham, bacon = ['apples', 'cats']
ValueError: need more than 2 values to unpack
Change lines 120 and 157 of the Hangman code to use multiple assignment with the return value of getRandomWord():
119. correctLetters = ''
120. secretWord, secretSet = getRandomWord(words)
121. gameIsDone = False
--snip--
156. gameIsDone = False
157. secretWord, secretSet = getRandomWord(words)
158. else:
159. break
Line 120 assigns the two returned values from getRandomWord(words) to secretWord and secretSet. Line 157 does this again if the player chooses to play another game.
The last change you’ll make is to tell the player which set of words they’re trying to guess. This way, the player will know if the secret word is an animal, color, shape, or fruit. Here is the original code:
91. while True:
92. displayBoard(missedLetters, correctLetters, secretWord)
In your new version of Hangman, add line 124 so your program looks like this:
123. while True:
124. print('The secret word is in the set: ' + secretSet)
125. displayBoard(missedLetters, correctLetters, secretWord)
Now you’re done with the changes to the Hangman program. Instead of just a single list of strings, the secret word is chosen from many different lists of strings. The program also tells the player which set of words the secret word is from. Try playing this new version. You can easily change the words dictionary starting on line 48 to include more sets of words.
We’re done with Hangman! You learned some new concepts when you added the extra features in this chapter. Even after you’ve finished writing a game, you can always add more features as you learn more about Python programming.
Dictionaries are similar to lists except that they can use any type of value for an index, not just integers. The indexes in dictionaries are called keys. Multiple assignment is a shortcut to assign multiple variables the values in a list.
Hangman was fairly advanced compared to the previous games in this book. But at this point, you know most of the basic concepts in writing programs: variables, loops, functions, and data types such as lists and dictionaries. The later programs in this book will still be a challenge to master, but you’ve finished the steepest part of the climb!