Skip to content

Instantly share code, notes, and snippets.

@philippdubach
Created July 4, 2025 11:55
Show Gist options
  • Select an option

  • Save philippdubach/741cbd56498e43375892966ca691b9c2 to your computer and use it in GitHub Desktop.

Select an option

Save philippdubach/741cbd56498e43375892966ca691b9c2 to your computer and use it in GitHub Desktop.
Minimal blackjack odds calculator returns win probabilities using Monte Carlo simulation and recommended next move (Hit, Stand, Double, Split).
import random
from collections import Counter
class BlackjackAdvisor:
def __init__(self):
# Basic strategy chart: [player_total][dealer_upcard] = action
# Actions: 'H'=Hit, 'S'=Stand, 'D'=Double, 'P'=Split
self.basic_strategy = {
# Hard totals (no aces counted as 11)
'hard': {
5: ['H'] * 10, 6: ['H'] * 10, 7: ['H'] * 10, 8: ['H'] * 10,
9: ['H','H','D','D','D','H','H','H','H','H'],
10: ['D','D','D','D','D','D','D','D','H','H'],
11: ['D','D','D','D','D','D','D','D','D','H'],
12: ['H','H','S','S','S','H','H','H','H','H'],
13: ['S','S','S','S','S','H','H','H','H','H'],
14: ['S','S','S','S','S','H','H','H','H','H'],
15: ['S','S','S','S','S','H','H','H','H','H'],
16: ['S','S','S','S','S','H','H','H','H','H'],
17: ['S'] * 10, 18: ['S'] * 10, 19: ['S'] * 10, 20: ['S'] * 10, 21: ['S'] * 10
},
# Soft totals (ace counted as 11)
'soft': {
13: ['H','H','H','D','D','H','H','H','H','H'], # A,2
14: ['H','H','H','D','D','H','H','H','H','H'], # A,3
15: ['H','H','D','D','D','H','H','H','H','H'], # A,4
16: ['H','H','D','D','D','H','H','H','H','H'], # A,5
17: ['H','D','D','D','D','H','H','H','H','H'], # A,6
18: ['S','D','D','D','D','S','S','H','H','H'], # A,7
19: ['S'] * 10, 20: ['S'] * 10, 21: ['S'] * 10 # A,8, A,9, A,10
},
# Pairs
'pairs': {
2: ['P','P','P','P','P','P','H','H','H','H'], # 2,2
3: ['P','P','P','P','P','P','H','H','H','H'], # 3,3
4: ['H','H','H','P','P','H','H','H','H','H'], # 4,4
5: ['D','D','D','D','D','D','D','D','H','H'], # 5,5 (treat as 10)
6: ['P','P','P','P','P','H','H','H','H','H'], # 6,6
7: ['P','P','P','P','P','P','H','H','H','H'], # 7,7
8: ['P','P','P','P','P','P','P','P','H','H'], # 8,8
9: ['P','P','P','P','P','S','P','P','S','S'], # 9,9
10: ['S'] * 10, # 10,10 (never split)
11: ['P'] * 10 # A,A
}
}
def card_value(self, card):
"""Convert card to numerical value"""
if card in ['J', 'Q', 'K']:
return 10
elif card == 'A':
return 11
return int(card)
def hand_value(self, cards):
"""Calculate hand value, handling aces"""
values = [self.card_value(card) for card in cards]
total = sum(values)
aces = values.count(11)
# Convert aces from 11 to 1 if needed
while total > 21 and aces > 0:
total -= 10
aces -= 1
return total, aces > 0 # Return (value, is_soft)
def is_pair(self, cards):
"""Check if hand is a pair"""
if len(cards) != 2:
return False
return self.card_value(cards[0]) == self.card_value(cards[1])
def get_basic_strategy(self, player_cards, dealer_upcard):
"""Get basic recommendation"""
player_total, is_soft = self.hand_value(player_cards)
dealer_value = self.card_value(dealer_upcard)
dealer_idx = dealer_value - 2 if dealer_value <= 10 else 9 # A=9, others 2-10 = 0-8
# Check for pair first
if self.is_pair(player_cards):
pair_value = self.card_value(player_cards[0])
if pair_value == 11: # Ace pair
pair_value = 11
return self.basic_strategy['pairs'][pair_value][dealer_idx]
# Soft hand (ace counted as 11)
if is_soft and player_total <= 21:
return self.basic_strategy['soft'][player_total][dealer_idx]
# Hard hand
player_total = min(player_total, 21) # Cap at 21 for lookup
player_total = max(player_total, 5) # Minimum lookup value
return self.basic_strategy['hard'][player_total][dealer_idx]
def calculate_win_probability(self, player_cards, dealer_upcard, seen_cards=None):
"""Monte Carlo simulation for win probability"""
if seen_cards is None:
seen_cards = []
# Create remaining deck
full_deck = (['A'] + [str(i) for i in range(2, 11)] + ['J', 'Q', 'K']) * 4
remaining_deck = full_deck.copy()
# Remove seen cards
for card in seen_cards + player_cards + [dealer_upcard]:
if card in remaining_deck:
remaining_deck.remove(card)
wins = 0
simulations = 1000
for _ in range(simulations):
# Simulate dealer hand
dealer_cards = [dealer_upcard]
deck_copy = remaining_deck.copy()
random.shuffle(deck_copy)
# Dealer plays
while True:
dealer_total, dealer_soft = self.hand_value(dealer_cards)
if dealer_total > 21:
break
if dealer_total >= 17:
if dealer_total == 17 and dealer_soft:
# Dealer hits soft 17 in some casinos
pass
else:
break
if deck_copy:
dealer_cards.append(deck_copy.pop())
else:
break
# Compare hands
player_total, _ = self.hand_value(player_cards)
dealer_total, _ = self.hand_value(dealer_cards)
if dealer_total > 21:
wins += 1
elif player_total > 21:
wins += 0
elif player_total > dealer_total:
wins += 1
elif player_total == dealer_total:
wins += 0.5 # Push
return wins / simulations
def get_advice(self, player_cards, dealer_upcard, seen_cards=None):
"""Recommend next best move"""
if seen_cards is None:
seen_cards = []
player_total, is_soft = self.hand_value(player_cards)
basic_action = self.get_basic_strategy(player_cards, dealer_upcard)
win_prob = self.calculate_win_probability(player_cards, dealer_upcard, seen_cards)
actions = {
'H': 'Hit',
'S': 'Stand',
'D': 'Double Down',
'P': 'Split'
}
return {
'player_total': player_total,
'is_soft': is_soft,
'basic_strategy': actions[basic_action],
'win_probability': f"{win_prob:.2%}",
'recommendation': actions[basic_action]
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment