Gambling & Probability (Python)

Written by ethan.jarrell | Published 2018/03/30
Tech Story Tags: mathematics | python | java | gambling | profitability

TLDRvia the TL;DR App

I recently became interested it the probability of certain outcomes. Python gives us a great way to test scenarios to see how likely certain scenarios are, by allowing you to run certain scenarios millions and millions of times, and calculate the number or times in a certain outcome happens.. Take, for example, a coin toss. We might be interested in seeing the probability of either a heads or tails outcome on any given coin toss. We could run a million coin tosses, and find out how likely each scenario is.

With coin tosses, etc, the outcomes depend largely on a random choice between two or more possibilities. Python includes a library called ‘random’, which you can use by importing the library with import random. Using random, I set up a script to determine a heads or tails probability, and ran the scenario 1 million times. I found the following:

Total Heads: 499,588

Total Tails: 500,412

Most Heads in a row: 19

Most Tails in a row: 19

Pretty interesting right? This provides some useful insight, because knowing the probability of a particular run of any thing, would help us determine how much to increase our wager after each coin is flipped.

So then, I decided to get a little more complex, and set up a test in Python that would simulate a game of blackjack. Now, I didn’t program both a dealer and player, so this isn’t technically an actual game. What I wanted to know though, was the probability of getting any particular value after the first two cards are dealt. So, out of 1 million rounds, what’s the probability of the first two cards adding up to 2, or 3 or 4, etc.

This alone creates an interesting problem, as the Ace could be either a value of 1 or 11, so before I get to determining the value of the Ace, if it occurs in the hand, I want to first just run through the code.

If you want to follow along, Here’s my github for the project: https://github.com/ethanjarrell/blackJackPython

First things first, in order to use determine a new card, I know I’ll want the random library, so at the top of my page, I’m importing random again.

import randomiterations = 1000000

I’m also creating a variable called iterations. If I want to run this 1 million times, or only 1 hundred times, I can just change this variable here, instead of having to change my math in multiple locations throughout the script.

Next, I’m creating a variable that I’m calling sumValue. Basically, all I’m going to actually keep track of is the sum after each card is drawn. When the first two cards are drawn, I want the initial sum. Then, if I draw a third card, I only want to know if the current sum is over 21. And the the same thing at the 4th card draw.

It helps me to draw things out, so here’s sort of what I have in mind:

Depending on the initial value of each of the first two cards, I want to know several things, like the number of times that total occurs. So it doesn’t matter what the first two cards are, just how often that total occurs. Then, based on the initial total, I want to know what my chances are of going over 21 at each card draw. Here, I just made these numbers up, but that’s the basic idea.

Translating this idea to Python, it seems like the best way to keep track of this is with a series of lists. 2, 3, 4, 5, 6 and so on will each be a Python list, and then as I draw cards and get new values, I’ll add to that list. Sort of like this:

So going back to my sumValue variable, sumValue will be a list of lists. Most of each sublist will be placeholder numbers which I will set to zero.

sumValue = [["2: ", 0, 0, 0, 0, 0, 0, 0, 0], ["3: ", 0, 0, 0, 0, 0, 0, 0, 0], ["4: ", 0, 0, 0, 0, 0, 0, 0, 0], ["5: ", 0, 0, 0, 0, 0, 0, 0, 0], ["6: ", 0, 0, 0, 0, 0, 0, 0, 0], ["7: ", 0, 0, 0, 0, 0, 0, 0, 0], ["8: ", 0, 0, 0, 0, 0, 0, 0, 0], ["9: ", 0, 0, 0, 0, 0, 0, 0, 0], ["10: ", 0, 0, 0, 0, 0, 0, 0, 0], ["11: ", 0, 0, 0, 0, 0, 0, 0, 0], ["12: ", 0, 0, 0, 0, 0, 0, 0, 0], ["13: ", 0, 0, 0, 0, 0, 0, 0, 0], ["14: ", 0, 0, 0, 0, 0, 0, 0, 0], ["15: ", 0, 0, 0, 0, 0, 0, 0, 0], ["16: ", 0, 0, 0, 0, 0, 0, 0, 0], ["17: ", 0, 0, 0, 0, 0, 0, 0, 0], ["18: ", 0, 0, 0, 0, 0, 0, 0, 0], ["19: ", 0, 0, 0, 0, 0, 0, 0, 0], ["20: ", 0, 0, 0, 0, 0, 0, 0, 0], ["21: ", 0, 0, 0, 0, 0, 0, 0, 0], ["21+: ", 0, 0, 0, 0, 0, 0, 0, 0]]

