## Blackjack

### October 17, 2014

Blackjack is a casino game of chance, played by a player and a dealer. Both player and dealer are initially dealt two cards from a standard 52-card deck. If the player’s initial hand consists of an ace and a ten or face card, the player wins, unless the dealer also has an ace and a ten or face card, in which case the game is a tie. Otherwise, the player draws cards until he decides to stop; if at any time the sum of the pips on the cards (aces count either 1 or 11, face cards count 10) exceeds 21, the player is busted, and loses. Once the player is finished, the dealer draws cards until he has 17 or more pips, or goes bust, at which time the game ends. If neither has gone bust, the hand with the most pips wins. There are many variant rules, but we’ll keep things simple with the rules described above.

Your task is to simulate Blackjack and determine the winning percentage for the player. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

I should stop, but here’s a J solution:

A sample run is

I don’t have time to work this up into a full solution right now, but it should be possible to calculate the exact percentages as follows:

Build a graph of the possible game states; a game state is a target value (initially 16), a score so far, and a deck (or maybe a shoe) that is just a bag of integers 1-10. Initially, for the player, the target is 17, the score is 0 and the deck is whatever we want to start with, eg. 1-9 map to 4, 10 maps to 16 for a single pack.

Each transition takes one element out of the bag and adds it to the score, a final state is one where the score is greater that the target, at which we stop (with the player bust or not). Each non-final state has a probability associated with each outgoing transition (just the probability of selecting that particular bag element) and we can propagate backwards to get the probabilities of the various final states.

There are a few thousand possible finishing states where the player doesn’t go bust and we need to use each one as a starting point for a simulation of the dealer, but this shouldn’t be completely intractable.

Here’s some Python then. Use a dictionary to keep track of states (just store the remaining deck as a tuple in here – dictionary keys must be immutable). Divide up the probabilities as we go and combine afterwards for an overall win/lose/draw value, we seem to be able to do this without too much problem with rounding error. Starting with a single pack, we end up with 1336 state in which the player hasn’t busted & must be simulated for the dealer. Total runtime on my i3 laptop is 25 secs and the final probabilities are: lose: 48.9%, win: 41.2%, draw: 9.8%.

#!/usr/bin/python

# Create a new deck with ith count decremented

def makenewdeck(deck,i):

newdeck = list(deck)

newdeck[i] -= 1

return tuple(newdeck)

class State:

def __init__ (self,score,ncards,aces,deck,prob):

self.score = score; self.aces = aces

self.deck = deck; self.prob = prob;

self.ncards = ncards; self.ndeck = sum(deck)

def __str__ (self):

return “[%d %d %d %g %s]”%(self.score,self.aces,

self.ncards,self.prob,self.deck)

def finalstate(self):

return self.score > 16

def getscore(self):

if self.score > 21: return None

else: return self.score

def blackjack(self):

return self.ncards == 2 and self.score == 21

def play(initdeck):

initstate = State(0,0,0,initdeck,1.0)

states = [initstate]

deckindex = { initdeck: 0 }

i = 0

while(i != len(states)):

s = states[i]; i += 1

if not s.finalstate():

for j in range(0,len(s.deck)):

if s.deck[j] > 0:

newdeck = makenewdeck(s.deck,j)

newprob = s.deck[j]*s.prob/s.ndeck

n = deckindex.get(newdeck)

if n != None:

# Seen this state already

states[n].prob += newprob

else:

newscore = s.score

newaces = s.aces

if (j == 0):

# Aces initially valued at 11

newscore += 11; newaces += 1

else:

newscore += j+1

if newscore > 21 and newaces > 0:

# If we busted with an ace left, count it as 1

newscore -= 10; newaces -= 1

newstate = State(newscore,s.ncards+1,

newaces,newdeck,newprob)

n = len(states)

states.append(newstate)

deckindex[newdeck] = n

return states

initdeck = (4,4,4,4,4,4,4,4,4,16)

pstates = play(initdeck)

dealerprob = 0; playerprob = 0; drawprob = 0 # Final probabilities

for i in range(0,len(pstates)):

pstate = pstates[i];

if pstate.finalstate():

pprob = pstate.prob

pscore = pstate.getscore() # player score

if pscore == None:

dealerprob += pprob # Player is bust

else:

dstates = play(pstate.deck)

# Accumulate subsidiary probabilities here

# to reduce rounding errors

dealerprob2 = 0; playerprob2 = 0; drawprob2 = 0

for j in range (0,len(dstates)):

dstate = dstates[j]

if dstate.finalstate():

dprob = dstate.prob

dscore = dstate.getscore() # dealer score

if dscore == None or dscore < pscore:

playerprob2 += dprob

elif dscore == pscore:

if pstate.blackjack() and not dstate.blackjack():

playerprob2 += dprob

else:

drawprob2 += dprob

else:

dealerprob2 += dprob

# Add in the subsidiary probabilities

dealerprob += pprob*dealerprob2

playerprob += pprob*playerprob2

drawprob += pprob*drawprob2

print (dealerprob,playerprob,drawprob,dealerprob+playerprob+drawprob)

And with proper indentation (it would be nice if comments were editable):

;; Interactive console Black jack

(defn cartesian-product [l & ls]

(reduce

(fn [a b] (for [x a y b] (conj x y)))

(map vector l) ls))

;; The deck of cards. The suits are not being used in the game but it’s more

;; fun if they’re printed anyway

(def deck (cartesian-product ‘(\u2665 \u2660 \u2666 \u2663) (range 13)))

(defn print-card [[color val]]

(let [v (if (== val 0) ‘A

(if ( a 9) 10 (+ a 1)))))

(defn bust? [v] (> v 21))

;; Find all the possible sums of values of the cards and choose the appropriate one

;; return 50 if bust

(defn hand-value [cards]

(let [possible-values (apply cartesian-product (map card-value cards))

possible-sums (map #(apply + %) possible-values)]

(if (bust? (apply min possible-sums)) 50

(apply max (filter #( v 16))

;; Interactive player’s logic

(defn players-logic [hand value]

(if (= 21 value) true

(do

(println (map print-card hand) “\n” value ” – More?”)

(= “n” (read-line)))))

;; The two game phases:

(defn dealers-game [hand pile value]

(let [[d-hand pile d-value] (deal-hand pile dealers-logic)]

(println “Dealer’s hand” (map print-card d-hand))

(if (or (bust? d-value) (> value d-value)) :win :lose)))

(defn game [pile]

(let [[hand pile value] (deal-hand pile players-logic)]

(println “Your hand” (map print-card hand))

(cond (= 21 value) :win

(bust? value) :lose

:else (dealers-game hand pile value))))

;; Let’s play!

(loop [won 0 lost 0]

(let [result (game (shuffle deck))

win (if (= :win result) 1 0)

won (+ win won)

lost (- (+ 1 lost) win)]

(println ({:win “You win” :lose “Dealer wins”} result))

(if ((fn [] (println “Play another game?”) (= “n” (read-line))))

(do

(println “You won” (int (* 100 (/ won (+ won lost)))) “% of the games”)

(vector won lost))

(recur won lost))))

my previous post is about clojure.

The commenting system on this blog goes bust! ;-)