Blackjack

Dans
compter les cartes
- Blackjack : le but est d’avoir une main plus proche de 21 que le croupier sans dépasser 21.
- Chaque carte a une valeur dans le comptage :
- Cartes basses (2 à 6) = +1
- Cartes neutres (7 à 9) = 0
- Cartes hautes (10, figures, as) = -1
- Si le reste du paquet est riche en cartes hautes (10, as), le joueur a plus de chances de faire blackjack et de gagner.
- Si le reste est riche en cartes basses, le croupier a un avantage plus important.
L’astuce : augmenter les mises quand le paquet est favorable, et miser peu quand il ne l’est pas.

import random
import logging
import tkinter as tk
from tkinter import messagebox
from tkinter import ttk
from collections import deque
import os
import sys
import argparse
# -------------------------
# Import optionnels
# -------------------------
# PIL (images de cartes)
try:
from PIL import Image, ImageTk
PIL_AVAILABLE = True
except Exception:
PIL_AVAILABLE = False
# Matplotlib (graphique)
try:
import matplotlib.pyplot as plt
MATPLOTLIB_AVAILABLE = True
except Exception:
MATPLOTLIB_AVAILABLE = False
# -------------------------
# Configuration du logging
# -------------------------
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s')
logger = logging.getLogger('BlackjackTrainer')
# Mode debug détaillé (comportement et prints supplémentaires)
DEBUG_MODE = False
# -------------------------
# Constantes et utilitaires
# -------------------------
RANKS = ['A','2','3','4','5','6','7','8','9','10','J','Q','K']
SUITS = ['S','H','D','C']
# Valeur blackjack nominale (A peut être 11 ou 1 selon l'ajustement)
VALUE_MAP = { 'A':11, '2':2, '3':3, '4':4, '5':5, '6':6, '7':7, '8':8, '9':9, '10':10, 'J':10, 'Q':10, 'K':10 }
# Hi-Lo count mapping
HI_LO_MAP = { '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 }
# Nom du dossier d'images
CARD_IMAGE_DIR = 'cards'
CARD_IMAGE_SIZE = (72, 96) # taille d'affichage (largeur, hauteur)
# Nombre max de slots (réduit pour éviter clipping)
MAX_CARD_SLOTS = 6
# -------------------------
# Fonctions utilitaires
# -------------------------
def build_shoe(num_decks):
"""Construit une 'shoe' (paquet multiple) sous forme deque de tuples (rank, suit)."""
shoe = []
for _ in range(num_decks):
for s in SUITS:
for r in RANKS:
shoe.append((r, s))
random.shuffle(shoe)
return deque(shoe)
def card_value(card):
"""Retourne la valeur du rang (A=11 par défaut, on ajustera après)."""
r, _ = card
return VALUE_MAP[r]
def card_str(card):
"""Représentation texte d'une carte (ex: 'A♠')."""
r, s = card
suit_symbol = {'S':'♠','H':'♥','D':'♦','C':'♣'}[s]
return f"{r}{suit_symbol}"
def hi_lo_value(card):
"""Valeur Hi-Lo d'une carte donnée (int)."""
r, _ = card
return HI_LO_MAP[r]
# -------------------------
# Conseiller basique (Basic Strategy simplifiée)
# -------------------------
def basic_strategy_advice(player_hand, dealer_upcard):
"""
Fournit un conseil simple: 'Hit' ou 'Stand' (et 'Double' si applicable).
C'est une version simplifiée pour l'entraînement.
"""
total = sum(card_value(c) for c in player_hand)
aces = sum(1 for c in player_hand if c[0] == 'A')
soft = False
if aces and total <= 21:
soft = True
dealer_rank = dealer_upcard[0]
dealer_val = 10 if dealer_rank in ['10','J','Q','K'] else (11 if dealer_rank=='A' else int(dealer_rank))
if soft:
if total >= 19:
return 'Stand'
if total == 18:
if dealer_val in [9,10,11]:
return 'Hit'
return 'Stand'
return 'Hit'
else:
if total >= 17:
return 'Stand'
if total <= 11:
return 'Hit'
if 12 <= total <= 16:
if 2 <= dealer_val <= 6:
return 'Stand'
else:
return 'Hit'
return 'Hit'
# -------------------------
# Classe principale GUI
# -------------------------
class BlackjackTrainerGUI:
def __init__(self, master, num_decks=6, base_bet=10):
self.master = master
master.title('Blackjack Trainer - Hi-Lo')
# Paramètres
self.num_decks = num_decks
self.base_bet = base_bet
self.player_money = 1000
self.min_bet = base_bet
self.max_bet = 10000
self.debug = DEBUG_MODE
# Delais configurables (ms)
self.dealer_draw_delay_ms = 700
self.initial_dealer_start_delay_ms = 150
self.end_round_delay_ms = 200
self.player_end_delay_ms = 800 # pour afficher la carte du joueur avant résolution
# Shoe et compte
self.shoe = build_shoe(self.num_decks)
self.running_count = 0
self.hands_played = 0
self.history_money = [self.player_money]
self.history_count = [self.running_count]
# Chargement images si possible
self.card_images = {}
if PIL_AVAILABLE:
self._load_card_images()
else:
logger.info('Pillow non disponible: affichage texte des cartes')
# Construire UI
self._build_ui()
# Forcer une taille minimale pour éviter clipping des slots
try:
self.master.minsize(480, 260)
except Exception:
pass
# Démarrer la première main
self.new_round()
# -------------------------
# Chargement images
# -------------------------
def _load_card_images(self):
dir_path = CARD_IMAGE_DIR
if not os.path.isdir(dir_path):
logger.warning(f"Dossier d'images '{dir_path}' introuvable. Utilisation du texte.")
return
loaded = 0
for r in RANKS:
for s in SUITS:
fname = f"{r}{s}.png"
path = os.path.join(dir_path, fname)
if os.path.isfile(path):
try:
im = Image.open(path).convert('RGBA')
im = im.resize(CARD_IMAGE_SIZE, Image.LANCZOS)
self.card_images[(r,s)] = ImageTk.PhotoImage(im)
loaded += 1
except Exception as e:
logger.warning(f"Erreur chargement image {path}: {e}")
else:
if self.debug:
logger.debug(f"Image non trouvée: {path}")
logger.info(f"Images chargées: {loaded} cartes")
# -------------------------
# UI Builder
# -------------------------
def _build_ui(self):
# Top info
top = ttk.Frame(self.master, padding=8)
top.pack(fill='x')
self.info_var = tk.StringVar()
info_label = ttk.Label(top, textvariable=self.info_var, font=('Courier', 11))
info_label.pack(side='left', padx=6)
# Buttons
btn_frame = ttk.Frame(self.master, padding=6)
btn_frame.pack(fill='x')
self.hit_btn = ttk.Button(btn_frame, text='Hit', command=self.hit)
self.stand_btn = ttk.Button(btn_frame, text='Stand', command=self.stand)
self.new_btn = ttk.Button(btn_frame, text='Nouvelle main', command=self.new_round)
if MATPLOTLIB_AVAILABLE:
self.graph_btn = ttk.Button(btn_frame, text='Graphiques', command=self.show_graph)
else:
# bouton désactivé si matplotlib absent
self.graph_btn = ttk.Button(btn_frame, text='Graphiques (requires matplotlib)', state='disabled', command=self._graph_unavailable)
self.debug_btn = ttk.Button(btn_frame, text='Toggle Debug', command=self.toggle_debug)
for w in (self.hit_btn, self.stand_btn, self.new_btn, self.graph_btn, self.debug_btn):
w.pack(side='left', padx=4)
# Board frames
board = ttk.Frame(self.master, padding=6)
board.pack(fill='both', expand=True)
dealer_frame = ttk.LabelFrame(board, text='Croupier')
dealer_frame.pack(fill='x', pady=4)
# ici on crée des slots fixes pour le croupier
self.dealer_canvas = ttk.Frame(dealer_frame)
self.dealer_canvas.pack(fill='x', padx=6, pady=6)
self.dealer_slots = []
for _ in range(MAX_CARD_SLOTS):
lbl = tk.Label(self.dealer_canvas, text='', borderwidth=1, relief='solid',
width=8, height=4, padx=6, pady=6, bg='white', fg='black', anchor='center')
lbl.pack(side='left', padx=3)
self.dealer_slots.append(lbl)
player_frame = ttk.LabelFrame(board, text='Joueur')
player_frame.pack(fill='x', pady=4)
self.player_canvas = ttk.Frame(player_frame)
self.player_canvas.pack(fill='x', padx=6, pady=6)
self.player_slots = []
for _ in range(MAX_CARD_SLOTS):
lbl = tk.Label(self.player_canvas, text='', borderwidth=1, relief='solid',
width=8, height=4, padx=6, pady=6, bg='white', fg='black', anchor='center')
lbl.pack(side='left', padx=3)
self.player_slots.append(lbl)
# Advice
advice_frame = ttk.Frame(self.master, padding=6)
advice_frame.pack(fill='x')
self.advice_var = tk.StringVar()
advice_label = ttk.Label(advice_frame, textvariable=self.advice_var, font=('Arial', 11, 'italic'))
advice_label.pack(side='left')
# Bet control
bet_frame = ttk.Frame(self.master, padding=6)
bet_frame.pack(fill='x')
ttk.Label(bet_frame, text='Mise:').pack(side='left')
self.bet_var = tk.IntVar(value=self.base_bet)
self.bet_spin = ttk.Spinbox(bet_frame, from_=self.min_bet, to=self.max_bet, textvariable=self.bet_var, width=8)
self.bet_spin.pack(side='left', padx=6)
# -------------------------
# Mécanique de jeu
# -------------------------
def _draw_from_shoe(self):
"""Tire une carte et gère le reshuffle quand la shoe est entamée."""
# Remélanger si il reste moins de 25% de la shoe (paramètre configurable)
if len(self.shoe) < int(0.25 * 52 * self.num_decks):
logger.info('Reshuffle: pas assez de cartes, reshuffle')
self.shoe = build_shoe(self.num_decks)
self.running_count = 0
card = self.shoe.popleft()
# Mettre à jour running count
self.running_count += hi_lo_value(card)
if self.debug:
logger.debug(f'Draw card: {card_str(card)} | hi-lo {hi_lo_value(card)} | running_count {self.running_count}')
self.history_count.append(self.running_count)
return card
def _update_info(self):
"""Met à jour la barre d'information en haut (argent, compte, true count, total joueur)."""
player_total = self._hand_value(self.player_hand) if hasattr(self, 'player_hand') else 0
true_count = self._true_count()
suggested_bet = self._suggest_bet()
info_text = (f"Argent: {self.player_money} "
f"Compte (running): {self.running_count} "
f"True Count: {true_count:.2f} "
f"Mise suggérée: {suggested_bet} "
f"Main joueur: {player_total}")
self.info_var.set(info_text)
def _update_advice(self):
"""Met à jour le conseil de base affiché."""
if hasattr(self, 'player_hand') and hasattr(self, 'dealer_hand') and len(self.dealer_hand) > 0:
advice = basic_strategy_advice(self.player_hand, self.dealer_hand[0])
self.advice_var.set(f"Conseil (basic): {advice}")
else:
self.advice_var.set("")
def new_round(self):
"""Commence une nouvelle main: distribue deux cartes au joueur et au croupier."""
# Réactiver contrôles (garantie)
self._enable_controls()
if self.player_money <= 0:
messagebox.showwarning('Fin', "Vous n'avez plus d'argent. Fin de la session.")
return
# Distribution
self.player_hand = [self._draw_from_shoe(), self._draw_from_shoe()]
self.dealer_hand = [self._draw_from_shoe(), self._draw_from_shoe()]
# récupérer la mise **courante** choisie par l'utilisateur
self.current_bet = int(self.bet_var.get())
self.hands_played += 1
if self.debug:
logger.debug(f'New round: player {self.player_hand}, dealer {self.dealer_hand}, bet {self.current_bet}')
logger.debug(f'Dealer hand ranks: {[c[0] for c in self.dealer_hand]}')
# Afficher la main du croupier avec la seconde carte cachée (placeholder)
self._render_cards(hide_dealer_second=True)
# Forcer le rendu immédiat de l'UI
try:
self.master.update_idletasks()
except Exception:
pass
self._update_info()
self._update_advice()
def _render_cards(self, hide_dealer_second=False):
"""Met à jour les slots existants au lieu de recréer des widgets."""
# Dealer
for i in range(MAX_CARD_SLOTS):
lbl = self.dealer_slots[i]
if i < len(self.dealer_hand):
# cas de la seconde carte cachée
if i == 1 and hide_dealer_second:
lbl.config(text='[??]', image='', borderwidth=1, relief='solid', bg='white', fg='black')
lbl.image = None
else:
card = self.dealer_hand[i]
if card in self.card_images:
lbl.config(image=self.card_images[card], text='', borderwidth=0, relief='flat', bg='white')
lbl.image = self.card_images[card]
else:
lbl.config(text=card_str(card), image='', borderwidth=1, relief='solid', bg='white', fg='black')
lbl.image = None
else:
# slot vide
lbl.config(text='', image='', borderwidth=0, relief='flat', bg='white')
lbl.image = None
# Player
for i in range(MAX_CARD_SLOTS):
lbl = self.player_slots[i]
if i < len(self.player_hand):
card = self.player_hand[i]
if card in self.card_images:
lbl.config(image=self.card_images[card], text='', borderwidth=0, relief='flat', bg='white')
lbl.image = self.card_images[card]
else:
lbl.config(text=card_str(card), image='', borderwidth=1, relief='solid', bg='white', fg='black')
lbl.image = None
else:
lbl.config(text='', image='', borderwidth=0, relief='flat', bg='white')
lbl.image = None
# Debug: log what est affiché dans les slots
if self.debug:
try:
dealer_texts = [self.dealer_slots[i].cget('text') for i in range(min(MAX_CARD_SLOTS, max(1, len(self.dealer_hand)+1)))]
logger.debug(f"Rendering dealer slots (texts): {dealer_texts}")
except Exception:
pass
# Forcer rafraîchissement UI
try:
self.master.update_idletasks()
except Exception:
pass
def hit(self):
"""Le joueur tire une carte."""
if not self._controls_enabled():
return
self.player_hand.append(self._draw_from_shoe())
if self.debug:
logger.debug(f'Player hits, hand now: {[card_str(c) for c in self.player_hand]}')
total = self._hand_value(self.player_hand)
# Toujours afficher la main complète
self._render_cards(hide_dealer_second=True)
self._update_info()
self._update_advice()
# Si la main est finie (21 ou bust), attendre un petit délai avant de résoudre pour que l'utilisateur voie la carte
if total >= 21:
self._disable_controls()
self.master.after(self.player_end_delay_ms, self.resolve_round)
def stand(self):
"""Le joueur reste: croupier joue selon règle (stand sur soft 17)."""
self._render_cards(hide_dealer_second=False)
self._update_info()
self._disable_controls()
self.master.after(self.initial_dealer_start_delay_ms, self._dealer_play)
def _dealer_play(self):
if self._hand_value(self.dealer_hand) < 17:
self.dealer_hand.append(self._draw_from_shoe())
if self.debug:
logger.debug(f'Dealer draws: {card_str(self.dealer_hand[-1])}')
self._render_cards(hide_dealer_second=False)
self._update_info()
self.master.after(self.dealer_draw_delay_ms, self._dealer_play)
else:
self.master.after(self.end_round_delay_ms, self._end_dealer_and_resolve)
def _end_dealer_and_resolve(self):
self._enable_controls()
self.resolve_round()
def resolve_round(self):
player_total = self._hand_value(self.player_hand)
dealer_total = self._hand_value(self.dealer_hand)
bet = self.current_bet
player_blackjack = (len(self.player_hand) == 2 and player_total == 21)
dealer_blackjack = (len(self.dealer_hand) == 2 and dealer_total == 21)
if player_blackjack and not dealer_blackjack:
payout = int(1.5 * bet)
self.player_money += payout
result_text = f'Blackjack! Vous gagnez {payout}.'
elif dealer_blackjack and not player_blackjack:
self.player_money -= bet
result_text = 'Le croupier a Blackjack. Vous perdez.'
else:
if player_total > 21:
self.player_money -= bet
result_text = 'Bust! Vous perdez.'
elif dealer_total > 21 or player_total > dealer_total:
self.player_money += bet
result_text = 'Vous gagnez.'
elif player_total == dealer_total:
result_text = 'Push (égalité).'
else:
self.player_money -= bet
result_text = 'Vous perdez.'
self.history_money.append(self.player_money)
message = (f"{result_text}\nArgent restant: {self.player_money}\n"
f"Compte running: {self.running_count} | True Count: {self._true_count():.2f}")
messagebox.showinfo('Résultat', message)
self.new_round()
# -------------------------
# Calculs & stratégies
# -------------------------
def _hand_value(self, hand):
total = sum(card_value(c) for c in hand)
aces = sum(1 for c in hand if c[0] == 'A')
while total > 21 and aces:
total -= 10
aces -= 1
return total
def _true_count(self):
decks_rem = max(1.0, len(self.shoe) / 52.0)
return self.running_count / decks_rem
def _suggest_bet(self):
"""Retourne la mise recommandée sans modifier la Spinbox de l'utilisateur."""
tc = self._true_count()
if tc <= 1:
bet = self.base_bet
else:
level = int(tc)
bet = min(self.player_money, self.base_bet * level)
return bet
# -------------------------
# Contrôles utilitaires
# -------------------------
def _disable_controls(self):
for w in (self.hit_btn, self.stand_btn, self.new_btn, self.graph_btn, self.debug_btn):
try:
w.state(['disabled'])
except Exception:
try:
w.config(state='disabled')
except Exception:
pass
try:
self.bet_spin.config(state='disabled')
except Exception:
pass
def _enable_controls(self):
for w in (self.hit_btn, self.stand_btn, self.new_btn, self.graph_btn, self.debug_btn):
try:
w.state(['!disabled'])
except Exception:
try:
w.config(state='normal')
except Exception:
pass
try:
self.bet_spin.config(state='normal')
except Exception:
pass
def _controls_enabled(self):
try:
return 'disabled' not in self.hit_btn.state()
except Exception:
try:
return self.hit_btn['state'] != 'disabled'
except Exception:
return True
# -------------------------
# Graphiques & utilitaires
# -------------------------
def show_graph(self):
if not MATPLOTLIB_AVAILABLE:
messagebox.showwarning('Graphiques indisponibles', 'matplotlib n\'est pas installé dans cet environnement.')
return
fig, ax1 = plt.subplots(figsize=(10,5))
ax1.plot(self.history_money, label='Argent du joueur')
ax1.set_xlabel('Mains jouées')
ax1.set_ylabel('Argent')
ax1.legend(loc='upper left')
ax2 = ax1.twinx()
ax2.plot(self.history_count, label='Running Count', linestyle='--')
ax2.set_ylabel('Running Count')
ax2.legend(loc='upper right')
plt.title('Historique: Argent et Running Count')
plt.grid(True)
plt.show()
def _graph_unavailable(self):
messagebox.showinfo('Graphiques désactivés', 'La fonctionnalité de graphiques nécessite matplotlib.\nInstallez-le via "pip install matplotlib" si vous voulez activer cette fonction.')
def toggle_debug(self):
self.debug = not self.debug
logger.setLevel(logging.DEBUG if self.debug else logging.INFO)
logger.info(f'Debug mode set to {self.debug}')
# -------------------------
# Tests unitaires (non-GUI)
# -------------------------
def run_unit_tests():
print('Running unit tests...')
shoe = build_shoe(2)
assert len(shoe) == 52 * 2, 'build_shoe: taille incorrecte'
assert card_value(('A','S')) == 11
assert card_value(('K','H')) == 10
assert hi_lo_value(('2','C')) == 1
assert hi_lo_value(('A','D')) == -1
advice = basic_strategy_advice([('Q','H'),('4','D')], ('6','S'))
assert advice == 'Stand', f'Expected Stand, got {advice}'
advice2 = basic_strategy_advice([('A','H'),('6','D')], ('10','S'))
assert advice2 == 'Hit', f'Expected Hit, got {advice2}'
advice3 = basic_strategy_advice([('10','H'),('9','D')], ('7','C'))
assert advice3 == 'Stand', f'Expected Stand, got {advice3}'
print('All non-GUI tests passed.')
# -------------------------
# Main
# -------------------------
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Blackjack Trainer GUI')
parser.add_argument('--test', action='store_true', help='Run non-GUI unit tests and exit')
args = parser.parse_args()
if args.test:
run_unit_tests()
sys.exit(0)
try:
root = tk.Tk()
except Exception as e:
print('Impossible de démarrer l\'interface graphique:', e)
sys.exit(1)
gui = BlackjackTrainerGUI(root, num_decks=6, base_bet=10)
root.mainloop()
Auteur/autrice
sdgadmin@tux.ovh
Publications similaires


parse_dsk.py
Un script Python robuste pour analyser des images de disquette .dsk (Amstrad CPC), détecter le répertoire, reconstituer la liste des fichiers et...
Lire la suite
cpcdb.py
Le script va maintenant : ✅ Se connecter au serveur MySQL ✅ Créer la table dsk_games dans la base cpcdb ✅ Scanner...
Lire la suite
Dans
iamuz.py
Script python pour télécharger le contenu d’une archive en fonction de l’id sur Internet Archive,
Lire la suite
CPC.PY
✅ Fait quoi ? 📁 Arborescence produite : 📂 cpc_games/ ← Fichiers .dsk, .zip…📂 screenshots/ ← Captures d’écran (cover, title, etc.)📄 cpc_software_metadata.csv...
Lire la suite