Un script Python robuste pour analyser des images de disquette .dsk (Amstrad CPC), détecter le répertoire, reconstituer la liste des fichiers et générer automatiquement un cat.txt lisible. Gère les formats variés, les disques protégés et les images corrompues.
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 randomimport loggingimport tkinter as tkfrom tkinter import messageboxfrom tkinter import ttkfrom collections import dequeimport osimport sysimport argparse# -------------------------# Import optionnels# -------------------------# PIL (images de cartes)try:from PIL import Image, ImageTk PIL_AVAILABLE =TrueexceptException: PIL_AVAILABLE =False# Matplotlib (graphique)try:import matplotlib.pyplot as plt MATPLOTLIB_AVAILABLE =TrueexceptException: 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 mappingHI_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'imagesCARD_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# -------------------------defbuild_shoe(num_decks):"""Construit une 'shoe' (paquet multiple) sous forme deque de tuples (rank, suit).""" shoe =[]for _ inrange(num_decks):for s in SUITS:for r in RANKS: shoe.append((r, s)) random.shuffle(shoe)returndeque(shoe)defcard_value(card):"""Retourne la valeur du rang (A=11 par défaut, on ajustera après).""" r, _ = cardreturn VALUE_MAP[r]defcard_str(card):"""Représentation texte d'une carte (ex: 'A♠').""" r, s = card suit_symbol ={'S':'♠','H':'♥','D':'♦','C':'♣'}[s]returnf"{r}{suit_symbol}"defhi_lo_value(card):"""Valeur Hi-Lo d'une carte donnée (int).""" r, _ = cardreturn HI_LO_MAP[r]# -------------------------# Conseiller basique (Basic Strategy simplifiée)# -------------------------defbasic_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(1for c in player_hand if c[0]=='A') soft =Falseif aces and total <=21: soft =True dealer_rank = dealer_upcard[0] dealer_val =10if dealer_rank in['10','J','Q','K']else(11if dealer_rank=='A'elseint(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'if12<= total <=16:if2<= dealer_val <=6:return'Stand'else:return'Hit'return'Hit'# -------------------------# Classe principale GUI# -------------------------classBlackjackTrainerGUI:def__init__(self,master,num_decks=6,base_bet=10):self.master = master master.title('Blackjack Trainer - Hi-Lo')# Paramètresself.num_decks = num_decksself.base_bet = base_betself.player_money =1000self.min_bet = base_betself.max_bet =10000self.debug = DEBUG_MODE# Delais configurables (ms)self.dealer_draw_delay_ms =700self.initial_dealer_start_delay_ms =150self.end_round_delay_ms =200self.player_end_delay_ms =800# pour afficher la carte du joueur avant résolution# Shoe et compteself.shoe =build_shoe(self.num_decks)self.running_count =0self.hands_played =0self.history_money =[self.player_money]self.history_count =[self.running_count]# Chargement images si possibleself.card_images ={}if PIL_AVAILABLE:self._load_card_images()else: logger.info('Pillow non disponible: affichage texte des cartes')# Construire UIself._build_ui()# Forcer une taille minimale pour éviter clipping des slotstry:self.master.minsize(480,260)exceptException:pass# Démarrer la première mainself.new_round()# -------------------------# Chargement images# -------------------------def_load_card_images(self): dir_path = CARD_IMAGE_DIRifnot os.path.isdir(dir_path): logger.warning(f"Dossier d'images '{dir_path}' introuvable. Utilisation du texte.")return loaded =0for 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 +=1exceptExceptionas e: logger.warning(f"Erreur chargement image {path}: {e}")else:ifself.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 absentself.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 croupierself.dealer_canvas = ttk.Frame(dealer_frame)self.dealer_canvas.pack(fill='x',padx=6,pady=6)self.dealer_slots =[]for _ inrange(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 _ inrange(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)iflen(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 countself.running_count +=hi_lo_value(card)ifself.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 carddef_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)ifhasattr(self,'player_hand')else0 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é."""ifhasattr(self,'player_hand')andhasattr(self,'dealer_hand')andlen(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("")defnew_round(self):"""Commence une nouvelle main: distribue deux cartes au joueur et au croupier."""# Réactiver contrôles (garantie)self._enable_controls()ifself.player_money <=0: messagebox.showwarning('Fin',"Vous n'avez plus d'argent. Fin de la session.")return# Distributionself.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'utilisateurself.current_bet =int(self.bet_var.get())self.hands_played +=1ifself.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 inself.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'UItry:self.master.update_idletasks()exceptException:passself._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."""# Dealerfor i inrange(MAX_CARD_SLOTS): lbl =self.dealer_slots[i]if i <len(self.dealer_hand):# cas de la seconde carte cachéeif i ==1and hide_dealer_second: lbl.config(text='[??]',image='',borderwidth=1,relief='solid',bg='white',fg='black') lbl.image =Noneelse: card =self.dealer_hand[i]if card inself.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 =Noneelse:# slot vide lbl.config(text='',image='',borderwidth=0,relief='flat',bg='white') lbl.image =None# Playerfor i inrange(MAX_CARD_SLOTS): lbl =self.player_slots[i]if i <len(self.player_hand): card =self.player_hand[i]if card inself.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 =Noneelse: lbl.config(text='',image='',borderwidth=0,relief='flat',bg='white') lbl.image =None# Debug: log what est affiché dans les slotsifself.debug:try: dealer_texts =[self.dealer_slots[i].cget('text')for i inrange(min(MAX_CARD_SLOTS,max(1,len(self.dealer_hand)+1)))] logger.debug(f"Rendering dealer slots (texts): {dealer_texts}")exceptException:pass# Forcer rafraîchissement UItry:self.master.update_idletasks()exceptException:passdefhit(self):"""Le joueur tire une carte."""ifnotself._controls_enabled():returnself.player_hand.append(self._draw_from_shoe())ifself.debug: logger.debug(f'Player hits, hand now: {[card_str(c)for c inself.player_hand]}') total =self._hand_value(self.player_hand)# Toujours afficher la main complèteself._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 carteif total >=21:self._disable_controls()self.master.after(self.player_end_delay_ms,self.resolve_round)defstand(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):ifself._hand_value(self.dealer_hand)<17:self.dealer_hand.append(self._draw_from_shoe())ifself.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()defresolve_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)==2and player_total ==21) dealer_blackjack =(len(self.dealer_hand)==2and dealer_total ==21)if player_blackjack andnot dealer_blackjack: payout =int(1.5* bet)self.player_money += payout result_text =f'Blackjack! Vous gagnez {payout}.'elif dealer_blackjack andnot 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 >21or 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(1for c in hand if c[0]=='A')while total >21and aces: total -=10 aces -=1return totaldef_true_count(self): decks_rem =max(1.0,len(self.shoe)/52.0)returnself.running_count / decks_remdef_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_betelse: 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'])exceptException:try: w.config(state='disabled')exceptException:passtry:self.bet_spin.config(state='disabled')exceptException:passdef_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'])exceptException:try: w.config(state='normal')exceptException:passtry:self.bet_spin.config(state='normal')exceptException:passdef_controls_enabled(self):try:return'disabled'notinself.hit_btn.state()exceptException:try:returnself.hit_btn['state']!='disabled'exceptException:returnTrue# -------------------------# Graphiques & utilitaires# -------------------------defshow_graph(self):ifnot 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.')deftoggle_debug(self):self.debug =notself.debug logger.setLevel(logging.DEBUG ifself.debug else logging.INFO) logger.info(f'Debug mode set to {self.debug}')# -------------------------# Tests unitaires (non-GUI)# -------------------------defrun_unit_tests():print('Running unit tests...') shoe =build_shoe(2)assertlen(shoe)==52*2,'build_shoe: taille incorrecte'assertcard_value(('A','S'))==11assertcard_value(('K','H'))==10asserthi_lo_value(('2','C'))==1asserthi_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()exceptExceptionas e:print('Impossible de démarrer l\'interface graphique:', e) sys.exit(1) gui =BlackjackTrainerGUI(root,num_decks=6,base_bet=10) root.mainloop()
Chat Control est un projet de règlement de l’Union européenne proposé par la Commission le 11 mai 2022 afin d’instaurer un cadre légal pour détecter, signaler et retirer les contenus d’abus sexuels sur mineur·es en ligne (CSAR/CSAM)1.