One of my hobbies is playing microstakes poker tournaments online. Recently, one of the sites that I play on added a new feature: “Spin to Get In.” Essentially, they take the top 20 cards off of a standard deck (10-A of every suits), and stick them in a slot machine. You pay a penny, pull the lever, and if you get three in a row, you win! You can then enter the tournament for essentially free. (So when I say “video poker”, it’s of course, nothing nearly that complex. But it’s still interesting to look at.)

The problem is, having pulled the lever hundreds of times (and throwing away many ones of dollars), I didn’t win once. Statistically, that didn’t seem to pan out. So naturally, needing to dust off my Python anyway, I decided to write a simple Monte Carlo simulation to test this. The basic idea behind a Monte Carlo simulation involves setting up a simulated scenario involving known or assumed probabilities (such as the random deck of 20 cards in deck.py), and then running that scenario over and over to guage the result (plus or minus the expected standard deviations.)

After running 100, 1000, and eventually up to 1,000,000 simulated “hands”, the win rate should be around 2.8%; not exactly what I was getting at the casino.

Wouldn’t you know it. The house always wins.

Full source here: https://github.com/cdwieber/sng-montecarlo

General Summary

The first research I had to do was on algorithms for creating and shuffling card decks. The most concise solution I found was here. My dusty old Python knowledge came back to me pretty quick and I was able to modify the algorithm by putting it in a class, and also swap the numeric ranks with named face values, thusly:

import itertools, random

class Deck:
    def __init__(self):
        # Create a deck of the top 5 cards of each suit.
        self.cards = list(itertools.product(range(10,14),['s','h','d','c']))
        # Shuffle.
        random.shuffle(self.cards)

    # Convert the numeric rank to the card's face value.
    def rank_to_face(self, rank):
        switcher = {
            10: "T",
            11: "J",
            12: "Q",
            13: "K",
            14: "A",
        }
        return switcher.get(rank)

    # Draw the desired number of cards off the top
    def draw(self, count):
        drawn_cards = {}
        for i in range(count):
            drawn_cards[i] = [self.rank_to_face(self.cards[i][0]), self.cards[i][1]]
        return drawn_cards

From there, it was a matter of pulling cards from the deck and displaying the results… hundreds of thousands of times.

from deck import Deck

class MonteCarlo:

    def run(self, iterations, stop_at_win = False, verbose = False):
        i = 1
        win_count = 0
        while i < iterations:
            # Get a new shuffled deck
            d = Deck()
            cards = d.draw(3)
            # Second condition will stop hand from displaying twice in verbose mode
            if verbose == True and cards[0][0] != cards[1][0]:
                print(cards[0][0] + cards[0][1] + 
                ", " + cards[1][0] + cards[1][1] + 
                ", " + cards[2][0] + cards[2][1])
            
            if cards[0][0] == cards[1][0] == cards[2][0]:
                print(cards[0][0] + cards[0][1] + 
                ", " + cards[1][0] + cards[1][1] + 
                ", " + cards[2][0] + cards[2][1])
                print("It's a match! Three of a kind after " + str(i) + " iterations.")
                win_count += 1
                if stop_at_win == True:
                    break
            # Advance iterator.
            i += 1

        # Calculate win rate.
        win_rate = 100 * float(win_count)/float(i)
        # Success message.
        print("Simulation complete. Won " + 
        str(win_count) + 
        " in " + str(i) 
        + " iterations. " + "(" + str(win_rate) + "%)" )

As you can see, I added some conditions to the main program file that allows us to specify the number of hands to run (“iterations”), whether to run in “verbose” mode (i.e., to display every hand that gets calculated, not just the ones that win), and whether or not to stop execution on the first win.

Leave a Reply

Your email address will not be published. Required fields are marked *