0% found this document useful (0 votes)
12 views15 pages

Bagels

The document describes the Bagels number guessing game and provides sample code to implement it in Python. The game has the computer generate a random 3-digit number for the player to guess. After each guess, the computer provides clues like "Fermi" if the player has a correct digit in the right spot or "Pico" if a digit is correct but in the wrong spot. The code shares how it generates a random secret number with unique digits, compares guesses to the secret, and provides clues to guide the player.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
12 views15 pages

Bagels

The document describes the Bagels number guessing game and provides sample code to implement it in Python. The game has the computer generate a random 3-digit number for the player to guess. After each guess, the computer provides clues like "Fermi" if the player has a correct digit in the right spot or "Pico" if a digit is correct but in the wrong spot. The code shares how it generates a random secret number with unique digits, compares guesses to the secret, and provides clues to guide the player.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 15

11

THE BAGELS DEDUCTION GAME


Bagels is a deduction game in which the player tries to guess a random three-digit
number (with no repeating digits) generated by the computer. After each guess, the
computer gives the player three types of clues:
Bagels None of the three digits guessed is in the secret number.
Pico One of the digits is in the secret number, but the guess has the digit in the
wrong place.
Fermi The guess has a correct digit in the correct place.
The computer can give multiple clues, which are sorted in alphabetical order. If the
secret number is 456 and the player’s guess is 546, the clues would be “fermi pico
pico.” The “fermi” is from the 6 and “pico pico” are from the 4 and 5.
In this chapter, you’ll learn a few new methods and functions that come with
Python. You’ll also learn about augmented assignment operators and string
interpolation. While they don’t let you do anything you couldn’t do before, they are
nice shortcuts to make coding easier.
TOPICS COVERED IN THIS CHAPTER
• The random.shuffle() function
• Augmented assignment operators, +=, -=, *=, /=
• The sort() list method
• The join() string method
• String interpolation
• The conversion specifier %s
• Nested loops

Sample Run of Bagels


Here’s what the user sees when they run the Bagels program. The text the player
enters is shown in bold.

I am thinking of a 3-digit number. Try to guess what it is.


The clues I give are...
When I say:    That means:
  Bagels       None of the digits is correct.
  Pico         One digit is correct but in the wrong position.
  Fermi        One digit is correct and in the right position.
I have thought up a number. You have 10 guesses to get it.
Guess #1:
123
Fermi
Guess #2:
453
Pico
Guess #3:
425
Fermi
Guess #4:
326
Bagels
Guess #5:
489
Bagels
Guess #6:
075
Fermi Fermi
Guess #7:
015
Fermi Pico
Guess #8:
175
You got it!
Do you want to play again? (yes or no)
no

Source Code for Bagels


In a new file, enter the following source code and save it as bagels.py. Then run the
game by pressing F5. If you get errors, compare the code you typed to the book’s code
with the online diff tool at https://www.nostarch.com/inventwithpython#diff.
bagels.py

 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.
 8.     numbers = list(range(10))
 9.     random.shuffle(numbers)
10.     secretNum = ''
11.     for i in range(NUM_DIGITS):
12.         secretNum += str(numbers[i])
13.     return secretNum
14.
15. def getClues(guess, secretNum):
16.     # Returns a string with the Pico, Fermi, & Bagels clues to the user.
17.     if guess == secretNum:
18.         return 'You got it!'
19.
20.     clues = []
21.     for i in range(len(guess)):
22.         if guess[i] == secretNum[i]:
23.             clues.append('Fermi')
24.         elif guess[i] in secretNum:
25.             clues.append('Pico')
26.     if len(clues) == 0:
27.         return 'Bagels'
28.
29.     clues.sort()
30.     return ' '.join(clues)
31.
32. def isOnlyDigits(num):
33.     # Returns True if num is a string of only digits. Otherwise, returns
          False.
34.     if num == '':
35.         return False
36.
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
42.
43.
44. print('I am thinking of a %s-digit number. Try to guess what it is.' %
      (NUM_DIGITS))
