Blackjack is my favorite casino game. It has the best odds of winning. Realistically, the odds are still against you and you will lose money over the long term*, but it is a great way to pass the time and have your money last as you enjoy the free drinks. You also tend to meet some colorful characters.
For a quick refresher on how the game works:
This is a very brief summary. There are entire books written on rules, variations, and strategies. For more information, check out Playing Blackjack to Win by Baldwin, Cantey, Maisel and McDermott. I essentially use their strategy. The math they first derived in the 1950s hasn't changed (as math is prone to do).
*Unless you count cards, which I will not get into. To any casino or government employees reading this, I officially do not know how to count cards. I mean, how could you possibly keep track of the low and high cards already played and use this knowledge to your advantage?
Like roulette, you shouldn't. End of post.
Blackjack players have more of an active role than roulette players. They can decide when to hit, double down, and split. This gives you more control and the potential to be a better than the average player (but still worse than the house).
Since the math is already solved for this game, and is a bit more complicated than roulette, I decided to make a simulation following 'The Best Strategy" presented in Playing Blackjack to Win. I also put the code at the bottom of the page so you can test and tweak it yourself.
Tweaking the code can be of great benefit to the casual player if they want to learn variations of the strategies. To play your best, you need to stick to the rules and know the strategy. Do you double down on 11 when the dealer has a 10 showing? Do you ever split 7s? Do you hit on a soft 16 against a dealer showing a 5? (Yes, sometimes, no). All of this logic is in the code but more than I think the casual reader cares for me to articulate. After 100,000 simulations:
100,000 Hands Played. Payout Percent: -0.4345%
95% Confidence Interval: -1.15% to 0.28%
-6.0 2 0.00%
-5.0 4 0.00%
-4.0 47 0.05%
-3.0 213 0.21%
-2.0 4243 4.24%
-1.0 43401 43.40%
0.0 8681 8.68%
1.0 32747 32.75%
1.5 4505 4.51%
2.0 5785 5.79%
3.0 268 0.27%
4.0 89 0.09%
5.0 13 0.01%
6.0 2 0.00%
So after playing 100,000 hands, betting $100,000, you end up losing $434.50. You hit 4,505 blackjacks. And on 4 occasions you split your hand twice and doubled down each hand, losing 2 and winning 2.
Note, these results may not be the same as the exact mathematical proof due to:
Let's imagine you are a serious player. You go out every week and play 100 hands a night. Considering Blackjack's variability, this would actually be a pretty volatile sample, so I will examine one simulation (maybe a few) and leave the code for you to create you own simulations. But let's consider:
We'll assume each hand is $20 (a low priced to moderate Vegas average) and blackjack payout is 1.5x the bet.
So for our first simulation:
Total Payout: -820.0
Weekly Average: -15.76923076923077
Winning Nights: 24
Winning Nights Average: 156.25
Loosing Nights: 28
Loosing Nights Average: -163.21428571428572
History: [-10.0, 90.0, -190.0, -440.0, -80.0, -30.0, 320.0, 140.0, 420.0, 310.0, 20.0, -90.0, 260.0, -130.0, -260.0, 70.0, 10.0, -80.0, -70.0, -380.0, -50.0, -90.0, -140.0, 330.0, -180.0, -60.0, -310.0, 80.0, -30.0, -70.0, -390.0, 70.0, -280.0, 270.0, -420.0, -60.0, -340.0, 220.0, 210.0, -30.0, 100.0, 40.0, 150.0, 80.0, 50.0, 80.0, 270.0, 70.0, -140.0, -10.0, 90.0, -210.0]
So not horrible. Loosing 15 bucks a week for a night out of entertainment is about the price of a movie. 100 games could take anywhere from one to three hours, so its about the same.
Let's look at another one:
Total Payout: 200.0
Weekly Average: 3.8461538461538463
Winning Nights: 29
Winning Nights Average: 168.9655172413793
Loosing Nights: 25
Loosing Nights Average: -188.0
History: [110.0, -40.0, 380.0, 190.0, -260.0, 140.0, 110.0, -100.0, 30.0, -50.0, -90.0, 350.0, -120.0, -340.0, -210.0, 350.0, 180.0, 190.0, -30.0, -380.0, 340.0, 0.0, 550.0, 70.0, -110.0, 320.0, -200.0, -180.0, 290.0, 60.0, -250.0, 40.0, -240.0, 0.0, 80.0, -360, 180.0, 150.0, 120.0, -390.0, 110.0, -230.0, 50.0, -20.0, 160.0, -350.0, -520.0, -130.0, 280.0, 60.0, 10.0, -100.0]
Oh! We won this time! $4 a night! about $2 an hour!
Let's look at one more:
Total Payout: -90.0
Weekly Average: -1.7307692307692308
Winning Nights: 26
Winning Nights Average: 176.15384615384616
Loosing Nights: 27
Loosing Nights Average: -172.96296296296296
History: [370.0, 110.0, -90.0, -340.0, 110.0, 40.0, 240.0, 270.0, -140.0, -140.0, 300.0, -460.0, -70.0, 50.0, 70.0, -200.0, -160.0, -140.0, 50.0, -480.0, -130.0, -70.0, 250.0, -20.0, -20.0, -320.0, 290.0, 170.0, -10.0, 260.0, -180.0, 90.0, -180.0, 110.0, 0.0, -240.0, -240.0, -130.0, 100.0, 160.0, -170.0, -30.0, 280.0, -180.0, 170.0, 210.0, -370.0, -160.0, 180.0, 130.0, 380.0, 190.0]
Looks like we lost again...
Some casinos have variations to make the game stacked even more against you. The one I most recently came across was Encore in Boston blackjack payout. In most casinos, if you get a natural blackjack with your first two cards, they give you 1.5 your bet. At encore, they lower this to 1.2.
Now considering this only happened in our 100,000 simulation 4,504 times, and each time you win 30 cents less, the casino took an extra $1,351.20 (or you lost an extra $1,351.20). So our -0.43% payout decreased to -1.79%.
I love playing blackjack and would like to think I have made a small amount of money over the time I have played it, though this is probably false. I often can't remember all the rules and find myself asking the dealer or nice people at my table. The more free drinks they give me the more i abandon my strategy and feel lucky. It certainly isn't a way to make money, but it's not the worst way to lose it.
Some quick notes on the program:
Some notes on my coding:
Here is the code to import my libraries and set up a dataframe for when to split:
#imports and initial condition
import pandas as pd
from random import randrange
import math
#function for summing hands (consdiering Aces)
def bjsum(list_cards):
if 1 in list_cards and sum(list_cards)+10<=21:
return sum(list_cards)+10
else:
return sum(list_cards)
#create dataframe to reflect when to split
split = pd.DataFrame(columns = ['Card','D1','D2','D3','D4','D5','D6','D7','D8','D9','D10'])
split.loc[1] = (1,'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y')
split.loc[2] = (2,'N','Y','Y','Y','Y','Y','Y','N','N','N')
split.loc[3] = (3,'N','Y','Y','Y','Y','Y','Y','N','N','N')
split.loc[4] = (4,'N','N','N','N','N','N','N','N','N','N')
split.loc[5] = (5,'N','N','N','N','N','N','N','N','N','N')
split.loc[6] = (6,'N','Y','Y','Y','Y','Y','Y','N','N','N')
split.loc[7] = (7,'N','Y','Y','Y','Y','Y','Y','N','N','N')
split.loc[8] = (8,'Y','Y','Y','Y','Y','Y','Y','Y','Y','Y')
split.loc[9] = (9,'N','Y','Y','Y','Y','Y','N','Y','Y','N')
split.loc[10] = (10,'N','N','N','N','N','N','N','N','N','N')
split.loc[11] = (11,'N','N','N','N','N','N','N','N','N','N')
split.loc[12] = (12,'N','N','N','N','N','N','N','N','N','N')
split.loc[13] = (13,'N','N','N','N','N','N','N','N','N','N')
Here is the code for the basic function of simulating a hand:
#simulate hands
def runSim(printOut = True,bet = 1,bj_pay = 1.5,hands = 100):
handtracker = pd.DataFrame(columns = ['PH1','PH2','PH3','PH1_tot','PH2_tot','PH3_tot',
'DH', 'DH_tot','Bet','W_L','Payout','Split','DD','Bust','BJ'])
for x in range(0,hands):
#creates the initial hand for the player
PH1_list = [randrange(13)+1,randrange(13)+1]
PH1_list = [10 if x>10 else x for x in PH1_list]
PH2_list = []
PH3_list = []
#creates hand for the dealer
DH_list = [randrange(13)+1,randrange(13)+1]
DH_list = [10 if x>10 else x for x in DH_list]
#record initial conditions
handtracker.at[x,'Bet']=bet
handtracker.at[x,'BJ']=0
handtracker.at[x,'DD']=0
handtracker.at[x,'Split']=0
handtracker.at[x,'Bust']=0
handtracker.at[x,'Payout']=0
handtracker.at[x,'W_L']=""
#check for both having blackjack
if bjsum(PH1_list)==21 and bjsum(DH_list)==21:
handtracker.at[x,'BJ']=1
handtracker.at[x,'Payout']=0
handtracker.at[x,'W_L']='P'
handtracker.at[x,'PH1']=PH1_list
handtracker.at[x,'PH2']=PH2_list
handtracker.at[x,'PH3']=PH3_list
handtracker.at[x,'PH1_tot']=bjsum(PH1_list)
handtracker.at[x,'PH2_tot']=bjsum(PH2_list)
handtracker.at[x,'PH3_tot']=bjsum(PH3_list)
handtracker.at[x,'DH']=DH_list
handtracker.at[x,'DH_tot']=bjsum(DH_list)
continue
#check for player blackjack
if bjsum(PH1_list)==21 :
handtracker.at[x,'BJ']=1
handtracker.at[x,'Payout']=bet*bj_pay
handtracker.at[x,'W_L']='W'
handtracker.at[x,'PH1']=PH1_list
handtracker.at[x,'PH2']=PH2_list
handtracker.at[x,'PH3']=PH3_list
handtracker.at[x,'PH1_tot']=bjsum(PH1_list)
handtracker.at[x,'PH2_tot']=bjsum(PH2_list)
handtracker.at[x,'PH3_tot']=bjsum(PH3_list)
handtracker.at[x,'DH']=DH_list
handtracker.at[x,'DH_tot']=bjsum(DH_list)
continue
#check for dealer blackjack
if bjsum(DH_list)==21:
handtracker.at[x,'BJ']=0
handtracker.at[x,'Payout']=-bet
handtracker.at[x,'W_L']='L'
handtracker.at[x,'PH1']=PH1_list
handtracker.at[x,'PH2']=PH2_list
handtracker.at[x,'PH3']=PH3_list
handtracker.at[x,'PH1_tot']=bjsum(PH1_list)
handtracker.at[x,'PH2_tot']=bjsum(PH2_list)
handtracker.at[x,'PH3_tot']=bjsum(PH3_list)
handtracker.at[x,'DH']=DH_list
handtracker.at[x,'DH_tot']=21
continue
#play the dealer hand
while bjsum(DH_list)<17:
DH_list.append(min(randrange(13)+1,10))
#split?
if PH1_list[0] == PH1_list[1] and split.loc[PH1_list[0]][DH_list[0]]=='Y':
handtracker.at[x,'Split']=1
PH1_list[1] = min(randrange(13)+1,10)
PH2_list.append(PH1_list[0])
PH2_list.append(min(randrange(13)+1,10))
split_two = "N"
#check if first hand resplit
if PH1_list[0] == PH1_list[1] and split.loc[PH1_list[0]][DH_list[0]]=='Y':
split_two = "Y"
handtracker.at[x,'Split']=2
PH1_list[1] = min(randrange(13)+1,10)
PH3_list.append(PH1_list[0])
PH3_list.append(min(randrange(13)+1,10))
#check if second hand resplit
if PH2_list[0] == PH2_list[1] and split.loc[PH2_list[0]][DH_list[0]]=='Y' and split_two=="N":
handtracker.at[x,'Split']=2
PH2_list[1] = min(randrange(13)+1,10)
PH3_list.append(PH2_list[0])
PH3_list.append(min(randrange(13)+1,10))
split_two="N"
#loop through hands, check for DD, then adjudicate the hand
for i in [PH1_list,PH2_list,PH3_list]:
#pass if hand is not played
if bjsum(i)==0:continue
#check for ace, if have, play soft rules
if 1 in i:
#check for double down
DD = 'N'
if bjsum(i)== 12 and DH_list[0] >=5 and DH_list[0]<=5: DD = 'Y'
if bjsum(i)== 13 and DH_list[0] >=5 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 14 and DH_list[0] >=5 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 15 and DH_list[0] >=5 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 16 and DH_list[0] >=5 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 17 and DH_list[0] >=3 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 18 and DH_list[0] >=4 and DH_list[0]<=6: DD = 'Y'
if DD =='Y':handtracker.at[x,'DD']=handtracker.at[x,'DD']+1
#playout player hand. One card for double down, and keep hitting while conditions met
if DD=='Y':
handtracker.at[x,'Bet']=bet*2
i.append(min(randrange(13)+1,10))
else:
H='Y'
while H == 'Y':
H = 'N'
#test if it is still soft
if bjsum(i)!=sum(i):
if bjsum(i)<= 17: H = 'Y'
elif bjsum(i)<= 18 and (DH_list[0]>=9): H = 'Y'
else:
if bjsum(i)<= 11: H = 'Y'
elif bjsum(i)<= 12 and (DH_list[0] <=3 or DH_list[0]>=7): H = 'Y'
elif bjsum(i)<= 16 and (DH_list[0] <=1 or DH_list[0]>=7): H = 'Y'
if H=='Y':
i.append(min(randrange(13)+1,10))
else:
#check for double down
DD = 'N'
if bjsum(i)== 9 and DH_list[0] >=2 and DH_list[0]<=6: DD = 'Y'
if bjsum(i)== 10 and DH_list[0] >=2 and DH_list[0]<=9: DD = 'Y'
if bjsum(i)== 11 and DH_list[0] >=2 : DD = 'Y'
if DD =='Y':handtracker.at[x,'DD']=handtracker.at[x,'DD']+1
#playout player hand. One card for double down, and keep hitting while conditions met
if DD=='Y':
handtracker.at[x,'Bet']=bet*2
i.append(min(randrange(13)+1,10))
else:
H='Y'
while H == 'Y':
H = 'N'
if bjsum(i)<= 11: H = 'Y'
elif bjsum(i)<= 12 and (DH_list[0] <=3 or DH_list[0]>=7): H = 'Y'
elif bjsum(i)<= 16 and (DH_list[0] <=1 or DH_list[0]>=7): H = 'Y'
if H=='Y':
i.append(min(randrange(13)+1,10))
#decide handoutcome
#player bust
if bjsum(i)>21:
handtracker.at[x,'Bust']=handtracker.at[x,'Bust'] +1
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'L'
if DD=='Y': handtracker.at[x,'Payout']=handtracker.at[x,'Payout']-2*bet
else:handtracker.at[x,'Payout']=handtracker.at[x,'Payout']-bet
#Dealer has higher hand and didn't bust
if bjsum(i)<bjsum(DH_list) and bjsum(DH_list)<=21:
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'L'
if DD=='Y': handtracker.at[x,'Payout']=handtracker.at[x,'Payout']-2*bet
else:handtracker.at[x,'Payout']=handtracker.at[x,'Payout']-bet
#push and no one busts
if bjsum(i)==bjsum(DH_list) and bjsum(i)<=21 and bjsum(DH_list)<=21:
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'P'
#Dealer busts
if bjsum(i)<=21 and bjsum(DH_list)>21:
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'W'
if DD=='Y': handtracker.at[x,'Payout']=handtracker.at[x,'Payout']+2*bet
else:handtracker.at[x,'Payout']=handtracker.at[x,'Payout']+bet
#Player has better hand and no one busts
if bjsum(i)>bjsum(DH_list) and bjsum(i)<=21 and bjsum(DH_list)<=21:
handtracker.at[x,'W_L']=handtracker.at[x,'W_L'] +'W'
if DD=='Y': handtracker.at[x,'Payout']=handtracker.at[x,'Payout']+2*bet
else:handtracker.at[x,'Payout']=handtracker.at[x,'Payout']+bet
handtracker.at[x,'PH1']=PH1_list
handtracker.at[x,'PH2']=PH2_list
handtracker.at[x,'PH3']=PH3_list
handtracker.at[x,'PH1_tot']=bjsum(PH1_list)
handtracker.at[x,'PH2_tot']=bjsum(PH2_list)
handtracker.at[x,'PH3_tot']=bjsum(PH3_list)
handtracker.at[x,'DH']=DH_list
handtracker.at[x,'DH_tot']=bjsum(DH_list)
if printOut:
print(hands, " Hands Played. Payout Percent:",handtracker['Payout'].mean())
#print("Standard Deviation:","{0:.4f}".format(handtracker['Payout'].std()))
low =handtracker['Payout'].mean()-1.96*handtracker['Payout'].std()/math.sqrt(hands)
high = handtracker['Payout'].mean()+1.96*handtracker['Payout'].std()/math.sqrt(hands)
print("95% Confidence Interval:","{0:.4f}".format(low)," to " ,"{0:.4f}".format(high))
print(handtracker['Payout'].value_counts().sort_index())
return handtracker['Payout'].sum()
Here is the code to run the simulation multiple times:
#master run for "true dist"
runSim(printOut = True,bet = 1,bj_pay = 1.5,hands = 100000)
Here is the code to run the simulation for a given year:
#simulation of a night
nightly_pay = []
for night in range(52):
nightly_pay.append(runSim(printOut = False,bet = 20,bj_pay = 1.5,hands = 100))
win_sum = 0
loss_sum =0
for pay in nightly_pay:
if pay> 0:
win_sum = win_sum+ pay
else:
loss_sum = loss_sum+pay
print("Total Payout: ",sum(nightly_pay))
print("Weekly Average: ",sum(nightly_pay)/52)
print("Winning Nights: ",sum(num >= 0 for num in nightly_pay))
print("Winning Nights Average: ",win_sum/sum(num >= 0 for num in nightly_pay))
print("Loosing Nights: ",sum(num <= 0 for num in nightly_pay))
print("Loosing Nights Average: ",loss_sum/sum(num <= 0 for num in nightly_pay))
print("History: ",nightly_pay)