Simulating Craps with Monte Carlo Simulation in Python

Learning objectives

In the core text, we introduced the following problem as a simple illustration of Monte Carlo simulation. We also introduced the rules of a simplified form of the game craps, in which a player rolls two dice, which sum to x, resultin in three possibilities. (These rules represent the “come bet” wager in craps, which is very common.)

  1. If is 7 or 11, then the player wins, and receives their wager back in addition to the amount of the wager.
  2. If x is 2, 3, or 12, then the player loses the wager.
  3. If has some other value, then the player continues to roll the dice. On these subsequent rolls, the game stops if is rolled, in which case the player wins, or if 7 is rolled, in which case the player loses. (Note that this is different to the first roll, when 7 is a winner.)

We then defined a game’s house edge is the amount that a player will lose on average per unit of currency wagered, which led us to the following computational problem.

Craps House Edge Problem

Input: An integer numTrials.

Output: An estimate of the house edge of craps resulting from running numTrials randomized simulations.

To solve this problem, we ntroduced the following pseudocode function ComputeCrapsHouseEdge().

ComputeCrapsHouseEdge(numTrials)
    count ← 0
    for numTrials total trials
        outcome ← PlayCrapsOnce()
        if outcome = true
            count ← count + 1
        else
            count ← count − 1
    return count/numTrials

This function in turn relies on a subroutine PlayCrapsOnce() that simulates craps once and returns true if the player wins and false otherwise. In turn, PlayCrapsOnce() relies on a function SumTwoDice() that we implemented in the previous code along.

PlayCrapsOnce()
    numDice ← 2
    firstRoll ← SumDice(numDice)
    if firstRoll = 2, 3, or 12
        return false (player loses)
    else if firstRoll = 7 or 11
        return true (player wins)
    else
        while true
            newRoll ← SumDice(numDice)
            if newRoll = firstRoll 
               return true
            else if newRoll = 7
                return false

In this code along, we will implement the above two functions in Python and then run a large number of trials to estimate the house edge of craps.

Code along summary

Setup

We will continue editing the main.py file that we created in the previous code along within the python/src/dice source code directory. This file should already contain sum_dice() and roll_die() functions as follows; note that we have simplified def main() down to a single line.

import random

def main():
    print("Rolling dice and playing craps.")

if __name__ == "__main__":
    main()

def sum_dice(num_dice: int) -> int:
    """
    Simulates the process of summing n dice.

    Parameters:
    - num_dice (int): The number of dice to sum.

    Returns:
    - int: The sum of num_dice simulated dice.
    """
    total = 0
    for _ in range(num_dice):
        total += roll_die()
    return total


def roll_die() -> int:
    """
    Simulates the roll of a die.

    Returns:
    - int: A pseudorandom integer between 1 and 6, inclusively.
    """
    return random.randrange(1, 7)

Estimating the house edge of craps

We first implement compute_craps_house_edge(), which takes as input an integer num_trials. It calls play_craps_once() for a total of num_trials times, keeping track of how much is won or lost in an integer variable count, and then returns the average per-trial amount won (as a positive number) or lost (as a negative number).

def compute_craps_house_edge(num_trials: int) -> float:
    """
    Estimates the "house edge" of craps over multiple simulations.

    Parameters:
    - num_trials (int): The number of simulations.

    Returns:
    - float: The house edge of craps (average amount won or lost per game, per unit bet).

    Positive means player profit; negative means house profit.
    """
    if num_trials <= 0:
        raise ValueError("num_trials must be a positive integer.")

    count = 0
    for _ in range(num_trials):
        outcome = play_craps_once()
        # did we win or lose?
        if outcome:
            count += 1  # win
        else:
            count -= 1  # loss

    return count / num_trials   # averaging total won/lost over num_trials

Playing craps once

We next implement play_craps_once(); the pseudocode for this function contains a while true statement ensuring that, if the sum of dice x on our first roll is anything other than 2, 3, 7, 11, or 12, then we continue to roll until the dice sum to x or 7, for as long as needed. Such a statement is a little nerve-wracking, since it could produce an infinite loop, although no one playing craps expects that they might stand at the table rolling dice forever.

In Python, we will implement this loop by using while True:

def play_craps_once() -> bool:
    """
    Simulates one game of craps.

    Returns:
    - bool: True if the game is a win, False if it's a loss.
    """
    first_roll = sum_dice(2)

    if first_roll == 7 or first_roll == 11:
        return True
    elif first_roll == 2 or first_roll == 3 or first_roll == 12:
        return False
    else:
        while True:
            new_roll = sum_dice(2)
            if new_roll == first_roll:
                return True
            elif new_roll == 7:
                return False

Let’s update main() to play the game a few times. We wish you luck!

def main():
    print("Rolling dice and playing craps.")

    for _ in range(10):
        outcome = play_craps_once()
        if outcome:
            print("Winner!")
        else:
            print("Loser :(")
STOP: In a new terminal window, navigate into our directory using cd python/src/dice. Then run the code by executing python3 main.py (macOS/Linux) or python main.py (Windows).

Running our simulation to estimate the craps house edge

We are ready to run the craps simulation in def main(). Let’s estimate the house edge first for a value of num_trials equal to 1000.

def main():
    num_trials = 1000
    if num_trials <= 0:
        raise ValueError("num_trials must be a positive integer.")

    print("Rolling dice...")
    edge = compute_craps_house_edge(num_trials)
    print(f"Estimated house edge with {num_trials} trials is: {edge:.6f}")
    print(f"That’s about {edge * 100:.3f}% of the amount wagered per bet.")

In our own run, we obtained a printed value of -0.028, meaning that for every dollar the player wagers in our simulation, they lose 2.8 cents. However, results will differ because of (pseudo)random chance, and so we should run our simulation for more trials to hone in on a value.

STOP: Set num_trials equal to 10,000, 100,000 and then 1 million, and run the simulation again for each value. What value do you obtain?

When we run our simulation with num_trials equal to 10 million, the simulation takes a few seconds to finish, but we obtain a more accurate estimation of approximately -0.014. This is a more accurate value and the published house edge of craps; for every dollar the player wagers, the house nets 1.4 cents. It may take many trials, but the house always wins.

Looking ahead

Now that we have grown comfortable with the basics of Monte Carlo simulation, we are ready to expand our work to simulate a presidential election from polling data. In the next code along, we will start this simulation by learning how to read in polling data from files.

Check your work from the code along

We now provide autograders in the window below (or via a direct link) allowing you to check your work for the following functions:

  • play_craps_once()
  • compute_craps_house_edge()

powered by Advanced iFrame

 

Scroll to Top
Programming for Lovers banner no background
programming for lovers logo cropped

Join our community!

programming for lovers logo cropped
Programming for Lovers banner no background

Join our community!