I asked a question on math.stackexchange.com, and recieved a great answer. This is my attempt to implement the insighful solution (link to question)
After some thought and reading comments on math stackexchange, i decided to implement the recursive solution rather than the linear algebra one for now
In [1]:
from __future__ import division
from copy import deepcopy
def hit_hand(score, soft, value):
'''given a hand and a card, returns the new hand
arguments:
score: the current score of the hand (int)
soft: whether or not the score is soft (boolean)
value: the value of the card to add to the hand
returns (score, soft): the score and whether or not the score is soft of the resulting hand
'''
if soft and score + value > 21:
score = score - 10 + value
soft = False
elif value == 1 and score + 11 <= 21:
score += 11
soft = True
else:
score += value
return (score, soft)
def p_bust(hand, shoe, p=0.0):
'''given a hand and a shoe, returns the probability of busting
assumes dealer stands on soft 17
arguments:
hand: tuple containing score and whether score is soft (tuple)
shoe: the number of cards of each value left to draw (list)
keyword_arguments:
p: aggregator for probability of busting (default=0.0)
returns p: probability of busting (float)
'''
score, soft = hand
if score <= 16:
for card in range(10):
if shoe[card] > 0:
score_after_hitting, soft = hit_hand(score, soft, card + 1)
if score_after_hitting >= 22:
p += shoe[card]/sum(shoe)
elif score_after_hitting <= 16:
new_shoe = deepcopy(shoe)
new_shoe[card] -= 1
p += shoe[card]/sum(shoe) * p_bust((score_after_hitting, soft), new_shoe)
return p
def possible_hands(showing, shoe):
''' generates a list of all possible hands and the probability
of seeing that hand given a shoe and an upcard
arguments:
showing: the value of the card that is showing
shoe: the number of cards of each value left to draw (list)
returns [((score, scoft) p), ...]: list of the hands (tuple of the score and whether it is soft) and probability of hand
'''
hands = []
for card in range(10):
p_card = shoe[card] / sum(shoe)
new_shoe = shoe_minus_card(shoe, card)
if showing == 1:
hands.append((hit_hand(11, True, card + 1), new_shoe, p_card))
else:
hands.append((hit_hand(showing, False, card + 1), new_shoe, p_card))
return hands
def shoe_minus_card(shoe, card):
new_shoe = deepcopy(shoe)
new_shoe[card] -= 1
return new_shoe
master_shoe = [4*6 for _ in range (9)] + [4*4*6]
for card in range(10):
shoe = shoe_minus_card(master_shoe, card)
print 'Probability of bust showing %d: %f' % (card + 1, sum([p_bust(hand, new_shoe)*p_card for hand, new_shoe, p_card in possible_hands(card + 1, shoe)]))
| Dealer's up Card |
Dealer's Final Total | ||||||
|---|---|---|---|---|---|---|---|
| 17 | 18 | 19 | 20 | 21 | BJ | Bust | |
| Ace | 0.130017 | 0.130822 | 0.130594 | 0.13091 | 0.0535035 | 0.308682 | 0.115473 |
| 2 | 0.139656 | 0.134389 | 0.13002 | 0.124019 | 0.118413 | 0 | 0.353504 |
| 3 | 0.134296 | 0.130539 | 0.125226 | 0.120817 | 0.114928 | 0 | 0.374194 |
| 4 | 0.130556 | 0.124062 | 0.121272 | 0.116441 | 0.111866 | 0 | 0.395805 |
| 5 | 0.121843 | 0.122437 | 0.117575 | 0.111793 | 0.107945 | 0 | 0.418406 |
| 6 | 0.165707 | 0.106194 | 0.106431 | 0.101551 | 0.0972756 | 0 | 0.422842 |
| 7 | 0.369208 | 0.137931 | 0.078428 | 0.0786816 | 0.0738158 | 0 | 0.261936 |
| 8 | 0.12894 | 0.359955 | 0.128723 | 0.0692189 | 0.0694701 | 0 | 0.243693 |
| 9 | 0.12031 | 0.117348 | 0.351854 | 0.120368 | 0.0608772 | 0 | 0.229242 |
| 10 | 0.111914 | 0.111669 | 0.111945 | 0.340014 | 0.0348174 | 0.0771704 | 0.212471 |
| All | 0.145245 | 0.139258 | 0.133685 | 0.179527 | 0.0728741 | 0.0474895 | 0.281921 |