Bagels
Bagels
1. import random
2.
3. NUM_DIGITS = 3
4. MAX_GUESS = 10
5.
6. def getSecretNum():
7. # Returns a string of unique random digits that is NUM_DIGITS long.
Instead of using the integer 3 for the number of digits in the answer, we use the
constant variable NUM_DIGITS. The same goes for the number of guesses the player
gets; we use the constant variable MAX_GUESS instead of the integer 10. Now it will be
easy to change the number of guesses or secret number digits. Just change the values
at line 3 or 4, and the rest of the program will still work without any more changes.
The getSecretNum() function generates a secret number that contains only unique
digits. The Bagels game is much more fun if you don’t have duplicate digits in the
secret number, such as '244' or '333'. We’ll use some new Python functions to make this
happen in getSecretNum().
8. numbers = list(range(10))
9. random.shuffle(numbers)
>>> import random
>>> spam = list(range(10))
>>> print(spam)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> random.shuffle(spam)
>>> print(spam)
[3, 0, 5, 9, 6, 8, 2, 4, 1, 7]
>>> random.shuffle(spam)
>>> print(spam)
[9, 8, 3, 5, 4, 7, 1, 2, 0, 6]
10. secretNum = ''
11. for i in range(NUM_DIGITS):
12. secretNum += str(numbers[i])
13. return secretNum
>>> spam = 42
>>> spam = spam + 10
>>> spam
52
>>> eggs = 'Hello '
>>> eggs = eggs + 'world!'
>>> eggs
'Hello world!'
The augmented assignment operators are shortcuts that free you from retyping the
variable name. The following code does the same thing as the previous code:
>>> spam = 42
>>> spam += 10 # The same as spam = spam + 10
>>> spam
52
>>> eggs = 'Hello '
>>> eggs += 'world!' # The same as eggs = eggs + 'world!'
>>> eggs
'Hello world!'
There are other augmented assignment operators as well. Enter the following into
the interactive shell:
>>> spam = 42
>>> spam -= 2
>>> spam
40
>>> spam *= 3
>>> spam
120
>>> spam /= 10
>>> spam
12.0
The statement spam *= 3 is the same as spam = spam * 3. So, since spam was set equal
to 40 earlier, the full expression would be spam = 40 * 3, which evaluates to 120. The
expression spam /= 10 is the same as spam = spam / 10, and spam = 120 / 10 evaluates to 12.0.
Notice that spam becomes a floating point number after it’s divided.
The most obvious step is to check whether the guess is the same as the secret
number, which we do in line 17. In that case, line 18 returns 'You got it!'.
If the guess isn’t the same as the secret number, the program must figure out what
clues to give the player. The list in clues will start empty and
have 'Fermi' and 'Pico' strings added as needed.
The program does this by looping through each possible index
in guess and secretNum. The strings in both variables will be the same length, so line 21
could have used either len(guess) or len(secretNum) and worked the same. As the value
of i changes from 0 to 1 to 2, and so on, line 22 checks whether the first, second, third,
and so on character of guess is the same as the character in the corresponding index
of secretNum. If so, line 23 adds the string 'Fermi' to clues.
Otherwise, line 24 checks whether the number at the ith position in guess exists
anywhere in secretNum. If so, you know that the number is somewhere in the secret
number but not in the same position. In that case, line 25 then adds 'Pico' to clues.
If the clues list is empty after the loop, then you know that there are no correct
digits at all in guess:
26. if len(clues) == 0:
27. return 'Bagels'
When we sort a list of strings, the strings are returned in alphabetical order, but
when we sort a list of numbers, the numbers are returned in numerical order.
On line 29, we use sort() on clues:
29. clues.sort()
The reason you want to sort the clue list alphabetically is to get rid of extra
information that would help the player guess the secret number more easily.
If clues was ['Pico', 'Fermi', 'Pico'], that would tell the player that the center digit of the
guess is in the correct position. Since the other two clues are both Pico, the player
would know that all they have to do to get the secret number is swap the first and third
digits.
If the clues are always sorted in alphabetical order, the player can’t be sure which
number the Fermi clue refers to. This makes the game harder and more fun to play.
The string that the method is called on (on line 30, this is a single space, ' ') appears
between each string in the list. To see an example, enter the following into the
interactive shell:
So the string that is returned on line 30 is each string in clue combined with a single
space between each string. The join() string method is sort of like the opposite of
the split() string method. While split() returns a list from a split-up string, join() returns a
string from a combined list.
Line 34 first checks whether num is the blank string and, if so, returns False.
The for loop then iterates over each character in the string num:
37. for i in num:
38. if i not in '0 1 2 3 4 5 6 7 8 9'.split():
39. return False
40.
41. return True
The value of i will have a single character on each iteration. Inside the for block,
the code checks whether i exists in the list returned by '0 1 2 3 4 5 6 7 8 9'.split(). (The
return value from split() is equivalent to ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'] .) If i doesn’t exist
in that list, you know there’s a nondigit character in num. In that case, line 39
returns False.
But if the execution continues past the for loop, then you know that every character
in num is a digit. In that case, line 41 returns True.
String Interpolation
String interpolation, also known as string formatting, is a coding shortcut. Normally,
if you want to use the string values inside variables in another string, you have to use
the + concatenation operator:
>>> name = 'Alice'
>>> event = 'party'
>>> location = 'the pool'
>>> day = 'Saturday'
>>> time = '6:00pm'
>>> print('Hello, ' + name + '. Will you go to the ' + event + ' at ' +
location + ' this ' + day + ' at ' + time + '?')
Hello, Alice. Will you go to the party at the pool this Saturday at 6:00pm?
As you can see, it can be time-consuming to type a line that concatenates several
strings. Instead, you can use string interpolation, which lets you put placeholders
like %s into the string. These placeholders are called conversion specifiers. Once
you’ve put in the conversion specifiers, you can put all the variable names at the end
of the string. Each %s is replaced with a variable at the end of the line, in the order in
which you entered the variable. For example, the following code does the same thing
as the previous code:
>>> name = 'Alice'
>>> event = 'party'
>>> location = 'the pool'
>>> day = 'Saturday'
>>> time = '6:00pm'
>>> print('Hello, %s. Will you go to the %s at %s this %s at %s?' % (name,
event, location, day, time))
Hello, Alice. Will you go to the party at the pool this Saturday at 6:00pm?
Notice that the first variable name is used for the first %s, the second variable for
the second %s, and so on. You must have the same number of %s conversion specifiers
as you have variables.
Another benefit of using string interpolation instead of string concatenation is that
interpolation works with any data type, not just strings. All values are automatically
converted to the string data type. If you concatenated an integer to a string, you’d get
this error:
>>> spam = 42
>>> print('Spam == ' + spam)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: Can't convert 'int' object to str implicitly
String concatenation can only combine two strings, but spam is an integer. You
would have to remember to put str(spam) instead of spam.
Now enter this into the interactive shell:
>>> spam = 42
>>> print('Spam is %s' % (spam))
Spam is 42
Inside the infinite loop, you get a secret number from the getSecretNum() function.
This secret number is assigned to secretNum. Remember, the value in secretNum is a
string, not an integer.
Line 53 tells the player how many digits are in the secret number by using string
interpolation instead of string concatenation. Line 55 sets the
variable guessesTaken to 1 to mark this is as the first guess. Then line 56 has a
new while loop that loops as long as the player has guesses left. In code, this is
when guessesTaken is less than or equal to MAX_GUESS.
Notice that the while loop on line 56 is inside another while loop that started on line
51. These loops inside loops are called nested loops. Any break or continue statements,
such as the break statement on line 66, will only break or continue out of the innermost
loop, not any of the outer loops.
Getting the Player’s Guess
The guess variable holds the player’s guess returned from input(). The code keeps
looping and asking the player for a guess until they enter a valid guess:
57. guess = ''
58. while len(guess) != NUM_DIGITS or not isOnlyDigits(guess):
59. print('Guess #%s: ' % (guessesTaken))
60. guess = input()
A valid guess has only digits and the same number of digits as the secret number.
The while loop that starts on line 58 checks for the validity of the guess.
The guess variable is set to the blank string on line 57, so the while loop’s condition
on line 58 is False the first time it is checked, ensuring the execution enters the loop
starting on line 59.
62. print(getClues(guess, secretNum))
63. guessesTaken += 1
It returns a string of the clues, which are displayed to the player on line 62. Line 63
increments guessesTaken using the augmented assignment operator for addition.
If guess is the same value as secretNum, the player has correctly guessed the secret
number, and line 66 breaks out of the while loop that was started on line 56. If not, then
execution continues to line 67, where the program checks whether the player ran out
of guesses.
If the player still has more guesses, execution jumps back to the while loop on line
56, where it lets the player have another guess. If the player runs out of guesses (or the
program breaks out of the loop with the break statement on line 66), execution
proceeds past the loop and to line 70.
The player’s response is returned by input(), has the lower() method called on it, and
then the startswith() method called on that to check if the player’s response begins with
a y. If it doesn’t, the program breaks out of the while loop that started on line 51. Since
there’s no more code after this loop, the program terminates.
If the response does begin with y, the program does not execute the break statement
and execution jumps back to line 51. The program then generates a new secret number
so the player can play a new game.
Summary
Bagels is a simple game to program but can be difficult to win. But if you keep
playing, you’ll eventually discover better ways to guess using the clues the game
gives you. This is much like how you’ll get better at programming the more you keep
at it.
This chapter introduced a few new functions and methods— shuffle(), sort(), and join()
—along with a couple of handy shortcuts. Augmented assignment operators require
less typing when you want to change a variable’s relative value; for example, spam =
spam + 1 can be shortened to spam += 1. With string interpolation, you can make your
code much more readable by placing %s (called a conversion specifier) inside the
string instead of using many string concatenation operations.
In Chapter 12, we won’t be doing any programming, but the concepts—Cartesian
coordinates and negative numbers—will be necessary for the games in the later
chapters of the book. These math concepts are used not only in the Sonar Treasure
Hunt, Reversegam, and Dodger games we will be making but also in many other
games. Even if you already know about these concepts, give Chapter 12 a brief read to
refresh yourself.