This chapter’s game introduces many new concepts, but don’t worry: you’ll experiment with them in the interactive shell before actually programming the game. You’ll learn about methods, which are functions attached to values. You’ll also learn about a new data type called a list. Once you understand these concepts, it will be much easier to program Hangman.
This chapter’s game is a bit longer than the previous games, but much of it is the ASCII art for the hanging man pictures. Enter the following into the file editor and save it as hangman.py. If you get errors after entering the following code, compare the code you typed to the book’s code with the online diff tool at https://www.nostarch.com/inventwithpython#diff.
hangman.py
1. import random
2. HANGMAN_PICS = ['''
3. +---+
4. |
5. |
6. |
7. ===''', '''
8. +---+
9. O |
10. |
11. |
12. ===''', '''
13. +---+
14. O |
15. | |
16. |
17. ===''', '''
18. +---+
19. O |
20. /| |
21. |
22. ===''', '''
23. +---+
24. O |
25. /|\ |
26. |
27. ===''', '''
28. +---+
29. O |
30. /|\ |
31. / |
32. ===''', '''
33. +---+
34. O |
35. /|\ |
36. / \ |
37. ===''']
38. words = 'ant baboon badger bat bear beaver camel cat clam cobra cougar
coyote crow deer dog donkey duck eagle ferret fox frog goat goose hawk
lion lizard llama mole monkey moose mouse mule newt otter owl panda
parrot pigeon python rabbit ram rat raven rhino salmon seal shark sheep
skunk sloth snake spider stork swan tiger toad trout turkey turtle
weasel whale wolf wombat zebra'.split()
39.
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]
44.
45. def displayBoard(missedLetters, correctLetters, secretWord):
46. print(HANGMAN_PICS[len(missedLetters)])
47. print()
48.
49. print('Missed letters:', end=' ')
50. for letter in missedLetters:
51. print(letter, end=' ')
52. print()
53.
54. blanks = '_' * len(secretWord)
55.
56. for i in range(len(secretWord)): # Replace blanks with correctly
guessed letters.
57. if secretWord[i] in correctLetters:
58. blanks = blanks[:i] + secretWord[i] + blanks[i+1:]
59.
60. for letter in blanks: # Show the secret word with spaces in between
each letter.
61. print(letter, end=' ')
62. print()
63.
64. def getGuess(alreadyGuessed):
65. # Returns the letter the player entered. This function makes sure the
player entered a single letter and not something else.
66. while True:
67. print('Guess a letter.')
68. guess = input()
69. guess = guess.lower()
70. if len(guess) != 1:
71. print('Please enter a single letter.')
72. elif guess in alreadyGuessed:
73. print('You have already guessed that letter. Choose again.')
74. elif guess not in 'abcdefghijklmnopqrstuvwxyz':
75. print('Please enter a LETTER.')
76. else:
77. return guess
78.
79. def playAgain():
80. # This function returns True if the player wants to play again;
otherwise, it returns False.
81. print('Do you want to play again? (yes or no)')
82. return input().lower().startswith('y')
83.
84.
85. print('H A N G M A N')
86. missedLetters = ''
87. correctLetters = ''
88. secretWord = getRandomWord(words)
89. gameIsDone = False
90.
91. while True:
92. displayBoard(missedLetters, correctLetters, secretWord)
93.
94. # Let the player enter a letter.
95. guess = getGuess(missedLetters + correctLetters)
96.
97. if guess in secretWord:
98. correctLetters = correctLetters + guess
99.
100. # Check if the player has won.
101. foundAllLetters = True
102. for i in range(len(secretWord)):
103. if secretWord[i] not in correctLetters:
104. foundAllLetters = False
105. break
106. if foundAllLetters:
107. print('Yes! The secret word is "' + secretWord +
'"! You have won!')
108. gameIsDone = True
109. else:
110. missedLetters = missedLetters + guess
111.
112. # Check if player has guessed too many times and lost.
113. if len(missedLetters) == len(HANGMAN_PICS) - 1:
114. displayBoard(missedLetters, correctLetters, secretWord)
115. print('You have run out of guesses!\nAfter ' +
str(len(missedLetters)) + ' missed guesses and ' +
str(len(correctLetters)) + ' correct guesses,
the word was "' + secretWord + '"')
116. gameIsDone = True
117.
118. # Ask the player if they want to play again (but only if the game is
done).
119. if gameIsDone:
120. if playAgain():
121. missedLetters = ''
122. correctLetters = ''
123. gameIsDone = False
124. secretWord = getRandomWord(words)
125. else:
126. break
The Hangman program randomly selects a secret word for the player to guess from a list of words. The random module will provide this ability, so line 1 imports it.
1. import random
But the HANGMAN_PICS variable on line 2 looks a little different from the variables we’ve seen so far. In order to understand what this code means, we need to learn about a few more concepts.
Lines 2 to 37 are one long assignment statement for the HANGMAN_PICS variable.
2. HANGMAN_PICS = ['''
3. +---+
4. |
5. |
6. |
7. ===''', '''
--snip--
37. ===''']
The HANGMAN_PICS variable’s name is in all uppercase letters. This is the programming convention for constant variables. Constants are variables meant to have values that never change from their first assignment statement. Although you can change the value in HANGMAN_PICS just as you can for any other variable, the all-uppercase name reminds you not to do so.
As with all conventions, you don’t have to follow this one. But doing so makes it easier for other programmers to read your code. They’ll know that HANGMAN_PICS will always have the value it was assigned from lines 2 to 37.
HANGMAN_PICS contains several multiline strings. It can do this because it’s a list. Lists have a list value that can contain several other values. Enter this into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'antelope', 'albert']
>>> animals
['aardvark', 'anteater', 'antelope', 'albert']
The list value in animals contains four values. List values begin with a left square bracket, [, and end with a right square bracket, ]. This is like how strings begin and end in quotation marks.
Commas separate the individual values inside of a list. These values are also called items. Each item in HANGMAN_PICS is a multiline string.
Lists let you store several values without using a variable for each one. Without lists, the code would look like this:
>>> animals1 = 'aardvark'
>>> animals2 = 'anteater'
>>> animals3 = 'antelope'
>>> animals4 = 'albert'
This code would be hard to manage if you had hundreds or thousands of strings. But a list can easily contain any number of values.
You can access an item inside a list by adding square brackets to the end of the list variable with a number between them. The number between the square brackets is the index. In Python, the index of the first item in a list is 0. The second item is at index 1, the third item is at index 2, and so on. Because the indexes begin at 0 and not 1, we say that Python lists are zero indexed.
While we’re still in the interactive shell and working with the animals list, enter animals[0], animals[1], animals[2], and animals[3] to see how they evaluate:
>>> animals[0]
'aardvark'
>>> animals[1]
'anteater'
>>> animals[2]
'antelope'
>>> animals[3]
'albert'
Notice that the first value in the list, 'aardvark', is stored in index 0 and not index 1. Each item in the list is numbered in order starting from 0.
Using the square brackets, you can treat items in the list just like any other value. For example, enter animals[0] + animals[2] into the interactive shell:
>>> animals[0] + animals[2]
'aardvarkantelope'
Both variables at indexes 0 and 2 of animals are strings, so the values are concatenated. The evaluation looks like this:
If you try accessing an index that is too high to be in the list, you’ll get an IndexError that will crash your program. To see an example of this error, enter the following into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'antelope', 'albert']
>>> animals[9999]
Traceback (most recent call last):
File "", line 1, in
animals[9999]
IndexError: list index out of range
Because there is no value at index 9999, you get an error.
You can also change the value of an item in a list using index assignment. Enter the following into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'antelope', 'albert']
>>> animals[1] = 'ANTEATER'
>>> animals
['aardvark', 'ANTEATER', 'antelope', 'albert']
The new 'ANTEATER' string overwrites the second item in the animals list. So typing animals[1] by itself evaluates to the list’s current second item, but using it on the left side of an assignment operator assigns a new value to the list’s second item.
You can join several lists into one list using the + operator, just as you can with strings. Doing so is called list concatenation. To see this in action, enter the following into the interactive shell:
>>> [1, 2, 3, 4] + ['apples', 'oranges'] + ['Alice', 'Bob']
[1, 2, 3, 4, 'apples', 'oranges', 'Alice', 'Bob']
['apples'] + ['oranges'] will evaluate to ['apples', 'oranges']. But ['apples'] + 'oranges' will result in an error. You can’t add a list value and a string value with the + operator. If you want to add values to the end of a list without using list concatenation, use the append() method (described in “The reverse() and append() List Methods” on page 95).
The in operator can tell you whether a value is in a list or not. Expressions that use the in operator return a Boolean value: True if the value is in the list and False if it isn’t. Enter the following into the interactive shell:
>>> animals = ['aardvark', 'anteater', 'antelope', 'albert']
>>> 'antelope' in animals
True
>>> 'ant' in animals
False
The expression 'antelope' in animals returns True because the string 'antelope' is one of the values in the animals list. It is located at index 2. But when you enter the expression 'ant' in animals, it returns False because the string 'ant' doesn’t exist in the list.
The in operator also works for strings, checking whether one string exists in another. Enter the following into the interactive shell:
>>> 'hello' in 'Alice said hello to Bob.'
True
Storing a list of multiline strings in the HANGMAN_PICS variable covered a lot of concepts. For example, you saw that lists are useful for storing multiple values in a single variable. You also learned some techniques for working with lists, such as index assignment and list concatenation. Methods are another new concept you’ll learn how to use in the Hangman game; we’ll explore them next.
A method is a function attached to a value. To call a method, you must attach it to a specific value using a period. Python has many useful methods, and we’ll use some of them in the Hangman program.
But first, let’s look at some list and string methods.
The list data type has a couple of methods you’ll probably use a lot: reverse() and append(). The reverse() method will reverse the order of the items in the list. Try entering spam = [1, 2, 3, 4, 5, 6, 'meow', 'woof'], and then spam.reverse() to reverse the list. Then enter spam to view the contents of the variable.
>>> spam = [1, 2, 3, 4, 5, 6, 'meow', 'woof']
>>> spam.reverse()
>>> spam
['woof', 'meow', 6, 5, 4, 3, 2, 1]
The most common list method you’ll use is append(). This method will add the value you pass as an argument to the end of the list. Try entering the following into the interactive shell:
>>> eggs = []
>>> eggs.append('hovercraft')
>>> eggs
['hovercraft']
>>> eggs.append('eels')
>>> eggs
['hovercraft', 'eels']
These methods do change the lists they are called on. They don’t return a new list. We say that these methods change the list in place.
The string data type has a split() method, which returns a list of strings made from a string that has been split. Try using the split() method by entering the following into the interactive shell:
>>> sentence = input()
My very energetic mother just served us nachos.
>>> sentence.split()
['My', 'very', 'energetic', 'mother', 'just', 'served', 'us', 'nachos.']
The result is a list of eight strings, one string for each word in the original string. The splitting occurs wherever there is a space in the string. The spaces are not included in any of the items in the list.
Line 38 of the Hangman program also uses the split() method, as shown next. The code is long, but it’s really just a simple assignment statement that has one long string of words separated by spaces, with a split() method call at the end. The split() method evaluates to a list with each word in the string as a single list item.
38. words = 'ant baboon badger bat bear beaver camel cat clam cobra cougar
coyote crow deer dog donkey duck eagle ferret fox frog goat goose hawk
lion lizard llama mole monkey moose mouse mule newt otter owl panda
parrot pigeon python rabbit ram rat raven rhino salmon seal shark sheep
skunk sloth snake spider stork swan tiger toad trout turkey turtle
weasel whale wolf wombat zebra'.split()
It’s easier to write this program using split(). If you created a list to begin with, you would have to type ['ant', 'baboon', 'badger', and so on, with quotes and commas for every word.
You can also add your own words to the string on line 38 or remove any you don’t want to be in the game. Just make sure that spaces separate the words.
Line 40 defines the getRandomWord() function. A list argument will be passed for its wordList parameter. This function will return a single secret word from the list in wordList.
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]
In line 42, we store a random index for this list in the wordIndex variable by calling randint() with two arguments. The first argument is 0 (for the first possible index), and the second is the value that the expression len(wordList) - 1 evaluates to (for the last possible index in a wordList).
Remember that list indexes start at 0, not 1. If you have a list of three items, the index of the first item is 0, the index of the second item is 1, and the index of the third item is 2. The length of this list is 3, but index 3 would be after the last index. This is why line 42 subtracts 1 from the length of wordList. The code on line 42 will work no matter what the size of wordList is. Now you can add or remove strings in wordList if you like.
The wordIndex variable will be set to a random index for the list passed as the wordList parameter. Line 43 will return the element in wordList at the integer stored in wordIndex.
Let’s pretend ['apple', 'orange', grape'] was passed as the argument to getRandomWord() and that randint(0, 2) returned the integer 2. That would mean that line 43 would evaluate to return wordList[2], and then evaluate to return 'grape'. This is how getRandomWord() returns a random string in wordList.
So the input to getRandomWord() is a list of strings, and the return value output is a randomly selected string from that list. In the Hangman game, this is how a secret word is selected for the player to guess.
Next, you need a function to print the Hangman board on the screen. It should also display how many letters the player has correctly (and incorrectly) guessed.
45. def displayBoard(missedLetters, correctLetters, secretWord):
46. print(HANGMAN_PICS[len(missedLetters)])
47. print()
This code defines a new function named displayBoard(). This function has three parameters:
missedLetters A string of the letters the player has guessed that are not in the secret word
correctLetters A string of the letters the player has guessed that are in the secret word
secretWord A string of the secret word that the player is trying to guess
The first print() function call will display the board. The global variable HANGMAN_PICS has a list of strings for each possible board. (Remember that global variables can be read from inside a function.) HANGMAN_PICS[0] shows an empty gallows, HANGMAN_PICS[1] shows the head (when the player misses one letter), HANGMAN_PICS[2] shows the head and body (when the player misses two letters), and so on until HANGMAN_PICS[6], which shows the full hanging man.
The number of letters in missedLetters will reflect how many incorrect guesses the player has made. Call len(missedLetters) to find out this number. So, if missedLetters is 'aetr', then len('aetr') will return 4. Printing HANGMAN_PICS[4] will display the appropriate hanging man picture for four misses. This is what HANGMAN_PICS[len(missedLetters)] on line 46 evaluates to.
Line 49 prints the string 'Missed letters:' with a space character at the end instead of a newline:
49. print('Missed letters:', end=' ')
50. for letter in missedLetters:
51. print(letter, end=' ')
52. print()
The for loop on line 50 will iterate over each character in the string missedLetters and print it on the screen. Remember that end=' ' will replace the newline character that is printed after the string with a single space character. For example, if missedLetters were 'ajtw', this for loop would display a j t w.
The rest of the displayBoard() function (lines 54 to 62) displays the missed letters and creates the string of the secret word with all of the not-yet-guessed letters as blanks. It does this using the range() function and list slicing.
When called with one argument, range() will return a range object of integers from 0 up to (but not including) the argument. This range object is used in for loops but can also be converted to the more familiar list data type with the list() function. Enter list(range(10)) into the interactive shell:
>>> list(range(10))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> list('Hello')
['H', 'e', 'l', 'l', 'o']
The list() function is similar to the str() or int() functions. It takes the value it’s passed and returns a list. It’s easy to generate huge lists with the range() function. For example, enter list(range(10000)) into the interactive shell:
>>> list(range(10000))
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,...
--snip--
...9989, 9990, 9991, 9992, 9993, 9994, 9995, 9996, 9997, 9998, 9999]
The list is so huge, it won’t even fit onto the screen. But you can store the list in a variable:
>>> spam = list(range(10000))
If you pass two integer arguments to range(), the range object it returns is from the first integer argument up to (but not including) the second integer argument. Next enter list(range(10, 20)) into the interactive shell as follows:
>>> list(range(10, 20))
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
As you can see, our list only goes up to 19 and does not include 20.
List slicing creates a new list value using a subset of another list’s items. To slice a list, specify two indexes (the beginning and end) with a colon in the square brackets after the list name. For example, enter the following into the interactive shell:
>>> spam = ['apples', 'bananas', 'carrots', 'dates']
>>> spam[1:3]
['bananas', 'carrots']
The expression spam[1:3] evaluates to a list with items in spam from index 1 up to (but not including) index 3.
If you leave out the first index, Python will automatically think you want index 0 for the first index:
>>> spam = ['apples', 'bananas', 'carrots', 'dates']
>>> spam[:2]
['apples', 'bananas']
If you leave out the second index, Python will automatically think you want the rest of the list:
>>> spam = ['apples', 'bananas', 'carrots', 'dates']
>>> spam[2:]
['carrots', 'dates']
You can also use slices with strings in the same way you use them with lists. Each character in the string is like an item in the list. Enter the following into the interactive shell:
>>> myName = 'Zophie the Fat Cat'
>>> myName[4:12]
'ie the F'
>>> myName[:10]
'Zophie the'
>>> myName[7:]
'the Fat Cat'
The next part of the Hangman code uses slicing.
Now you want to print the secret word, but with blank lines for the letters that haven’t been guessed. You can use the underscore character (_) for this. First create a string with nothing but one underscore for each letter in the secret word. Then replace the blanks for each letter in correctLetters.
So if the secret word were 'otter', then the blanked-out string would be '_____' (five underscores). If correctLetters were the string 'rt', you would change the string to '_tt_r'. Lines 54 to 58 are the part of the code that does that:
54. blanks = '_' * len(secretWord)
55.
56. for i in range(len(secretWord)): # Replace blanks with correctly
guessed letters.
57. if secretWord[i] in correctLetters:
58. blanks = blanks[:i] + secretWord[i] + blanks[i+1:]
Line 54 creates the blanks variable full of underscores using string replication. Remember that the * operator can be used on a string and an integer, so the expression '_' * 5 evaluates to '_____'. This will ensure that blanks has the same number of underscores as secretWord has letters.
Line 56 has a for loop that goes through each letter in secretWord and replaces the underscore with the actual letter if it exists in correctLetters.
Let’s take another look at the previous example, where the value of secretWord is 'otter' and the value in correctLetters is 'tr'. You would want the string '_tt_r' displayed to the player. Let’s figure out how to create this string.
Line 56’s len(secretWord) call would return 5. The range(len(secretWord)) call becomes range(5), which makes the for loop iterate over 0, 1, 2, 3, and 4.
Because the value of i will take on each value in [0, 1, 2, 3, 4], the code in the for loop looks like this:
if secretWord[0] in correctLetters:
blanks = blanks[:0] + secretWord[0] + blanks[1:]
if secretWord[1] in correctLetters:
blanks = blanks[:1] + secretWord[1] + blanks[2:]
--snip--
We’re showing only the first two iterations of the for loop, but starting with 0, i will take the value of each number in the range. In the first iteration, i takes the value 0, so the if statement checks whether the letter in secretWord at index 0 is in correctLetters. The loop does this for every letter in the secretWord, one letter at a time.
If you are confused about the value of something like secretWord[0] or blanks[3:], look at Figure 8-1. It shows the value of the secretWord and blanks variables and the index for each letter in the string.
Figure 8-1: The indexes of the blanks and secretWord strings
If you replace the list slices and the list indexes with the values they represent, the loop code looks like this:
if 'o' in 'tr': # False
blanks = '' + 'o' + '____' # This line is skipped.
--snip--
if 'r' in 'tr': # True
blanks = '_tt_' + 'r' + '' # This line is executed.
# blanks now has the value '_tt_r'.
The preceding code examples all do the same thing when secretWord is 'otter' and correctLetters is 'tr'. The next few lines of code print the new value of blanks with spaces between each letter:
60. for letter in blanks: # Show the secret word with spaces in between
each letter.
61. print(letter, end=' ')
62. print()
Notice that the for loop on line 60 doesn’t call the range() function. Instead of iterating on the range object this function call would return, it iterates on the string value in the blanks variable. On each iteration, the letter variable takes on a new character from the 'otter' string in blanks.
The printed output after the spaces are added would be '_ t t _ r'.
The getGuess() function will be called so that the player can enter a letter to guess. The function returns the letter the player guessed as a string. Further, getGuess() will make sure that the player types a valid letter before it returns from the function.
64. def getGuess(alreadyGuessed):
65. # Returns the letter the player entered. This function makes sure the
player entered a single letter and not something else.
A string of the letters the player has guessed is passed as the argument for the alreadyGuessed parameter. Then the getGuess() function asks the player to guess a single letter. This single letter will be getGuess()’s return value. Now, because Python is case sensitive, we need to make sure the player’s guess is a lowercase letter so we can check it against the secret word. That’s where the lower() method comes in.
Enter 'Hello world!'.lower() into the interactive shell to see an example of the lower() method:
>>> 'Hello world!'.lower()
'hello world!'
The lower() method returns a string with all the characters in lowercase. There is also an upper() method for strings, which returns a string with all the characters in uppercase. Try it out by entering 'Hello world!'.upper() into the interactive shell:
>>> 'Hello world!'.upper()
'HELLO WORLD!'
Because the upper() method returns a string, you can also call a method on that string.
Now enter this into the interactive shell:
>>> 'Hello world!'.upper().lower()
'hello world!'
'Hello world!'.upper() evaluates to the string 'HELLO WORLD!', and then the string’s lower() method is called. This returns the string 'hello world!', which is the final value in the evaluation:
The order is important. 'Hello world!'.lower().upper() isn’t the same as 'Hello world!'.upper().lower():
>>> 'Hello world!'.lower().upper()
'HELLO WORLD!'
That evaluation looks like this:
If a string is stored in a variable, you can also call a string method on that variable:
>>> spam = 'Hello world!'
>>> spam.upper()
'HELLO WORLD!'
This code does not change the value in spam. The spam variable will still contain 'Hello world!'.
Going back to the Hangman program, we use lower() when we ask for the player’s guess:
66. while True:
67. print('Guess a letter.')
68. guess = input()
69. guess = guess.lower()
Now, even if the player enters an uppercase letter as a guess, the getGuess() function will return a lowercase letter.
Line 66’s while loop will keep asking the player for a letter until they enter a single letter that hasn’t been guessed previously.
The condition for the while loop is simply the Boolean value True. That means the only way the execution will ever leave this loop is by executing a break statement, which leaves the loop, or a return statement, which leaves not just the loop but the entire function.
The code inside the loop asks the player to enter a letter, which is stored in the variable guess. If the player entered an uppercase letter, it would be overwritten with a lowercase letter on line 69.
The next part of the Hangman program uses elif statements. You can think of elif or “else-if” statements as saying, “If this is true, do this. Or else if this next condition is true, do that. Or else if none of them is true, do this last thing.” Take a look at the following code:
if catName == 'Fuzzball':
print('Your cat is fuzzy.')
elif catName == 'Spots':
print('Your cat is spotted.')
else:
print('Your cat is not fuzzy or spotted.')
If the catName variable is equal to the string 'Fuzzball', then the if statement’s condition is True and the if block tells the user that their cat is fuzzy. However, if this condition is False, then Python tries the elif statement’s condition next. If catName is 'Spots', then the string 'Your cat is spotted.' is printed to the screen. If both are False, then the code tells the user their cat isn’t fuzzy or spotted.
You can have as many elif statements as you want:
if catName == 'Fuzzball':
print('Your cat is fuzzy.')
elif catName == 'Spots':
print('Your cat is spotted.')
elif catName == 'Chubs':
print('Your cat is chubby.')
elif catName == 'Puff':
print('Your cat is puffy.')
else:
print('Your cat is neither fuzzy nor spotted nor chubby nor puffy.')
When one of the elif conditions is True, its code is executed, and then the execution jumps to the first line past the else block. So one, and only one, of the blocks in the if-elif-else statements will be executed. You can also leave off the else block if you don’t need one and just have if-elif statements.
The guess variable contains the player’s letter guess. The program needs to make sure they entered a valid guess: one, and only one, letter that has not yet been guessed. If they didn’t, the execution will loop back and ask them for a letter again.
70. if len(guess) != 1:
71. print('Please enter a single letter.')
72. elif guess in alreadyGuessed:
73. print('You have already guessed that letter. Choose again.')
74. elif guess not in 'abcdefghijklmnopqrstuvwxyz':
75. print('Please enter a LETTER.')
76. else:
77. return guess
Line 70’s condition checks whether guess is not one character long, line 72’s condition checks whether guess already exists inside the alreadyGuessed variable, and line 74’s condition checks whether guess is not a letter in the standard English alphabet. If any of these conditions are True, the game prompts the player to enter a new guess.
If all of these conditions are False, then the else statement’s block executes, and getGuess() returns the value in guess on line 77.
Remember, only one of the blocks in an if-elif-else statement will be executed.
The playAgain() function has just a print() function call and a return statement:
79. def playAgain():
80. # This function returns True if the player wants to play again;
otherwise, it returns False.
81. print('Do you want to play again? (yes or no)')
82. return input().lower().startswith('y')
The return statement has an expression that looks complicated, but you can break it down. Here’s a step-by-step look at how Python evaluates this expression if the user enters YES:
The point of the playAgain() function is to let the player enter yes or no to tell the program if they want to play another round of Hangman. The player should be able to type YES, yes, Y, or anything else that begins with a y in order to mean “yes.” If the player enters YES, then the return value of input() is the string 'YES'. And 'YES'.lower() returns the lowercase version of the attached string. So the return value of 'YES'.lower() is 'yes'.
But there’s the second method call, startswith('y'). This function returns True if the associated string begins with the string parameter between the parentheses and False if it doesn’t. The return value of 'yes'.startswith('y') is True.
That’s it—you evaluated this expression! It lets the player enter a response, sets the response in lowercase, checks whether it begins with the letter y, and then returns True if it does and False if it doesn’t.
On a side note, there’s also an endswith(someString) string method that will return True if the string ends with the string in someString and False if it doesn’t. endswith() is sort of like the opposite of startswith().
That’s all the functions we’re creating for this game! Let’s review them:
getRandomWord(wordList) Takes a list of strings passed to it and returns one string from it. That is how a word is chosen for the player to guess.
displayBoard(missedLetters, correctLetters, secretWord) Shows the current state of the board, including how much of the secret word the player has guessed so far and the wrong letters the player has guessed. This function needs three parameters passed to it to work correctly. correctLetters and missedLetters are strings made up of the letters that the player has guessed that are in and not in the secret word, respectively. And secretWord is the secret word the player is trying to guess. This function has no return value.
getGuess(alreadyGuessed) Takes a string of letters the player has already guessed and will keep asking the player for a letter that isn’t in alreadyGuessed. This function returns the string of the valid letter the player guessed.
playAgain() Asks if the player wants to play another round of Hangman. This function returns True if the player does, False if they don’t.
After the functions, the code for the main part of the program begins at line 85. Everything up to this point has been just function definitions and a large assignment statement for HANGMAN_PICS.
The main part of the Hangman program displays the name of the game, sets up some variables, and executes a while loop. This section walks through the remainder of the program step by step.
85. print('H A N G M A N')
86. missedLetters = ''
87. correctLetters = ''
88. secretWord = getRandomWord(words)
89. gameIsDone = False
Line 85 is the first print() call that executes when the game is run. It displays the title of the game. Next, blank strings are assigned to the variables missedLetters and correctLetters since the player hasn’t guessed any missed or correct letters yet.
The getRandomWord(words) call at line 88 will evaluate to a randomly selected word from the words list.
Line 89 sets gameIsDone to False. The code will set gameIsDone to True when it wants to signal that the game is over and ask the player whether they want to play again.
The remainder of the program consists of a while loop. The loop’s condition is always True, which means it will loop forever until it encounters a break statement. (This happens later on line 126.)
91. while True:
92. displayBoard(missedLetters, correctLetters, secretWord)
Line 92 calls the displayBoard() function, passing it the three variables set on lines 86, 87, and 88. Based on how many letters the player has correctly guessed and missed, this function displays the appropriate Hangman board to the player.
Next the getGuess() function is called so the player can enter their guess.
94. # Let the player enter a letter.
95. guess = getGuess(missedLetters + correctLetters)
The getGuess() function requires an alreadyGuessed parameter so it can check whether the player enters a letter they’ve already guessed. Line 95 concatenates the strings in the missedLetters and correctLetters variables and passes the result as the argument for the alreadyGuessed parameter.
If the guess string exists in secretWord, then this code concatenates guess to the end of the correctLetters string:
97. if guess in secretWord:
98. correctLetters = correctLetters + guess
This string will be the new value of correctLetters.
How does the program know whether the player has guessed every letter in the secret word? Well, correctLetters has each letter that the player correctly guessed, and secretWord is the secret word itself. But you can’t simply check whether correctLetters == secretWord. If secretWord were the string 'otter' and correctLetters were the string 'orte', then correctLetters == secretWord would be False even though the player has guessed each letter in the secret word.
The only way you can be sure the player has won is to iterate over each letter in secretWord and see if it exists in correctLetters. If, and only if, every letter in secretWord exists in correctLetters has the player won.
100. # Check if the player has won.
101. foundAllLetters = True
102. for i in range(len(secretWord)):
103. if secretWord[i] not in correctLetters:
104. foundAllLetters = False
105. break
If you find a letter in secretWord that doesn’t exist in correctLetters, you know that the player has not guessed all the letters. The new variable foundAllLetters is set to True on line 101 before the loop begins. The loop starts out assuming that all the letters in the secret word have been found. But the loop’s code on line 104 will change foundAllLetters to False the first time it finds a letter in secretWord that isn’t in correctLetters.
If all the letters in the secret word have been found, the player is told they have won, and gameIsDone is set to True:
106. if foundAllLetters:
107. print('Yes! The secret word is "' + secretWord +
'"! You have won!')
108. gameIsDone = True
Line 109 is the start of the else block.
109. else:
110. missedLetters = missedLetters + guess
Remember, the code in this block will execute if the condition was False. But which condition? To find out, point your finger at the start of the else keyword and move it straight up. You’ll see that the else keyword’s indentation is the same as the if keyword’s indentation on line 97:
97. if guess in secretWord:
--snip--
109. else:
110. missedLetters = missedLetters + guess
So if the condition on line 97 (guess in secretWord) were False, then the execution would move into this else block.
Wrongly guessed letters are concatenated to the missedLetters string on line 110. This is like what line 98 did for letters the player guessed correctly.
Each time the player guesses incorrectly, the code concatenates the wrong letter to the string in missedLetters. So the length of missedLetters—or, in code, len(missedLetters)—is also the number of wrong guesses.
112. # Check if player has guessed too many times and lost.
113. if len(missedLetters) == len(HANGMAN_PICS) - 1:
114. displayBoard(missedLetters, correctLetters, secretWord)
115. print('You have run out of guesses!\nAfter ' +
str(len(missedLetters)) + ' missed guesses and ' +
str(len(correctLetters)) + ' correct guesses,
the word was "' + secretWord + '"')
116. gameIsDone = True
The HANGMAN_PICS list has seven ASCII art strings. So when the length of the missedLetters string is equal to len(HANGMAN_PICS) - 1 (that is, 6), the player has run out of guesses. You know the player has lost because the hanging man picture will be finished. Remember, HANGMAN_PICS[0] is the first item in the list, and HANGMAN_PICS[6] is the last one.
Line 115 prints the secret word, and line 116 sets the gameIsDone variable to True.
118. # Ask the player if they want to play again (but only if the game is
done).
119. if gameIsDone:
120. if playAgain():
121. missedLetters = ''
122. correctLetters = ''
123. gameIsDone = False
124. secretWord = getRandomWord(words)
Whether the player won or lost after guessing their letter, the game should ask the player if they want to play again. The playAgain() function handles getting a yes or no from the player, so it is called on line 120.
If the player does want to play again, the values in missedLetters and correctLetters must be reset to blank strings, gameIsDone reset to False, and a new secret word stored in secretWord. This way, when the execution loops back to the beginning of the while loop on line 91, the board will be reset to a fresh game.
If the player didn’t enter something that began with y when asked whether they wanted to play again, then line 120’s condition would be False, and the else block would execute:
125. else:
126. break
The break statement causes the execution to jump to the first instruction after the loop. But because there are no more instructions after the loop, the program terminates.
Hangman has been our most advanced game yet, and you’ve learned several new concepts while making it. As your games get more and more complex, it’s a good idea to sketch out a flowchart of what should happen in your program.
Lists are values that can contain other values. Methods are functions attached to a value. Lists have an append() method. Strings have lower(), upper(), split(), startswith(), and endswith() methods. You’ll learn about many more data types and methods in the rest of this book.
The elif statement lets you add an “or else-if” clause to the middle of your if-else statements.