4.5 Case Study: A Game of Chance

In this section, we simulate the popular dice game known as “craps.” Here is the requirements statement:

You roll two six-sided dice, each with faces containing one, two, three, four, five and six spots, respectively. When the dice come to rest, the sum of the spots on the two upward faces is calculated. If the sum is 7 or 11 on the first roll, you win. If the sum is 2, 3 or 12 on the first roll (called “craps”), you lose (i.e., the “house” wins). If the sum is 4, 5, 6, 8, 9 or 10 on the first roll, that sum becomes your “point.” To win, you must continue rolling the dice until you “make your point” (i.e., roll that same point value). You lose by rolling a 7 before making your point.

The following script simulates the game and shows several sample executions, illustrating winning on the first roll, losing on the first roll, winning on a subsequent roll and losing on a subsequent roll.

Fig. 4.2 | Game of craps.


 1 # fig04_02.py
 2 """Simulating the dice game Craps."""
 3 import random
 4
 5 def roll_dice():
 6     """Roll two dice and return their face values as a tuple."""
 7     die1 = random.randrange(1, 7)
 8     die2 = random.randrange(1, 7)
 9     return (die1, die2)  # pack die face values into a tuple
10
11 def display_dice(dice):
12     """Display one roll of the two dice."""
13     die1, die2 = dice  # unpack the tuple into variables die1 and die2
14     print(f'Player rolled {die1} + {die2} = {sum(dice)}')
15
16 die_values = roll_dice()  # first roll
17 display_dice(die_values)
18
19 # determine game status and point, based on first roll
20 sum_of_dice = sum(die_values)
21
22 if sum_of_dice in (7, 11):  # win
23     game_status = 'WON'
24 elif sum_of_dice in (2, 3, 12):  # lose
25     game_status = 'LOST'
26 else:  # remember point
27     game_status = 'CONTINUE'
28     my_point = sum_of_dice
29     print('Point is', my_point)
30
31 # continue rolling until player wins or loses
32 while game_status == 'CONTINUE':
33     die_values = roll_dice()
34     display_dice(die_values)
35     sum_of_dice = sum(die_values)
36
37     if sum_of_dice == my_point:  # win by making point
38         game_status = 'WON'
39     elif sum_of_dice == 7:  # lose by rolling 7
40         game_status = 'LOST'
41
42 # display "wins" or "loses" message
43 if game_status == 'WON':
44     print('Player wins')
45 else:
46     print('Player loses')

Player rolled 2 + 5 = 7
Player wins

Player rolled 1 + 2 = 3
Player loses

Player rolled 5 + 4 = 9
Point is 9
Player rolled 4 + 4 = 8
Player rolled 2 + 3 = 5
Player rolled 5 + 4 = 9
Player wins

Player rolled 1 + 5 = 6
Point is 6
Player rolled 1 + 6 = 7
Player loses

Function roll_dice—Returning Multiple Values Via a Tuple

Function roll_dice (lines 5–9) simulates rolling two dice on each roll. The function is defined once, then called from several places in the program (lines 16 and 33). The empty parameter list indicates that roll_dice does not require arguments to perform its task.

The built-in and custom functions you’ve called so far each return one value. Sometimes it’s useful to return more than one value, as in roll_dice, which returns both die values (line 9) as a tuple—an immutable (that is, unmodifiable) sequences of values. To create a tuple, separate its values with commas, as in line 9:


(die1, die2)

This is known as packing a tuple. The parentheses are optional, but we recommend using them for clarity. We discuss tuples in depth in the next chapter.

Function display_dice

To use a tuple’s values, you can assign them to a comma-separated list of variables, which unpacks the tuple. To display each roll of the dice, the function display_dice (defined in lines 11–14 and called in lines 17 and 34) unpacks the tuple argument it receives (line 13). The number of variables to the left of = must match the number of elements in the tuple; otherwise, a ValueError occurs. Line 14 prints a formatted string containing both die values and their sum. We calculate the sum of the dice by passing the tuple to the built-in sum function—like a list, a tuple is a sequence.

Note that functions roll_dice and display_dice each begin their blocks with a docstring that states what the function does. Also, both functions contain local variables die1 and die2. These variables do not “collide,” because they belong to different functions’ blocks. Each local variable is accessible only in the block that defined it.

First Roll

When the script begins executing, lines 16–17 roll the dice and display the results. Line 20 calculates the sum of the dice for use in lines 22–29. You can win or lose on the first roll or any subsequent roll. The variable game_status keeps track of the win/loss status.

The in operator in line 22


sum_of_dice in (7, 11)

tests whether the tuple (7, 11) contains sum_of_dice’s value. If this condition is True, you rolled a 7 or an 11. In this case, you won on the first roll, so the script sets game_status to 'WON'. The operator’s right operand can be any iterable. There’s also a not in operator to determine whether a value is not in an iterable. The preceding concise condition is equivalent to


(sum_of_dice == 7) or (sum_of_dice == 11)

Similarly, the condition in line 24


sum_of_dice in (2, 3, 12)

tests whether the tuple (2, 3, 12) contains sum_of_dice’s value. If so, you lost on the first roll, so the script sets game_status to 'LOST'.

For any other sum of the dice (4, 5, 6, 8, 9 or 10):

  • line 27 sets game_status to 'CONTINUE' so you can continue rolling

  • line 28 stores the sum of the dice in my_point to keep track of what you must roll to win and

  • line 29 displays my_point.

Subsequent Rolls

If game_status is equal to 'CONTINUE' (line 32), you did not win or lose, so the while statement’s suite (lines 33–40) executes. Each loop iteration calls roll_dice, displays the die values and calculates their sum. If sum_of_dice is equal to my_point (line 37) or 7 (line 39), the script sets game_status to 'WON' or 'LOST', respectively, and the loop terminates. Otherwise, the while loop continues executing with the next roll.

Displaying the Final Results

When the loop terminates, the script proceeds to the ifelse statement (lines 43–46), which prints 'Player wins' if game_status is 'WON', or 'Player loses' otherwise.

Self Check

  1. (Fill-In) The       operator tests whether its right operand’s iterable contains its left operand’s value.
    Answer: in.

  2. (IPython Session) Pack a student tuple with the name 'Sue' and the list [89, 94, 85], display the tuple, then unpack it into variables name and grades, and display their values.
    Answer:

    
    In [1]: student = ('Sue', [89, 94, 85])
    
    In [2]: student
    Out[2]: ('Sue', [89, 94, 85])
    
    In [3]: name, grades = student
    
    In [4]: print(f'{name}: {grades}')
    Sue: [89, 94, 85]