As you can see, each sub list has 8 indexes. Here’s how I intend to use each index:

Index 0: the current list.

Index 1: the number of times the value of this list occurs after 2 cards.

Index 2: The percentage of times this value occurs after 2 cards.

Index 3: The number of times a third card is drawn and the sum isn’t over 21.

index 4: The percentage of times this sum is not over 21, based on the initial sum.

Index 5: The number of times a 4th card is drawn and the sum isn’t over 21.

index 6: The percentage of times this sum is not over 21, based on the initial sum.

Index 7: The number of times a 5th card is drawn and the sum isn’t over 21.

index 8: The percentage of times this sum is not over 21, based on the initial sum.

Feel free to check my github, and give me some feedback on how I generated everything. Here’s the output I got, which was pretty cool, based on 1 million simulated hands:

['2: ', 4407, 0.4407, 4407, 100.0, 3611, 81.9378261856138, 1622, 44.918305178620884]['3: ', 12242, 1.2242, 12242, 100.0, 9461, 77.28312367260251, 3754, 39.67868090053906]['4: ', 16655, 1.6655, 16655, 100.0, 11803, 70.8676073251276, 4461, 37.79547572651021]['5: ', 24185, 2.4185, 24185, 100.0, 15744, 65.09820136448212, 5311, 33.733485772357724]['6: ', 28641, 2.8641, 28641, 100.0, 16746, 58.46862888865612, 5186, 30.96858951391377]['7: ', 36233, 3.6233, 36233, 100.0, 18682, 51.56073192945657, 5175, 27.700460336152446]['8: ', 40852, 4.0852, 40852, 100.0, 18091, 44.284245569372366, 4668, 25.80288541263612]['9: ', 48010, 4.801, 44060, 91.77254738596126, 17717, 40.211075805719474, 4134, 23.333521476547947]['10: ', 52891, 5.2891, 44294, 83.7458168686544, 16580, 37.43170632591322, 3451, 20.814234016887816]['11: ', 60226, 6.0226, 45693, 75.86922591571746, 15375, 33.64848007353424, 2967, 19.297560975609755]['12: ', 64898, 6.4898, 44762, 68.97284970261025, 13456, 30.061212635717798, 2244, 16.676575505350772]['13: ', 72088, 7.2088, 44059, 61.11835534346909, 11615, 26.362377720783496, 1602, 13.792509685751185]['14: ', 77072, 7.7072, 41540, 53.897654141581896, 9312, 22.416947520462205, 1042, 11.189862542955327]['15: ', 72808, 7.2808, 33722, 46.31633886386111, 6446, 19.115117727299687, 588, 9.121936084393422]['16: ', 64783, 6.4783, 25256, 38.98553632897519, 3841, 15.208267342413684, 240, 6.248372819578234]['17: ', 60308, 6.0308, 18896, 31.3324932015653, 2196, 11.621507197290432, 85, 3.8706739526411655]['18: ', 52487, 5.2487, 12599, 24.00403909539505, 926, 7.349789665846496, 11, 1.187904967602592]['19: ', 48301, 4.8301, 7708, 15.958261733711517, 239, 3.1006746237675142, 0, 0.0]['20: ', 40825, 4.0825, 3244, 7.946111451316595, 0, 0.0, 0, 0]['21: ', 36151, 3.6151, 0, 0.0, 0, 0, 0, 0]['21+: ', 85937, 8.5937, 0, 0.0, 0, 0, 0, 0]

[Finished in 29.804s]

Now, most of this data seems pretty obvious, and in reality, you don’t need Python to tell you that you’re less likely to go over 21 when you draw two low cards. But it’s still fun to play around with!

You may remember I mentioned earlier the issue of dealing with Aces. This output reflects dealing with Aces as only a value of 1, instead of 11.

So here’s the first bit of my function:

def hand():deck = []value = range(1, 14)for i in value:if i == 10 or i == 11 or i == 12 or i == 13:i = 10

    d = "D-"+str(i)  
    c = "C-"+str(i)  
    s = "S-"+str(i)  
    h = "H-"+str(i)  
    deck.append(d)  
    deck.append(c)  
    deck.append(s)  
    deck.append(h)  
hand = \[\]  
hand2 = \[\]  
card1 = random.choice(deck)  
hand.append(card1)  
if card1 in deck:  
    deck.remove(card1)  
card2 = random.choice(deck)  
hand.append(card2)  
if card2 in deck:  
    deck.remove(card2)

value1 = int(hand[0].split("-")[1])value2 = int(hand[1].split("-")[1])

Basically, my function, called hand(), has a list called deck. since each suite has cards with a value starting at 1(ace) and ending with 13(king) . However, one difference between Poker and Blackjack is that, in poker, the value of 10 is 10, Jack is 11, Queen is 12 and King is 13. However, in Blackjack, Jack, Queen and King are all 10. So I start with a range of the values 1–13.

value = range(1, 14)

Next I loop through it to create my cards, but, if the value is 10 through 13, I change it to a 10.

for i in value:if i == 10 or i == 11 or i == 12 or i == 13:i = 10

Now, for BlackJack, the suits don’t really matter, but just for fun, I create the suites as well.

    d = "D-"+str(i)  
    c = "C-"+str(i)  
    s = "S-"+str(i)  
    h = "H-"+str(i)  
    deck.append(d)  
    deck.append(c)  
    deck.append(s)  
    deck.append(h)

This way, when the script runs, our deck will create the 4 aces in D (diamonds), then the 4 aces in C (clubs) etc, as it iterates through my range. Your output will look something like this:

['D-1', 'C-1', 'S-1', 'H-1', 'D-2', 'C-2', 'S-2', 'H-2', 'D-3', 'C-3', 'S-3', 'H-3', 'D-4', 'C-4', 'S-4', 'H-4', 'D-5', 'C-5', 'S-5', 'H-5'.......]

In drawing a card from the deck, I want to make sure that I don’t duplicate any draws, and end up with the same card twice. my thought here is to have a list for the deck, and a list for the hand. When I randomly select a card from the deck list, I’ll append it to the card list, and then remove it from the deck list. Like this:

card1 = random.choice(deck)hand.append(card1)if card1 in deck:deck.remove(card1)

Now, I can basically repeat this same code for as many card draws as I want to calculate.

Next, I’ll think about how to handle the value of the Ace card. Here’s my thought on that. I’ll randomly draw two cards and add it to the hand. Then, if either value is a 1 (ace), I’ll change the value to 11, ensuring I’ll have the highest total. Then, if the sum of the two cards is over 21, and either card is an ace, I’ll change it to an 1. Here’s how I’ll do that:

value1 = int(hand\[0\].split("-")\[1\])  
value2 = int(hand\[1\].split("-")\[1\])  
if value1 == 1:  
    value1 = 11  
if value2 == 1:  
    value2 = 11  
sum1 = value1 + value2  
if sum1 > 21 and value2 == 11:  
    value2 = 1  
    sum1 = value1 + value2  
if sum1 > 21 and value1 == 11:  
    value1 = 1  
    sum1 = value1 + value2

Since my list only has two values at this point, hand[0] and hand[1] represent the two values in the hand list. But, as I said, I don’t need the suite, so I’m splitting it, and getting the integer of the second part of the split, and setting that value to 11 if it’s a 1. Then getting the sum, and resetting the value to 1, if one of the values is 11, if the sum is over 21.

It’s interesting to see how drastically this affects things. Without this initial reset of the Ace card, out of 5000 simulated hands, more than 600 were over 21 in the first two cards. But after adding the Ace value adjustment, that number dropped to just over 300 out of 5000 simulated hands.

How we handle the value of the ace could be adjusted. For example, we may want to wait until we deal 3 or 4 cards to check the sum, and make the ace adjustment if necessary. It’s interesting to see how that change affects our statistics down the road too. For example, the output I included earlier was from a million simulated hands, but without the ace adjustment, but here is the same 1 million simulations with the adjustment:

['2: ', 0, 0.0, 0, 0, 0, 0, 0, 0]['3: ', 0, 0.0, 0, 0, 0, 0, 0, 0]['4: ', 4499, 0.4499, 4499, 100.0, 3555, 79.01755945765726, 1382, 38.874824191279885]['5: ', 11950, 1.195, 11950, 100.0, 8737, 73.11297071129707, 2958, 33.85601465033764]['6: ', 16511, 1.6511, 16511, 100.0, 10815, 65.50178668766277, 3390, 31.345353675450763]['7: ', 24130, 2.413, 24130, 100.0, 14029, 58.13924575217572, 3971, 28.30565257680519]['8: ', 28895, 2.8895, 28895, 100.0, 15073, 52.164734383111266, 3712, 24.626816161348106]['9: ', 36296, 3.6296, 36296, 100.0, 15917, 43.85331716993608, 3419, 21.48017842558271]['10: ', 40583, 4.0583, 40583, 100.0, 14188, 34.960451420545546, 2718, 19.157034113335214]['11: ', 48467, 4.8467, 48467, 100.0, 12446, 25.679328202694617, 2456, 19.733247629760566]['12: ', 93381, 9.3381, 64545, 69.12005654255148, 19480, 30.180494228832597, 3069, 15.754620123203285]['13: ', 96590, 9.659, 59441, 61.539496842323224, 15431, 25.960195824430947, 2162, 14.010757565938695]['14: ', 89771, 8.9771, 48436, 53.955063439195285, 10855, 22.411016599223718, 1254, 11.552280055274068]['15: ', 84662, 8.4662, 38888, 45.93324041482602, 7362, 18.931289858053898, 669, 9.08720456397718]['16: ', 76903, 7.6903, 30166, 39.22603799591693, 4636, 15.368295431943247, 274, 5.910267471958585]['17: ', 72024, 7.2024, 22710, 31.531156281239586, 2576, 11.343020695728754, 95, 3.687888198757764]['18: ', 65253, 6.5253, 15392, 23.588187516282776, 1102, 7.159563409563409, 8, 0.7259528130671506]['19: ', 59526, 5.9526, 9415, 15.816617948459497, 288, 3.0589484864577803, 0, 0.0]['20: ', 102666, 10.2666, 8011, 7.802972746576277, 0, 0.0, 0, 0]['21: ', 47893, 4.7893, 0, 0.0, 0, 0, 0, 0]['21+: ', 0, 0.0, 0, 0, 0, 0, 0, 0]

[Finished in 29.911s]

As you can see, we don’t get any 2 or 3 values at all, because we’re always starting with 11’s instead of 1’s. It also slightly increases our chances of not going over even after the 3rd and 4th card.

Again, this isn’t earth shattering, but it’s easy to see how building a simple function like this out, can really help us see some cool patterns. Anyway, I would appreciate any feedback, and good luck with your future gambling!


Published by HackerNoon on 2018/03/30