Created
March 24, 2026 14:45
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| ============================================================= | |
| 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