Hangman
The range() and list() Functions
The range() function is easy to understand. You can call it with either one or two integer arguments. 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 can be converted to the more familiar list data type with the list() function. Try typing list(range (10)) into the shell:
>>> list(range(10)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] >>>
The list() function is very similar to the str() or int() functions. It just converts the object it is passed into a list. It's very easy to generate huge lists with the range() function. Try typing in list(range(10000)) into the shell:
>>> list(range(10000)) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,... ...The text here has been skipped for brevity... ...9989, 9990, 9991, 9992, 9993, 9994, 9995, 9996, 9997, 9998, 9999] >>>
The list is so huge, that it won't even all fit onto the screen. But we can save the list into the variable just like any other list by entering this:
>>> spam = list(range(10000)) >>>
If you pass two arguments to range(), the list of integers it returns is from the first argument up to (but not including) the second argument. Try typing list(range(10, 20)) into the shell:
>>> list(range(10, 20)) [10, 11, 12, 13, 14, 15, 16, 17, 18, 19] >>>
The range() is a very useful function, because we often use it in for loops (which are much like the while loops we have already seen).
for Loops
The for loop is very good at looping over a list of values. This is different from the while loop, which loops as long as a certain condition is true. A for statement begins with the for keyword, followed by a variable, followed by the in keyword, followed by a sequence (such as a list or string) or a range object (returned by the range() function), and then a colon. Each time the program execution goes through the loop (that is, on each iteration through the loop) the variable in the for statement takes on the value of the next item in the list.
For example, you just learned that the range() function will return a list of integers. We will use this list as the for statement's list. In the shell, type for i in range (10): and press Enter. Nothing will happen, but the shell will indent the cursor, because it is waiting for you to type in the for-block. Type print(i) and press Enter. Then, to tell the interactive shell you are done typing in the for-block, press Enter again to enter a blank line. The shell will then execute your for statement and block:
>>> for i in range(10): ... print(i) ... 0 1 2 3 4 5 6 7 8 9 >>>
Notice that with for loops, you do not need to convert the range object returned by the range() function into a list with list(). For loops do this for us automatically.
The for loop executes the code inside the for-block once for each item in the list. Each time it executes the code in the for-block, the variable i is assigned the next value of the next item in the list. If we used the for statement with the list [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] instead of range(10), it would have been the same since the range() function's return value is the same as that list:
>>> for i in range([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]): ... print(i) ... 0 1 2 3 4 5 6 7 8 9 >>>
Try typing this into the shell: for thing in ['cats', 'pasta', 'programming', 'spam']: and press Enter, then type print('I really like ' + thing) and press Enter, and then press Enter again to tell the shell to end the for-block. The output should look like this:
>>> for thing in ['cats', 'pasta', 'programming', 'spam']: ... print('I really like ' + thing) ... I really like cats I really like pasta I really like programming I really like spam >>
And remember, because strings are also a sequence data type just like lists, you can use them in for statements as well. This example uses a single character from the string on each iteration:
>>> for i in 'Hello world!': ... print(i) ... H e l l o w o r l d ! >>>
A while Loop Equivalent of a for Loop
The for loop is very similar to the while loop, but when you only need to iterate over items in a list, using a for loop is much less code to type. You can make a while loop that acts the same way as a for loop by adding extra code:
>>> sequence = ['cats', 'pasta', 'programming', 'spam'] >>> index = 0 >>> while (index < len(sequence)): ... thing = sequence[index] ... print('I really like ' + thing) ... index = index + 1 ... I really like cats I really like pasta I really like programming I really like spam >>>
But using the for statement automatically does all this extra code for us and makes programming easier since we have less to type. Our Hangman game will use for loops so you can see how useful they are in real games.
One more thing about for loops, is that the for statement has the in keyword in it. But when you use the in keyword in a for statement, Python does not treat it like the in operator you would use in something like 42 in [0, 42, 67]. The in keyword in for statements is just used to separate the variable and the list it gets its values from.
The rest of the displayBoard() function displays the missed letters and creates the string of the secret word with all the unguessed letters as blanks.
print('Missed letters:', end=' ') for letter in missedLetters: print(letter, end=' ') print()
This for loop one line 71 will display all the missed guesses that the player has made. When you play Hangman on paper, you usually write down these letters off to the side so you know not to guess them again. On each iteration of the loop the value of letter will be each letter in missedLetters in turn. Remember that the end=' ' will replace the newline character that is printed after the string is replaced by a single space character.
If missedLetters was 'ajtw' then this for loop would display a j t w.
Displaying the Secret Word with Blanks
So by this point we have shown the player the hangman board and the missed letters. Now we want to print the secret word, except we want blank lines for the letters. We can use the _ character (called the underscore character) for this. But we should print the letters in the secret word that the player has guessed, and use _ characters for the letters the player has not guessed yet. We can first create a string with nothing but one underscore for each letter in the secret word. Then we can replace the blanks for each letter in correctLetters. So if the secret word was 'otter' then the blanked out string would be ' _____ ' (five _
characters). If correctLetters was the string 'rt' then we would want to change the blanked string to '_tt_r'. Here is the code that does that:
75. blanks = '_' * len(secretWord) 76 . 77. for i in range(len(secretWord)): # replace blanks with correctly guessed letters 78. if secretWord[i] in correctLetters: 79. blanks = blanks[:i] + secretWord[i] + blanks [i + 1: ] 80 . 81. for letter in blanks: # show the secret word with spaces in between each letter
Line 75 creates the blanks variable full of _ underscores using string replication. Remember that the * operator can also be used on a string and an integer, so the expression 'hello' * 3 evaluates to 'hellohellohello'. This will make sure that blanks has the same number of underscores as secretWord has letters.
Then we use a for loop to go through each letter in secretWord and replace the underscore with the actual letter if it exists in correctLetters. Line 79 may look confusing. It seems that we are using the square brackets with the blanks and secretWord variables. But wait a second, blanks and secretWord are strings, not lists. And the len() function also only takes lists as parameters, not strings. But in Python, many of the things you can do to lists you can also do to strings:
Replacing the Underscores with Correctly Guessed Letters
77. for i in range(len(secretWord)): # replace blanks with correctly guessed letters 78. if secretWord[i] in correctLetters: 79. blanks = blanks[:i] + secretWord[i] + blanks [i + 1: ]
Let's pretend the value of secretWord is 'otter' and the value in correctLetters is 'tr'. Then len(secretWord) will return 5. Then range (len(secretWord)) becomes range(5), which in turn returns the list [0, 1, 2, 3, 4].
Because the value of i will take on each value in [0, 1, 2, 3, 4], then the for loop code is equivalent to this:
if secretWord[0] in blanks = blanks[:0] if secretWord[1] in blanks = blanks[:1] if secretWord[2] in blanks = blanks[:2] if secretWord[3] in blanks = blanks[:3] if secretWord[4] in blanks = blanks[:4] correctLetters: + secretWord[0] correctLetters: + secretWord[1] correctLetters: + secretWord[2] correctLetters: + secretWord[3] correctLetters: + secretWord[4] + blanks[1:] + blanks[2:] + blanks[3:] + blanks[4:] + blanks[5:]
(By the way, writing out the code like this is called loop unrolling.)
If you are confused as to what the value of something like secretWord[0] or blanks[3:] is, then look at this picture. It shows the value of the secretWord and blanks variables, and the index for each letter in the string.
If we replace the list slices and the list indexes with the values that they represent, the unrolled loop code would be the same as this:
if 'o' in 'tr': # Condition is False, blanks == ' _____ ' blanks = '' + 'o' + '____' # This line is skipped. if 't' in 'tr': # Condition is True, blanks == ' _____ ' blanks = '_' + 't' + '___' # This line is executed. if 't' in 'tr': # Condition is True, blanks == '_t___' blanks = '_t' + 't' + '__' # This line is executed. if 'e' in 'tr': # Condition is False, blanks == '_tt__' blanks = '_tt' + 'e' + '_' # This line is skipped. if 'r' in 'tr': # Condition is True, blanks == '_tt__' blanks = '_tt_' + 'r' + '' # This line is executed. # blanks now has the value '_tt_r'
The above three code examples all do the same thing (at least, they do when secretWord is 'otter' and correctLetters is 'tr'. The first box is the actual code we have in our game. The second box shows code that does the same thing except without a for loop. The third box is the same as the second box, except we have evaluated many of the expressions in the second box.
The next few lines of code display the new value of blanks with spaces in between each letter.
81. for letter in blanks: # show the secret word with spaces in between each letter 82. print(letter, end=' ') 83. print()
This for loop will print out each character in the string blanks. Remember that by now, blanks may have some of its underscores replaced with the letters in secretWord. The end keyword argument in line 82's print() call makes the print () function put a space character at the end of the string instead of a newline character. This is the end of the displayBoard() function.
Get the Player's Guess
The getGuess() function we create next will be called whenever we want to let the player type in 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 returning from the function.
85. def getGuess(alreadyGuessed): 86. # Returns the letter the player entered. This function makes sure the player entered a single letter, and not something else.
The getGuess() function has a string parameter called alreadyGuessed which contains the letters the player has already guessed, and will ask the player to guess a single letter. This single letter will be the return value for this function.
87. while True: 88. print('Guess a letter.') 89. guess = input() 90. guess = guess.lower()
We will use a while loop because we want to keep asking the player for a letter until they enter text that is a single letter they have not guessed previously. Notice that the condition for the while loop is simply the Boolean value True. That means the only way execution will ever leave this loop is by executing a break statement (which leaves the loop) or a return statement (which leaves the entire function). Such a loop is called an infinite loop, because it will loop forever (unless it reaches a break statement).
The code inside the loop asks the player to enter a letter, which is stored in the variable guess. If the player entered a capitalized letter, it will be converted to lowercase on line 90.