Skip to content

Instantly share code, notes, and snippets.

@LeDonBobo
Created March 24, 2026 14:45
Show Gist options
  • Select an option

  • Save LeDonBobo/186fea71cae7a03eb0359a9f34d3ada1 to your computer and use it in GitHub Desktop.

Select an option

Save LeDonBobo/186fea71cae7a03eb0359a9f34d3ada1 to your computer and use it in GitHub Desktop.
A python simulation of blackjack games (100000) to see what resultas we obtain and calculates what are the statistics behind this famous game.
"""
=============================================================
BLACKJACK v5 — Stratégie sur les mains molles (soft hands)
=============================================================
"""
import random
# =============================================================
# MODULES 1 & 2 : DECK + SCORE
# =============================================================
def creer_deck(nb_decks=6):
cartes = ['2','3','4','5','6','7','8','9','10','J','Q','K','A']
deck = cartes * 4 * nb_decks
random.shuffle(deck)
return deck
VALEURS = {
'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,
'10':10,'J':10,'Q':10,'K':10,'A':11
}
HI_LO = {
'2':+1,'3':+1,'4':+1,'5':+1,'6':+1,
'7':0,'8':0,'9':0,
'10':-1,'J':-1,'Q':-1,'K':-1,'A':-1
}
def calculer_score(main):
score = sum(VALEURS[c] for c in main)
nb_as = main.count('A')
while score > 21 and nb_as > 0:
score -= 10
nb_as -= 1
return score
def est_blackjack(main):
return len(main) == 2 and calculer_score(main) == 21
def maj_compteur(rc, cartes):
for c in cartes:
rc += HI_LO[c]
return rc
def est_molle(main):
"""
Retourne True si la main est "molle" : contient un As
compté comme 11 (sans dépasser 21).
Exemple :
['A', '6'] → molle (score = 17, As vaut 11)
['A', '6', '5']→ dure (score = 12, As vaut 1 pour éviter bust)
['A', '9'] → molle (score = 20)
"""
score_brut = sum(VALEURS[c] for c in main)
nb_as = main.count('A')
# La main est molle si on peut compter au moins un As comme 11
# sans dépasser 21, c'est-à-dire si score_brut <= 21
return nb_as > 0 and score_brut <= 21
# =============================================================
# MODULE 3 : STRATÉGIE COMPLÈTE AVEC MAINS MOLLES
# =============================================================
STRATEGIE_DURE = {
8: {2:'H',3:'H',4:'H',5:'H',6:'H',7:'H',8:'H',9:'H',10:'H',11:'H'},
9: {2:'H',3:'D',4:'D',5:'D',6:'D',7:'H',8:'H',9:'H',10:'H',11:'H'},
10: {2:'D',3:'D',4:'D',5:'D',6:'D',7:'D',8:'D',9:'D',10:'H',11:'H'},
11: {2:'D',3:'D',4:'D',5:'D',6:'D',7:'D',8:'D',9:'D',10:'D',11:'H'},
12: {2:'H',3:'H',4:'S',5:'S',6:'S',7:'H',8:'H',9:'H',10:'H',11:'H'},
13: {2:'S',3:'S',4:'S',5:'S',6:'S',7:'H',8:'H',9:'H',10:'H',11:'H'},
14: {2:'S',3:'S',4:'S',5:'S',6:'S',7:'H',8:'H',9:'H',10:'H',11:'H'},
15: {2:'S',3:'S',4:'S',5:'S',6:'S',7:'H',8:'H',9:'H',10:'H',11:'H'},
16: {2:'S',3:'S',4:'S',5:'S',6:'S',7:'H',8:'H',9:'H',10:'H',11:'H'},
17: {2:'S',3:'S',4:'S',5:'S',6:'S',7:'S',8:'S',9:'S',10:'S',11:'S'},
}
# Table des mains molles
# Score "soft" → {valeur_croupier → action}
# Note : soft 19+ → toujours S (19 ou 20 = main très forte)
STRATEGIE_MOLLE = {
# Soft 13 = A+2 : doubler si croupier 5-6
13: {2:'H',3:'H',4:'H',5:'D',6:'D',7:'H',8:'H',9:'H',10:'H',11:'H'},
# Soft 14 = A+3 : doubler si croupier 5-6
14: {2:'H',3:'H',4:'H',5:'D',6:'D',7:'H',8:'H',9:'H',10:'H',11:'H'},
# Soft 15 = A+4 : doubler si croupier 4-6
15: {2:'H',3:'H',4:'D',5:'D',6:'D',7:'H',8:'H',9:'H',10:'H',11:'H'},
# Soft 16 = A+5 : doubler si croupier 4-6
16: {2:'H',3:'H',4:'D',5:'D',6:'D',7:'H',8:'H',9:'H',10:'H',11:'H'},
# Soft 17 = A+6 : doubler si croupier 3-6, sinon TOUJOURS tirer
17: {2:'H',3:'D',4:'D',5:'D',6:'D',7:'H',8:'H',9:'H',10:'H',11:'H'},
# Soft 18 = A+7 : cas le plus subtil
# - Doubler si croupier 3-6 (on peut améliorer)
# - Rester si croupier 2, 7, 8 (18 est suffisant)
# - Tirer si croupier 9, 10, As (croupier trop fort)
18: {2:'S',3:'D',4:'D',5:'D',6:'D',7:'S',8:'S',9:'H',10:'H',11:'H'},
# Soft 19+ : toujours rester (géré séparément ci-dessous)
}
def vc(carte):
return VALEURS[carte]
def strategie(main, carte_croupier):
"""
Détermine l'action optimale en tenant compte du type de main.
Nouveauté v4 : détecte si la main est molle et consulte
STRATEGIE_MOLLE au lieu de STRATEGIE_DURE si c'est le cas.
Retourne : 'H', 'S', ou 'D'
"""
score = calculer_score(main)
nb_cartes = len(main)
v_croupier = vc(carte_croupier)
# --- Main molle ---
if est_molle(main):
if score >= 19:
return 'S' # Soft 19/20 → toujours rester
if score in STRATEGIE_MOLLE:
action = STRATEGIE_MOLLE[score][v_croupier]
# Le Double n'est possible qu'avec 2 cartes
if action == 'D' and nb_cartes > 2:
return 'H'
return action
# --- Main dure (logique v3 inchangée) ---
if score >= 17:
return 'S'
if score <= 8:
return 'H'
action = STRATEGIE_DURE[score][v_croupier]
if action == 'D' and nb_cartes > 2:
return 'H'
return action
# =============================================================
# MODULE 4 : SPLIT (inchangé depuis v3 corrigé)
# =============================================================
STRATEGIE_SPLIT = {
'A': {2:True,3:True,4:True,5:True,6:True,7:True,8:True,9:True,10:True,11:True},
'8': {2:True,3:True,4:True,5:True,6:True,7:True,8:True,9:True,10:True,11:True},
'2': {2:True,3:True,4:True,5:True,6:True,7:True,8:False,9:False,10:False,11:False},
'3': {2:True,3:True,4:True,5:True,6:True,7:True,8:False,9:False,10:False,11:False},
'4': {2:False,3:False,4:False,5:True,6:True,7:False,8:False,9:False,10:False,11:False},
'5': {2:False,3:False,4:False,5:False,6:False,7:False,8:False,9:False,10:False,11:False},
'6': {2:True,3:True,4:True,5:True,6:True,7:False,8:False,9:False,10:False,11:False},
'7': {2:True,3:True,4:True,5:True,6:True,7:True,8:False,9:False,10:False,11:False},
'9': {2:True,3:True,4:True,5:True,6:True,7:False,8:True,9:True,10:False,11:False},
'10':{2:False,3:False,4:False,5:False,6:False,7:False,8:False,9:False,10:False,11:False},
'J': {2:False,3:False,4:False,5:False,6:False,7:False,8:False,9:False,10:False,11:False},
'Q': {2:False,3:False,4:False,5:False,6:False,7:False,8:False,9:False,10:False,11:False},
'K': {2:False,3:False,4:False,5:False,6:False,7:False,8:False,9:False,10:False,11:False},
}
def normaliser(c):
return '10' if c in ['J','Q','K'] else c
def paire_splittable(main):
if len(main) != 2:
return None
n1, n2 = normaliser(main[0]), normaliser(main[1])
return main[0] if n1 == n2 else None
# =============================================================
# MODULE 5 : JOUER UNE MAIN
# =============================================================
def joueur_joue(deck, main, carte_croupier, rc, peut_splitter=True):
"""Joue une main, retourne (liste de (score, mise), rc)."""
paire = paire_splittable(main)
if peut_splitter and paire and STRATEGIE_SPLIT[normaliser(paire)][vc(carte_croupier)]:
main1 = [main[0], deck.pop()]
main2 = [main[1], deck.pop()]
rc = maj_compteur(rc, [main1[1], main2[1]])
if normaliser(paire) == 'A':
return [(calculer_score(main1), 1), (calculer_score(main2), 1)], rc
m1, rc = joueur_joue(deck, main1, carte_croupier, rc, peut_splitter=False)
m2, rc = joueur_joue(deck, main2, carte_croupier, rc, peut_splitter=False)
return m1 + m2, rc
mise = 1
while True:
score = calculer_score(main)
if score > 21:
return [(score, mise)], rc
# Appel à la nouvelle stratégie qui gère les mains molles
action = strategie(main, carte_croupier)
if action == 'D':
mise = 2
c = deck.pop()
main.append(c)
rc = maj_compteur(rc, [c])
break
elif action == 'H':
c = deck.pop()
main.append(c)
rc = maj_compteur(rc, [c])
else:
break
return [(calculer_score(main), mise)], rc
def croupier_joue(deck, main, rc):
while calculer_score(main) < 17:
c = deck.pop()
main.append(c)
rc = maj_compteur(rc, [c])
return calculer_score(main), rc
def resoudre(mains_joueur, score_croupier):
gain = 0
for score, mise in mains_joueur:
if score > 21:
gain -= mise
elif score_croupier > 21 or score > score_croupier:
gain += mise
elif score < score_croupier:
gain -= mise
return gain
def jouer_une_main(deck, rc):
main_j = [deck.pop(), deck.pop()]
main_c = [deck.pop(), deck.pop()]
rc = maj_compteur(rc, main_j + main_c)
if est_blackjack(main_j):
if est_blackjack(main_c):
return 'égalité', 0, rc
return 'blackjack', +1.5, rc
if est_blackjack(main_c):
return 'défaite', -1, rc
mains_finales, rc = joueur_joue(deck, main_j, main_c[0], rc)
score_c, rc = croupier_joue(deck, main_c, rc)
gain = resoudre(mains_finales, score_c)
if gain > 0: return 'victoire', gain, rc
elif gain < 0: return 'défaite', gain, rc
else: return 'égalité', 0, rc
# =============================================================
# MODULE 6 : SIMULATION
# =============================================================
def simuler(nb_parties=100_000, nb_decks=6):
compteurs = {'victoire':0,'défaite':0,'égalité':0,'blackjack':0}
gain_total = 0
rc = 0
deck = creer_deck(nb_decks)
seuil = int(52 * nb_decks * 0.25)
for _ in range(nb_parties):
if len(deck) < seuil:
deck = creer_deck(nb_decks)
rc = 0
résultat, gain, rc = jouer_une_main(deck, rc)
compteurs[résultat] += 1
gain_total += gain
t = nb_parties
return {
'nb_parties' : t,
'taux_victoire' : round((compteurs['victoire']+compteurs['blackjack'])/t*100, 2),
'taux_blackjack': round(compteurs['blackjack']/t*100, 2),
'taux_défaite' : round(compteurs['défaite']/t*100, 2),
'taux_égalité' : round(compteurs['égalité']/t*100, 2),
'gain_total' : round(gain_total, 1),
'gain_par_main' : round(gain_total/t, 4),
'blackjacks' : compteurs['blackjack'],
}
# =============================================================
# PROGRAMME PRINCIPAL
# =============================================================
if __name__ == "__main__":
print("=" * 62)
print(" BLACKJACK v4 — Stratégie mains molles ajoutée")
print("=" * 62)
# --- Test de la détection des mains molles ---
print("\n📋 TEST : DÉTECTION MAINS MOLLES")
print("-" * 42)
cas = [
(['A','6'], True, "A+6 = soft 17"),
(['A','7'], True, "A+7 = soft 18"),
(['A','6','5'], False, "A+6+5 : As repasse à 1 → hard 12"),
(['A','9'], True, "A+9 = soft 20"),
(['10','7'], False, "10+7 = hard 17, pas d'As"),
(['A','A'], True, "A+A = soft 12"),
]
for main, attendu, explication in cas:
résultat = est_molle(main)
icone = "✅" if résultat == attendu else "❌"
label = "molle" if résultat else "dure "
print(f" {icone} {str(main):<18} → {label} {explication}")
# --- Test de stratégie sur mains molles ---
print("\n📋 TEST : STRATÉGIE MAINS MOLLES")
print("-" * 42)
cas_strat = [
(['A','6'], '5', 'D', "Soft 17 vs 5 → Doubler"),
(['A','6'], '8', 'H', "Soft 17 vs 8 → Tirer (pas rester !)"),
(['A','7'], '6', 'D', "Soft 18 vs 6 → Doubler"),
(['A','7'], '8', 'S', "Soft 18 vs 8 → Rester"),
(['A','7'], '9', 'H', "Soft 18 vs 9 → Tirer (croupier fort)"),
(['A','8'], '6', 'S', "Soft 19 vs 6 → Toujours rester"),
]
for main, croupier, attendu, explication in cas_strat:
action = strategie(main, croupier)
icone = "✅" if action == attendu else "❌"
print(f" {icone} {str(main):<12} vs {croupier} → {action} {explication}")
# --- Simulation finale ---
print("\n📊 SIMULATION : 100 000 parties | 6 decks\n")
stats = simuler(nb_parties=100_000, nb_decks=6)
print(f" ┌─────────────────────────┬──────────┬──────────┬──────────┬──────────┐")
print(f" │ │ v1 simple│v2 complète│ v3 corr. │ v4 soft │")
print(f" ├─────────────────────────┼──────────┼──────────┼──────────┼──────────┤")
print(f" │ Taux de victoire │ ~42% │ ~42% │ ~43% │ {stats['taux_victoire']}% │")
print(f" │ dont blackjacks 3:2 │ - │ - │ ~4.6% │ {stats['taux_blackjack']}% │")
print(f" │ Taux de défaite │ ~50% │ ~48% │ ~48% │ {stats['taux_défaite']}% │")
print(f" │ Taux d'égalité │ ~8% │ ~9% │ ~9% │ {stats['taux_égalité']}% │")
print(f" │ Gain par main │ -0.07 │ -0.047 │ -0.015 │ {stats['gain_par_main']} │")
print(f" └─────────────────────────┴──────────┴──────────┴──────────┴──────────┘")
avantage = abs(stats['gain_par_main']) * 100
print(f"\n Gain total : {stats['gain_total']:+,.1f}")
print(f" Avantage de la maison estimé : {avantage:.2f}%")
print(f" Valeur théorique attendue : ~0.50%")
if avantage < 1.0:
print("\n ✅ Excellent ! Très proche du théorique.")
print(" La stratégie est quasi-complète.")
elif avantage < 1.5:
print("\n Bon résultat, encore un petit écart résiduel.")
else:
print("\n Écart encore significatif — à investiguer.")
print("\n" + "=" * 62)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment