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.0Weekly Average: -15.76923076923077Winning Nights: 24Winning Nights Average: 156.25Loosing Nights: 28Loosing Nights Average: -163.21428571428572History: [-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.0Weekly Average: 3.8461538461538463Winning Nights: 29Winning Nights Average: 168.9655172413793Loosing Nights: 25Loosing Nights Average: -188.0History: [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.0Weekly Average: -1.7307692307692308Winning Nights: 26Winning Nights Average: 176.15384615384616Loosing Nights: 27Loosing Nights Average: -172.96296296296296History: [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 pdfrom random import randrangeimport 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 handsdef 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 = 0loss_sum =0for 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)