45. print('The clues I give are...')
46. print('When I say:    That means:')
47. print(' Bagels        None of the digits is correct.')
48. print(' Pico          One digit is correct but in the wrong position.')
49. print(' Fermi         One digit is correct and in the right position.')
50.
51. while True:
52.     secretNum = getSecretNum()
53.     print('I have thought up a number. You have %s guesses to get it.' %
          (MAX_GUESS))
54.
55.     guessesTaken = 1
56.     while guessesTaken <= MAX_GUESS:
57.         guess = ''
58.         while len(guess) != NUM_DIGITS or not isOnlyDigits(guess):
59.             print('Guess #%s: ' % (guessesTaken))
60.             guess = input()
61.
62.         print(getClues(guess, secretNum))
63.         guessesTaken += 1
64.
65.         if guess == secretNum:
66.             break
67.         if guessesTaken > MAX_GUESS:
68.             print('You ran out of guesses. The answer was %s.' %
                  (secretNum))
69.
70.     print('Do you want to play again? (yes or no)')
71.     if not input().lower().startswith('y'):
72.         break

Flowchart for Bagels


The flowchart in Figure 11-1 describes what happens in this game and the order in
which each step can happen.
The flowchart for Bagels is pretty simple. The computer generates a secret
number, the player tries to guess that number, and the computer gives the player clues
based on their guess. This happens over and over again until the player either wins or
loses. After the game finishes, whether the player won or not, the computer asks the
player whether they want to play again.

Figure 11-1: Flowchart for the Bagels game

Importing random and Defining getSecretNum()


At the start of the program, we’ll import the random module and set up some global
variables. Then we’ll define a function named getSecretNum().

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().

Shuffling a Unique Set of Digits


The first two lines of getSecretNum() shuffle a set of nonrepeating numbers:

8.     numbers = list(range(10))
9.     random.shuffle(numbers)

Line 8’s list(range(10)) evaluates to [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] , so the numbers variable


contains a list of all 10 digits.

Changing List Item Order with the random.shuffle() Function


The random.shuffle() function randomly changes the order of a list’s items (in this case,
the list of digits). This function doesn’t return a value but rather modifies the list you
pass it in place. This is similar to the way the makeMove() function in Chapter 10’s Tic-
Tac-Toe game modified the list it was passed in place, rather than returning a new list
with the change. This is why you do not write code like numbers =
random.shuffle(numbers).
Try experimenting with the shuffle() function by entering the following code into
the interactive shell:

>>> 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]

Each time random.shuffle() is called on spam, the items in the spam list are shuffled.


You’ll see how we use the shuffle() function to make a secret number next.

Getting the Secret Number from the Shuffled Digits


The secret number will be a string of the first NUM_DIGITS digits of the shuffled list of
integers:

10.     secretNum = ''
11.     for i in range(NUM_DIGITS):
12.         secretNum += str(numbers[i])
13.     return secretNum

The secretNum variable starts out as a blank string. The for loop on line 11


iterates NUM_DIGITS number of times. On each iteration through the loop, the integer
at index i is pulled from the shuffled list, converted to a string, and concatenated to the
end of secretNum.
For example, if numbers refers to the list [9, 8, 3, 5, 4, 7, 1, 2, 0, 6] , then on the first
iteration, numbers[0] (that is, 9) will be passed to str(); this returns '9', which is
concatenated to the end of secretNum. On the second iteration, the same happens
with numbers[1] (that is, 8), and on the third iteration the same happens
with numbers[2] (that is, 3). The final value of secretNum that is returned is '983'.
Notice that secretNum in this function contains a string, not an integer. This may
seem odd, but remember that you cannot concatenate integers. The expression 9 + 8 +
3 evaluates to 20, but what you want is '9' + '8' + '3', which evaluates to '983'.

Augmented Assignment Operators


The += operator on line 12 is one of the augmented assignment operators. Normally,
if you want to add or concatenate a value to a variable, you use code that looks like
this:

>>> 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

The statement spam –= 2 is the same as the statement spam = spam – 2, so the


expression evaluates to spam = 42 – 2 and then to spam = 40.
There are augmented assignment operators for multiplication and division, too:

>>> 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.

Calculating the Clues to Give


The getClues() function will return a string with fermi, pico, and bagels clues depending
on the guess and secretNum parameters.

15. def getClues(guess, secretNum):


16.     # Returns a string with the Pico, Fermi, & Bagels clues to the user.
17.     if guess == secretNum:
18.         return 'You got it!'
19.
20.     clues = []
21.     for i in range(len(guess)):
22.         if guess[i] == secretNum[i]:
23.             clues.append('Fermi')
24.         elif guess[i] in secretNum:
25.             clues.append('Pico')

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'

In this case, line 27 returns the string 'Bagels' as the only clue.

The sort() List Method


Lists have a method named sort() that arranges the list items in alphabetical or
numerical order. When the sort() method is called, it doesn’t return a sorted list but
rather sorts the list in place. This is just like how the shuffle() method works.
You would never want to use return spam.sort() because that would return the
value None. Instead you want a separate line, spam.sort(), and then the line return spam.
Enter the following into the interactive shell:
>>> spam = ['cat', 'dog', 'bat', 'anteater']
>>> spam.sort()
>>> spam
['anteater', 'bat', 'cat', 'dog']
>>> spam = [9, 8, 3, 5.5, 5, 7, 1, 2.1, 0, 6]
>>> spam.sort()
>>> spam
[0, 1, 2.1, 3, 5, 5.5, 6, 7, 8, 9]

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 join() String Method


The join() string method returns a list of strings as a single string joined together.

30.     return ' '.join(clues)

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:

>>> ' '.join(['My', 'name', 'is', 'Zophie'])


'My name is Zophie'
>>> ', '.join(['Life', 'the Universe', 'and Everything'])
'Life, the Universe, and Everything'

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.

Checking Whether a String Has Only Numbers


The isOnlyDigits() function helps determine whether the player entered a valid guess:

32. def isOnlyDigits(num):


33.     # Returns True if num is a string of only digits. Otherwise, returns
          False.
34.     if num == '':
35.         return False

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.

Starting the Game


After all of the function definitions, line 44 is the actual start of the program:

44. print('I am thinking of a %s-digit number. Try to guess what it is.' %


      (NUM_DIGITS))
45. print('The clues I give are...')
46. print('When I say:    That means:')
47. print('  Bagels       None of the digits is correct.')
48. print('  Pico         One digit is correct but in the wrong position.')
49. print('  Fermi        One digit is correct and in the right position.')
The print() function calls tell the player the rules of the game and what the pico,
fermi, and bagels clues mean. Line 44’s print() call has % (NUM_DIGITS) added to the
end and %s inside the string. This is a technique known as string interpolation.

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

With string interpolation, this conversion to strings is done for you.

The Game Loop


Line 51 is an infinite while loop that has a condition of True, so it will loop forever until
a break statement is executed:

51. while True:


52.     secretNum = getSecretNum()
53.     print('I have thought up a number. You have %s guesses to get it.' %
          (MAX_GUESS))
54.
55.     guessesTaken = 1
56.     while guessesTaken <= MAX_GUESS:

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.

Getting the Clues for the Player’s Guess


After execution gets past the while loop that started on line 58, guess contains a valid
guess. Now the program passes guess and secretNum to the getClues() function:

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.

Checking Whether the Player Won or Lost


Now we figure out if the player won or lost the game:

65.         if guess == secretNum:


66.             break
67.         if guessesTaken > MAX_GUESS:
68.             print('You ran out of guesses. The answer was %s.' %
                  (secretNum))

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.

Asking the Player to Play Again


Line 70 asks the player whether they want to play again:

70.     print('Do you want to play again? (yes or no)')


71.     if not input().lower().startswith('y'):
72.         break

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.

You might also like