Опубликован: 12.07.2013 | Доступ: свободный | Студентов: 975 / 36 | Длительность: 37:41:00
Лекция 9:

Hangman

When the Player Guesses Incorrectly

130.    else:

This is the start of the else-block. 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 will see that the else keyword's indentation is the same as the if keyword's indentation on line 118. So if the condition on line 118 was False, then we will run the code in this else-block. Otherwise, we skip down past the else-block to line 140.

131.        missedLetters = missedLetters + guess

Because the player's guessed letter was wrong, we will add it to the missedLetters string. This is like what we did on line 119 when the player guessed correctly.

133.         # Check if player has guessed too many times and lost
134.          if len(missedLetters) == len(HANGMANPICS) - 1:
135.             displayBoard(HANGMANPICS, missedLetters, correctLetters, secretWord)
136.             print('You have run out of guesses!\nAfter ' + str(len(missedLetters)) 
  + ' missed guesses and ' + str (len(correctLetters) ) + ' 
  correct guesses, the word was
" ' + secretWord + '"')
137.                                gameIsDone = True

Think about how we know when the player has guessed too many times. When you play Hangman on paper, this is when the drawing of the hangman is finished. We draw the hangman on the screen with print() calls, based on how many letters are in missedLetters. Remember that each time the player guesses wrong, we add (or as a programmer would say, concatenate) the wrong letter to the string in missedLetters. So the length of missedLetters (or, in code, len(missedLetters)) can tell us the number of wrong guesses.

At what point does the player run out of guesses and lose? Well, the HANGMANPICS list has 7 pictures (really, they are ASCII art strings). So when len(missedLetters) equals 6, we know the player has lost because the hangman picture will be finished. (Remember that HANGMANPICS[0] is the first item in the list, and HANGMANPICS[6] is the last one. This is because the index of a list with 7 items goes from 0 to 6, not 1 to 7.)

So why do we have len(missedLetters) == len(HANGMANPICS) - 1 as the condition on line 134, instead of len(missedLetters) == 6? Pretend that we add another string to the HANGMANPICS list (maybe a picture of the full hangman with a tail, or a third mutant arm). Then the last picture in the list would be at HANGMANPICS [7]. So not only would we have to change the HANGMANPICS list with a new string, but we would also have to remember to change line 134 to len(missedLetters) == 7. This might not be a big deal for a program like Hangman, but when you start writing larger programs you may have to change several different lines of code all over your program just to make a change in the program's behavior. This way, if we want to make the game harder or easier, we just have to add or remove ASCII art strings to HANGMANPICS and change nothing else.

A second reason we user len(HANGMANPICS) - 1 is so that when we read the code in this program later, we know why this program behaves the way it does. If you wrote len(missedLetters) == 6 and then looked at the code two weeks later, you may wonder what is so special about the number 6. You may have forgotten that 6 is the last index in the HANGMANPICS list. Of course, you could write a comment to remind yourself, like:

134. if len(missedLetters) == 6: # 6 is the last index in the HANGMANPICS list

But it is easier to just use len(HANGMANPICS) - 1 instead.

So, when the length of the missedLetters string is equal to len(HANGMANPICS) - 1, we know the player has run out of guesses and has lost the game. We print a long string telling the user what the secret word was, and then set the gameIsDone value to the Boolean value True. This is how we will tell ourselves that the game is done and we should start over.

Remember that when we have \n in a string, that represents the newline character.

139.     # Ask the player if they want to play again (but only if the game is done).
140.      if gameIsDone:
141.          if playAgain() :
142 .                             missedLetters = ' '
143 .                             correctLetters = ' '
144.             gameIsDone = False
145.              secretWord = getRandomWord(words)

If the player won or lost after guessing their letter, then our code would have set the gameIsDone variable to True. If this is the case, we should ask the player if they want to play again. We already wrote the playAgain() function to handle getting a yes or no from the player. This function returns a Boolean value of True if the player wants to play another game of Hangman, and False if they've had enough.

If the player does want to play again, we will reset the values in missedLetters and correctLetters to blank strings, set gameIsDone to False, and then choose a new secret word by calling getRandomWord() again, passing it the list of possible secret words.

This way, when we loop back to the beginning of the loop (on line 112) the board will be back to the start (remember we decide which hangman picture to show based on the length of missedLetters, which we just set as the blank string) and the game will be just as the first time we entered the loop. The only difference is we will have a new secret word, because we programmed getRandomWord() to return a randomly chosen word each time we call it.

There is a small chance that the new secret word will be the same as the old secret word, but this is just a coincidence. Let's say you flipped a coin and it came up heads, and then you flipped the coin again and it also came up heads. Both coin flips were random, it was just a coincidence that they came up the same both times. Accordingly, you may get the same word return from getRandomWord() twice in a row, but this is just a coincidence.

146.        else:
147.                             break

If the player typed in 'no' when asked if they wanted to play again, then they return value of the call to the playAgain() function would be False and the else-block would have executed. This else-block only has one line, a break statement. This causes the execution to jump to the end of the loop that was started on line 112. But because there is no more code after the loop, the program terminates.

Making New Changes to the Hangman Program

This program was much bigger than the Dragon Realm program, but this program is also more sophisticated. It really helps to make a flow chart or small sketch to remember how you want everything to work. Take a look at the flow chart and try to find the lines of code that represent each block.

At this point, you can move on to the next chapter. But I suggest you keep reading on to find out about some ways we can improve our Hangman game.

After you have played Hangman a few times, you might think that six guesses aren't enough to get many of the words. We can easily give the player more guesses by adding more multi-line strings to the HANGMANPICS list. It's easy, just change the ] square bracket on line 58 to a ,''' comma and three quotes (see line 57 below). Then add the following:

58.    ==========''', '''
59 .
60.   +----+
61.     |             |
62.   [O    |
63.   /|\   |
64.   / \   |
65.         |
66. ==========''', '''
67 .
68.   +----+
69.    |    |
70.    [O]           |
71.   /|\        |
72.   / \        | 
73 .                        |
74. = = = = = = = = = = ' ' ' ]

We have added two new multi-line strings to the HANGMANPICS list, one with the hangman's left ear drawn, and the other with both ears drawn. Because our program will tell the player they have lost when the number of guesses is the same as the number of strings in HANGMANPICS (minus one), this is the only change we need to make.

We can also change the list of words by changing the words on line 59. Instead of animals, we could have colors:

59. words = 'red orange yellow green blue indigo violet white black brown'.split()
60. Or shapes:
61. words = 'square triangle rectangle circle ellipse rhombus trapazoid chevron 
 pentagon hexagon septagon
octogon' .split ()
62. Or fruits :
63. words = 'apple orange lemon lime pear watermelon grape grapefruit cherry 
banana cantalope mango strawberry tomato'.split()

Dictionaries

With some modification, we can change our code so that our Hangman game can use all of these words as separate sets. We can tell the player which set the secret word is from (like "animal", "color", "shape", or "fruit"). This way, the player isn't guessing animals all the time.

To make this change, we will introduce a new data type called a dictionary. A dictionary is a collection of other values much like a list, but instead of accessing the items in the dictionary with an integer index, you access them with an index of any data type (but most often strings).

Try typing the following into the shell:

>>> stuff = {'hello':'Hello there, how are you?', 'chat':'How is the weather?', 
'goodbye':'It was nice talking to you!'} >>>

Those are curly braces { and }. On the keyboard they are on the same key as the square braces [ and ]. We use curly braces to type out a dictionary value in Python. The values in between them are key-value pairs. The keys are the things on the left of the colon and the values are on the right of the colon. You can access the values (which are like items in lists) in the dictionary by using the key (which are like indexes in lists). Try typing into the shell stuff['hello'] and stuff['chat'] and stuff['goodbye']:

>>> stuff['hello']
'Hello there, how are you?'
>>> stuff['chat']
'How is the weather?'
>>> stuff['goodbye']
'It was nice talking to you!'
>>>

Getting the Size of Dictionaries with len()

You see, instead of putting an integer index in between the square brackets, you put a key string index. This will evaluate to the value for that key. You can get the size (that is, how many key-value pairs in the dictionary) with the len() function. Try typing len (stuff) into the shell:

>>> len(stuff)
3
>>>

The list version of this dictionary would have only the values, and look something like this:

listStuff = ['Hello there, how are you?', 'How is the weather?', 'It was nice talking to you!']

The list doesn't have any keys, like 'hello' and 'chat' and 'goodbye' in the dictionary. We have to use integer indexes 0, 1, and 2.

The Difference Between Dictionaries and Lists

Dictionaries are different from lists because they are unordered. The first item in a list named listStuff would be listStuff[0]. But there is no "first" item in a dictionary, because dictionaries do not have any sort of order. Try typing this into the shell:

>>> favorites1 = {'fruit':'apples',
'animal':'cats', 'number':42}
>>> favorites2 = {'animal':'cats', 'number':42,
'fruit':'apples'}
>>> favorites1 == favorites2
True
>>>

As you can see, the expression favorites1 == favorites2 evaluates to True because dictionaries are unordered, and they are considered to be the same if they have the same key-value pairs in them. Lists are ordered, so a list with the same values in them but in a different order are not the same. Try typing this into the shell:

>>> listFavs1 = ['apples', 'cats', 42]
>>> listFavs2 = ['cats', 42, 'apples']
>>> listFavs1 == listFavs2
False
>>>

As you can see, the two lists listFavs1 and listFavs2 are not considered to be the same because order matters in lists.

You can also use integers as the keys for dictionaries. Dictionaries can have keys of any data type, not just strings. But remember, because 0 and '0' are different values, they will be different keys. Try typing this into the shell:

>>> myDict = {'0':'a string', 0:'an integer'}
>>> myDict[0]
'an integer'
>>> myDict['0']
'a string'
>>>

You might think that using a for loop is hard with dictionaries because they do not have integer indexes. But actually, it's easy. Try typing the following into the shell. (Here's a hint, in IDLE, you do not have to type spaces to start a new block. IDLE does it for you. To end the block, just insert a blank line by just hitting the Enter key. Or you could start a new file, type in this code, and then press F5 to run the program.)

>>> favorites = {'fruit':'apples', 'animal':'cats',
'number':42}
>>> for i in favorites:
...    
print(i)
fruit
number
animal
>>> for i in favorites:
...    print(favorites[i])
apples 42 cats 
>>>

As you can see, if you just use a dictionary in a for loop, the variable i will take on the values of the dictionary's keys, not its values. But if you have the dictionary and the key, you can get the value as we do above with favorites[i]. But remember that because dictionaries are unordered, you cannot predict which order the for loop will execute in. Above, we typed the 'animal' key as coming before the 'number' key, but the for loop printed out 'number' before 'animal'.

Dictionaries also have two useful methods, keys() and values(). These will return values of a type called dict_keys and dict_values, respectively. Those data types are beyond the scope of this book, but you can easily convert them to lists with the list() function (just like str() converts a value to a string value.) Then you will have an ordered list of the key values and the value values in the dictionary value. Try typing the following into the shell:

>>> favorites = {'fruit':'apples', 'animal':'cats',
'number':42}
>>> list(favorites.keys())
['fruit', 'number', 'animal']
>>> list(favorites.values())
['apples', 42, 'cats']
>>>

Using these methods to get a list of the keys and values that are in a dictionary can be very helpful. Do not forget to convert the return value of dict_keys and dict_keys with the dict_keys function first, otherwise you may get errors in your program.

Sets of Words for Hangman

We will make changes to our original Hangman program. These changes can be downloaded from http://inventwithpython.com/hangman2.py

So how can we use dictionaries in our game? First, let's change the list words into a dictionary whose keys are strings and values are lists of strings. (Remember that the string method split() evaluates to a list.

59. words = {'Colors':'red orange yellow green blue indigo 
violet white black brown'.split(),
60.  'Shapes':'square triangle rectangle circle ellipse 
rhombus trapazoid chevron pentagon hexagon septagon octogon'.split () ,
61.  'Fruits':'apple orange lemon lime pear watermelon grape 
grapefruit cherry banana cantalope mango strawberry tomato'.split(),
62.  '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()}

This code is put across multiple lines in the file, even though the Python interpreter thinks of it as just one "line of code." (The line of code doesn't end until the final } curly brace.)