CS 21, Spring 2017

Week 4 - Friday

Craps is a casino game in which players standing around a table take turns rolling two dice. Before each round, players are given the opportunity to place a wager on the roller's success or failure. Players typically bet that the round of dice rolling will be successful. If they are right, the casino will double the money wagered. If they are wrong, they lose their money. In the long run, as will all gambling, craps players always lose their money.

A round begins with an initial roll. If the sum of the two dice on this initial roll is 7 or 11, then the player 'wins'. If the sum is 2, 3, or 12 then the player 'loses'. If the sum is anything else (4, 5, 6, 8, 9, or 10) this sum is established as 'point'. The player continues, hoping for a roll whose sum is equal to the point roll. If they achieve such a roll before rolling a 7, then they win. If they roll a 7 before repeating the point roll, they lose, having 'crapped out'. After the roller craps out, the dice pass to the next person around the table.

  1. Write a program, craps.py, that simulates a round of craps. Running your program might look like this:

    $ python craps.py
    You rolled 11
    You win!
    $ python craps.py
    You rolled 3
    You lose :(
    $ python craps.py
    You rolled 6
    Press enter to roll again
    You rolled 11
    Press enter to roll again
    You rolled 4
    Press enter to roll again
    You rolled 7
    You lose :(
    $ python craps.py
    You rolled 4
    Press enter to roll again
    You rolled 5
    Press enter to roll again
    You rolled 4
    You win!
    

    Note that the "Press enter to roll again" bit can be achieved by calling raw_input("Press enter to roll again") without saving the result in a variable. After importing the random library with the line from random import * you can use the randrange function to simulate rolling the two dice.

  2. If your code for part 1 was something like mine, then there was more than one place in the program where you were simulating dice rolling with randrange. Rewrite your code to use a function, rollDice, in these places. This is a good time to use a function, because we'd otherwise have to repeat the same code in more than one spot.

    The rollDice function should have no parameters. It should return the sum of two random dice rolls. Make sure you call this function in main. After this step there should be no calls to randrange within your main.

    def rollDice():
        """
        Purpose: simulate the rolling of two dice
        Parameters: none
        Returns: The sum of the two dice
        """
        # Your code here
  3. Extend the rollDice function so it prints a visual representation of the two dice rolled as a side effect, before returning the sum of the two dice. At this point your program might look like this:

    $ python craps-a.py
    |5| |4|
    You rolled 9
    Please roll again
    |4| |6|
    You rolled 10
    Please roll again
    |4| |5|
    You rolled 9
    You win!
    bash-3.2$ python craps-a.py
    |4| |2|
    You rolled 6
    Please roll again
    |4| |6|
    You rolled 10
    Please roll again
    |4| |3|
    You rolled 7
    You lose :(
    
  4. Now we'll extend your program so you can play multiple rounds of craps. The first step is to rename your main function as playRound. Then create a new main in which you invoke playRound:

    def playRound():
        """
        Your comment here
        """
    
        # What used to be main() here
    
    def main():
        keepPlaying = 'y'
        while keepPlaying == 'y':
            playRound()
            keepPlaying = raw_input("Do you want to play again? (y/n) ")
    
    main()

    Read the code in this new main and try to understand what it's doing. Then run the program to make sure you can really play multiple games without having to re-enter the $ python craps.py command. Notice that now we have defined an auxiliary function, playRound, which itself makes use of another auxiliary function, rollDice. How do you think this would look on the stack diagram?

  5. If in the above version of main the user enters anything other than 'y', we will take that as a no response. That's ok, but we can do better. Create a function getYNAnswer in which you continue asking the user whether they want to play again until you get a valid response, i.e. 'y' or 'n'. Because you don't know how many invalid responses you will get before the user enters something valid, it makes sense to use a while loop here.

    def getYNAnswer():
        """
        Purpose: Get the user's yes/no response to whether they 
                 want to play again.
        Parameters: None
        Returns: 'y' or 'n'
        """

    Running the code at this point might look like:

    $ python craps-a.py
    |6| |5|
    You rolled 11
    You win!
    Do you want to play again? (y/n) yes
    Please enter y or n: no
    Please enter y or n: y
    |1| |2|
    You rolled 3
    You lose :(
    Do you want to play again? (y/n) asdflkj
    Please enter y or n: n
    
  6. Now we have a nice working program that makes use of three auxiliary functions other than main. They are getYNAnswer, playRound, and rollDice. At this point there are a number of interesting ways for you to extend the program.

    One extension is to incorporate the wagering aspect of craps. Give the user some initial stake and before each round ask them how much they want to wager on the round. Update their stake based on whether they win or lose. As 'the house' it's up to you whether you want to let the user go into debt or not.

    Another interesting extension is to turn your program into a simulation that attempts to figure out the odds of winning a round of craps. Take out the print statements and user interaction aspects of the program, except to ask the user how many rounds of craps they want to simulate. Use a for loop to simulate that number of rounds, recording how many wins occur. After the simulation finishes, print the win percentage.

    If you attempt this extension you may want to change the playRound function so it returns some indication of whether the round was a win or a loss. For instance, playRound might return True for a win and False for a loss. You will have converted playRound from a function that was called for its side effects (printing and getting user input) to one that is called for its return value (True to indicate a win, False for a loss).

    Based on the rules of probability, people have calculated that there's about a .493 chance of winning a round of craps. See how close your simulation gets to this figure. If you only simulate a few rounds, the variance will be high and your win rate may be far away from .493. If you simulate many rounds (say on the order of 10000 or 100000) then your figure should be very close to .493.

    A 49.3% chance of doubling your money is about as close to fair as it gets for a casino game. Still 49.3% is less than 50% and over time the house will always come out on top. It doesn't pay to gamble!