diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..39587cf --- /dev/null +++ b/.gitignore @@ -0,0 +1,68 @@ +# MasterMind .gitignore +# ===================== + +# Fichiers compilés Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +env/ +venv/ +ENV/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# Environnement et variables +.env +.env.local +*.env + +# IDE / Éditeurs +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# Configuration spécifique +*.iml +.project +.pydevproject +.settings/ + +# Fichiers de données générés +data/*.png +# (Garde le repertoire data mais ignore les fichiers si ajoutés) + +# Logs +*.log + +# Tests et coverage +.pytest_cache/ +.coverage +htmlcov/ +.tox/ + +# Continue.dev +.continue/ + +# Cursor IDE +.cursor/ + +# Theia +.theia/ diff --git a/README.md b/README.md new file mode 100644 index 0000000..9eef423 --- /dev/null +++ b/README.md @@ -0,0 +1,184 @@ +# MasterMind + +> Implémentation élégante du jeu de logique classique en Python avec interface Tkinter. + +![Python](https://img.shields.io/badge/Python-3.7+-blue.svg) +![License](https://img.shields.io/badge/License-MIT-green.svg) + +--- + +## 📋 Description + +**MasterMind** est un jeu de logique et de déduction où le joueur doit deviner un code secret composé de 4 couleurs parmi 6 possibilités. À chaque tentative, le jeu fournit un feedback indiquant le nombre de couleurs correctement placées (noirs) et le nombre de couleurs présentes mais mal placées (blanches). + +Cette implémentation offre une interface graphique soignée avec des boutons octogonaux Originaux, un historique scrollable des tentatives, et une expérience utilisateur fluide. + +--- + +## 🎮 Règles du Jeu + +### Configuration Standard +- **Code secret**: 4 positions +- **Couleurs disponibles**: 6 (Rouge, Bleu, Vert, Jaune, Orange, Violet) +- **Tentatives maximum**: 15 +- **Répétition de couleurs**: Autorisée + +### Déroulement d'une Partie +1. Le jeu génère aléatoirement un code secret de 4 couleurs. +2. Le joueur sélectionne ses 4 couleurs en cliquant sur les cercles ou en utilisant la palette. +3. En validant, le joueur reçoit le feedback: + - **⬛ Noir**: Bonne couleur, bonne position + - **⬜ Blanc**: Bonne couleur, mauvaise position +4. Le joueur continues jusqu'à: + - Trouver le code (victoire) + - Épuiser les 15 tentatives (défaite) + +### Commandes de l'Interface +- **Clic sur les cercles**: Cycle à travers les couleurs (par position) +- **Clic sur la palette**: Applique la couleur sélectionnée à toutes les positions +- **Valider**: Soumet la tentative actuelle +- **Rejouer**: Commence une nouvelle partie +- **Solution**: Révèle le code secret (mode triche) + +--- + +## 🚀 Installation et Utilisation + +### Prérequis +- Python 3.7 ou supérieur +- Tkinter (inclus dans la distribution standard Python) + +### Installation +```bash +# Cloner le dépôt (si applicable) +git clone +cd Master_Mind + +# Aucune dépendance externe n'est nécessaire! +# Tkinter est inclus avec Python +``` + +### Lancement du Jeu +```bash +cd src +python main.py +``` + +Ou directement: +```bash +python src/main.py +``` + +--- + +## 🏗️ Structure du Projet + +``` +Master_Mind/ +├── src/ +│ ├── main.py # Point d'entrée de l'application +│ ├── game_logic.py # Moteur de jeu (logique pure) +│ ├── gui.py # Interface graphique Tkinter +│ ├── config.py # Configuration centralisée +│ └── requirements.txt # Dépendances (toutes facultatives) +├── data/ +│ └── Master_mind.png # Icône de l'application +├── .gitignore # Exclusions pour Git +└── README.md # Ce fichier +``` + +--- + +## 🎨 Architecture Technique + +### Séparation des Responsabilités +Le projet suit une Architecture Moderne avec séparation claire: + +- **`game_logic.py`**: Moteur de jeu indépendant, testable sans interface +- **`gui.py`**: Vue qui utilise l'API du moteur +- **`config.py`**: Configuration centralisée pour personnalisation facile + +### Design Patterns +- **MVC inspiré**: Modèle (game_logic), Vue (gui), Contrôleur (événements) +- **Configuration Centralisée**: Tous les paramètres dans `config.py` +- **Type Hints**: annotations de type Python pour la clarté + +--- + +## ⚙️ Personnalisation + +Tous les paramètres du jeu sont dans `src/config.py`: + +```python +CODE_LENGTH = 4 # Longueur du code secret +MAX_ATTEMPTS = 15 # Nombre de tentatives +NUM_COLORS = 6 # Nombre de couleurs dans la palette + +# Palette de couleurs (nom, hex) +COLORS = [ + ('Rouge', '#FF0000'), + ('Bleu', '#0000FF'), + # ... ajoutez vos propres couleurs +] + +# Dimensions de l'interface +CIRCLE_DIAMETER = 30 # Taille des cercles de sélection +WINDOW_WIDTH = 450 # Largeur de la fenêtre +WINDOW_HEIGHT = 800 # Hauteur de la fenêtre +``` + +--- + +## 🧪 Tests + +Le fichier `game_logic.py` inclut un test simple en `__main__`: + +```bash +python -m src.game_logic +``` + +Pour des tests complets, il serait judicieux d'ajouter `pytest` et d'écrire une suite de tests unitaires couvrant: +- Génération du code secret +- Calcul du feedback +- Gestion des tentatives +- Conditions de victoire/défaite + +--- + +## 📦 Dépendances + +**État actuel:** Aucune dépendance externe n'est requise. + +Le fichier `requirements.txt` contient initialement des bibliothèques courantes pour projets Python data/science. Pour ce jeu spécifique, **seul Tkinter est nécessaire** (inclus avec Python). + +Si vous souhaitez étendre le projet avec des fonctionnalités web, bases de données, ou analytics, vous pouvez utiliser les packages listés. + +--- + +## 🚧 Améliorations Possibles + +### Idées d'Évolution +- [ ] **Mode difficulté variable**: codes de 3-5 couleurs, 4-10 couleurs différentes +- [ ] **Solver intégré**: algorithme de résolution automatique (mode indice) +- [ ] **Statistiques**: suivi des parties, taux de victoire, distribution des tentatives +- [ ] **Persistance des scores**: fichier JSON ou base de données SQLite +- [ ] **Interface sonore**: feedback audio sur les victoires/erreurs +- [ ] **Thèmes visuels**: plusieurs palettes de couleurs/styles +- [ ] **Mode multijoueur**: un joueur crée le code, l'autre devine +- [ ] **Export d'historique**: sauvegarde des parties en CSV/JSON + +--- + +## 📄 Licence + +Ce projet est livré sans licence spécifique pour le moment. Vous êtes libre de l'utiliser, le modifier et le distribuer à des fins éducatives et personnelles. + +--- + +## 🙏 Remerciements + +Inspiré par le jeu de plateau classique MasterMind créé par [Invicta](https://en.wikipedia.org/wiki/Mastermind_(board_game)). + +--- + +**Amusez-vous bien à deviner ces codes colorés!** 🎨 diff --git a/src/config.py b/src/config.py new file mode 100644 index 0000000..7bb41a3 --- /dev/null +++ b/src/config.py @@ -0,0 +1,43 @@ +""" +Configuration centralisée du jeu MasterMind +""" + +# --- Règles du jeu --- +CODE_LENGTH = 4 # Nombre de positions dans le code secret +MAX_ATTEMPTS = 15 # Nombre maximum de tentatives +NUM_COLORS = 6 # Nombre de couleurs disponibles + +# --- Couleurs standard --- +# Format: (nom, couleur hex) +COLORS = [ + ('Rouge', '#FF0000'), + ('Bleu', '#0000FF'), + ('Vert', '#00FF00'), + ('Jaune', '#FFFF00'), + ('Orange', '#FFA500'), + ('Violet', '#800080'), +] + +# --- Interface Tkinter --- +# Dimensions des cercles de couleur +CIRCLE_DIAMETER = 30 +CIRCLE_SPACING = 8 + +# Feedback circles (petits cercles noirs/blancs pour indiquer correspondance) +FEEDBACK_DIAMETER = 10 +FEEDBACK_SPACING = 5 + +# Grille des tentatives +GRID_PADDING = 20 +ROW_HEIGHT = CIRCLE_DIAMETER + 40 # cercle + feedback + espace + +# Couleurs d'interface +BG_COLOR = '#F0F0F0' +BUTTON_COLOR = '#DDDDDD' +SELECTED_BORDER = '#000000' +DEFAULT_BORDER = '#AAAAAA' + +# Fenêtre +WINDOW_WIDTH = 450 +WINDOW_HEIGHT = 800 +WINDOW_TITLE = "MasterMind" \ No newline at end of file diff --git a/src/game_logic.py b/src/game_logic.py new file mode 100644 index 0000000..2023d4e --- /dev/null +++ b/src/game_logic.py @@ -0,0 +1,113 @@ +""" +Logique pure du jeu MasterMind - sans GUI +""" + +import random +from typing import List, Tuple +from config import CODE_LENGTH, NUM_COLORS, MAX_ATTEMPTS, COLORS + + +class MasterMindGame: + """Moteur du jeu MasterMind""" + + def __init__(self): + """Initialise une nouvelle partie""" + self.secret_code = self._generate_secret() + self.attempts = [] # Liste de tuples (guess, feedback) + self.game_over = False + self.won = False + + def _generate_secret(self) -> List[int]: + """Génère un code secret aléatoire (indices de couleurs)""" + return [random.randint(0, NUM_COLORS - 1) for _ in range(CODE_LENGTH)] + + def _calculate_feedback(self, guess: List[int]) -> Tuple[int, int]: + """ + Calcule le feedback pour une tentative. + Retourne (noirs, blancs) où: + - noirs = positions correctes (bonne couleur + bonne position) + - blancs = bonnes couleurs mais mauvaises positions + """ + secret = self.secret_code.copy() + guess_copy = guess.copy() + + # D'abord compter les noirs (positions exactes) + black = 0 + for i in range(CODE_LENGTH): + if guess_copy[i] == secret[i]: + black += 1 + guess_copy[i] = None # Marquer comme compté + secret[i] = None + + # Ensuite compter les blancs (bonnes couleurs mal placées) + white = 0 + for i in range(CODE_LENGTH): + if guess_copy[i] is not None: + for j in range(CODE_LENGTH): + if secret[j] is not None and guess_copy[i] == secret[j]: + white += 1 + secret[j] = None + break + + return black, white + + def make_guess(self, guess: List[int]) -> Tuple[bool, Tuple[int, int]]: + """ + Effectue une tentative. + Args: + guess: Liste d'indices de couleurs (0 à NUM_COLORS-1) + Returns: + (is_correct, feedback) où feedback = (noirs, blancs) + """ + if self.game_over: + raise ValueError("La partie est terminée") + + if len(guess) != CODE_LENGTH: + raise ValueError(f"Le guess doit contenir {CODE_LENGTH} couleurs") + + feedback = self._calculate_feedback(guess) + self.attempts.append((guess.copy(), feedback)) + + # Vérifier si gagné + if feedback[0] == CODE_LENGTH: + self.won = True + self.game_over = True + elif len(self.attempts) >= MAX_ATTEMPTS: + self.game_over = True + + return self.won, feedback + + def get_remaining_attempts(self) -> int: + """Retourne le nombre de tentatives restantes""" + return MAX_ATTEMPTS - len(self.attempts) + + def reveal_secret(self) -> List[int]: + """Retourne le code secret (pour affichage en fin de partie)""" + return self.secret_code.copy() + + def reset(self): + """Réinitialise la partie""" + self.__init__() + + +def guess_to_colors(guess: List[int]) -> List[str]: + """Convertit une liste d'indices en noms de couleurs""" + return [COLORS[i][0] for i in guess] + + +def feedback_to_string(feedback: Tuple[int, int]) -> str: + """Formatte le feedback pour affichage""" + black, white = feedback + return f"⬛{black} ⬜{white}" + + +if __name__ == "__main__": + # Test simple de la logique + game = MasterMindGame() + print(f"Code secret (indices): {game.secret_code}") + print(f"Code secret (couleurs): {guess_to_colors(game.secret_code)}") + + # Test d'une tentative + test_guess = game.secret_code.copy() # Devine le secret (should win) + won, feedback = game.make_guess(test_guess) + print(f"Guess: {guess_to_colors(test_guess)} -> Feedback: {feedback} - Gagné: {won}") \ No newline at end of file diff --git a/src/gui.py b/src/gui.py new file mode 100644 index 0000000..533ca8a --- /dev/null +++ b/src/gui.py @@ -0,0 +1,401 @@ +""" +Interface graphique Tkinter avancée pour MasterMind +""" + +import tkinter as tk +from tkinter import ttk +from typing import List, Tuple +from game_logic import MasterMindGame, guess_to_colors, feedback_to_string +from config import ( + COLORS, + CIRCLE_DIAMETER, + CIRCLE_SPACING, + FEEDBACK_DIAMETER, + FEEDBACK_SPACING, + GRID_PADDING, + ROW_HEIGHT, + BG_COLOR, + BUTTON_COLOR, + SELECTED_BORDER, + DEFAULT_BORDER, + WINDOW_WIDTH, + WINDOW_HEIGHT, + WINDOW_TITLE, +) + + +def create_octagon(canvas: tk.Canvas, x1: int, y1: int, x2: int, y2: int, **kwargs): + """Dessine un octogone (polygone à 8 côtés)""" + # Coins d'un rectangle + left, top, right, bottom = x1, y1, x2, y2 + width = right - left + height = bottom - top + cut = min(width, height) // 4 # Taille des coins coupés + + # Points de l'octogone (sens horaire) + points = [ + left + cut, + top, # haut-gauche (début coupe) + right - cut, + top, # haut-droit (début coupe) + right, + top + cut, # droite-haut (début coupe) + right, + bottom - cut, # droite-bas (début coupe) + right - cut, + bottom, # bas-droit (début coupe) + left + cut, + bottom, # bas-gauche (début coupe) + left, + bottom - cut, # gauche-bas (début coupe) + left, + top + cut, # gauche-haut (début coupe) + ] + return canvas.create_polygon(points, **kwargs) + + +class MasterMindGUI: + """Interface Tkinter pour MasterMind""" + + def __init__(self): + self.root = tk.Tk() + self.root.title(WINDOW_TITLE) + self.root.geometry(f"{WINDOW_WIDTH}x{WINDOW_HEIGHT}") + self.root.configure(bg=BG_COLOR) + + # Jeu + self.game = MasterMindGame() + + # Variables pour la sélection de couleurs + self.selected_colors = [0] * 4 # Indices des couleurs choisies + self.guess_buttons = [] # Canvas des 4 positions à deviner + + # Style ttk + self.style = ttk.Style() + self.style.theme_use("clam") + # ICI: configuration de la taille de police des boutons + # Modifie 'font' pour changer la taille (9 par défaut, 8 pour plus petit) + self.style.configure( + "TButton", padding=3, background=BUTTON_COLOR, font=("Helvetica", 8) + ) + self.style.configure("Title.TLabel", font=("Helvetica", 16, "bold")) + + # Layout principal en grille + self.root.columnconfigure(1, weight=1) # Colonne principale extensible + self.root.rowconfigure(2, weight=1) # Ligne historique extensible + + # Construction de l'interface + self._build_palette() # Colonne 0 (avec boutons de contrôle) + self._build_main_area() # Colonne 1 + + # Initialiser l'affichage + self._update_guess_display() + + def _build_palette(self): + """Palette verticale de boutons octogonaux (côté gauche) + boutons de contrôle""" + palette_frame = ttk.Frame(self.root, padding=10) + palette_frame.grid(row=0, column=0, rowspan=4, sticky="ns", padx=(10, 5)) + + ttk.Label(palette_frame, text="Couleurs", font=("Helvetica", 12, "bold")).pack( + pady=(0, 10) + ) + + self.palette_canvas = tk.Canvas( + palette_frame, width=70, height=350, bg=BG_COLOR, highlightthickness=0 + ) + self.palette_canvas.pack() + + # Créer les octogones pour chaque couleur + self.color_buttons = [] + button_size = 45 + spacing = 55 + start_y = 15 + + for idx, (name, color) in enumerate(COLORS): + x1 = 15 + y1 = start_y + idx * spacing + x2 = x1 + button_size + y2 = y1 + button_size + + # Octogone + oct_id = create_octagon( + self.palette_canvas, + x1, + y1, + x2, + y2, + fill=color, + outline="black", + width=2, + tags=f"color_{idx}", + ) + self.palette_canvas.tag_bind( + oct_id, "", lambda e, c=idx: self._select_color(c) + ) + + # Texte (nom de la couleur) + text_id = self.palette_canvas.create_text( + (x1 + x2) / 2, + (y1 + y2) / 2, + text=name[:3].upper(), + font=("Helvetica", 8, "bold"), + fill="white" if self._is_dark(color) else "black", + tags=f"text_{idx}", + ) + self.palette_canvas.tag_bind( + text_id, "", lambda e, c=idx: self._select_color(c) + ) + + self.color_buttons.append(oct_id) + + # Boutons de contrôle sous la palette + control_frame = ttk.Frame(palette_frame) + control_frame.pack(pady=(20, 0)) + + self.validate_btn = ttk.Button( + control_frame, text="Valider", command=self._on_validate, width=8 + ) + self.validate_btn.pack(pady=3) + + self.new_game_btn = ttk.Button( + control_frame, text="Rejouer", command=self._on_new_game, width=8 + ) + self.new_game_btn.pack(pady=3) + + self.reveal_btn = ttk.Button( + control_frame, text="Solution", command=self._on_reveal, width=8 + ) + self.reveal_btn.pack(pady=3) + + def _build_main_area(self): + """Zone principale (droite) avec titre, guess, historique""" + main_frame = ttk.Frame(self.root, padding=10) + main_frame.grid(row=0, column=1, rowspan=4, sticky="nsew", padx=(5, 10)) + + # Configuration de la grille interne + main_frame.columnconfigure(0, weight=1) + main_frame.rowconfigure(2, weight=1) # Historique extensible + + # Titre + title = ttk.Label(main_frame, text="MASTERMIND", style="Title.TLabel") + title.grid(row=0, column=0, pady=(0, 10)) + + # Zone de sélection du guess (votre combinaison) + guess_frame = ttk.LabelFrame(main_frame, text="Votre combinaison", padding=10) + guess_frame.grid(row=1, column=0, pady=(0, 10), sticky="ew") + guess_frame.columnconfigure(tuple(range(4)), weight=1) + + # Cercles cliquables pour le guess + self.guess_buttons = [] + for i in range(4): + canvas = tk.Canvas( + guess_frame, + width=CIRCLE_DIAMETER + 4, + height=CIRCLE_DIAMETER + 4, + bg=BG_COLOR, + highlightthickness=0, + ) + canvas.grid(row=0, column=i, padx=5, pady=5) + canvas.create_oval( + 2, + 2, + CIRCLE_DIAMETER + 2, + CIRCLE_DIAMETER + 2, + fill=COLORS[0][1], + outline=SELECTED_BORDER, + width=2, + tags=f"circle_{i}", + ) + canvas.bind("", lambda e, idx=i: self._on_guess_circle_click(idx)) + self.guess_buttons.append(canvas) + + # Historique + history_frame = ttk.LabelFrame(main_frame, text="Historique", padding=5) + history_frame.grid(row=2, column=0, sticky="nsew", pady=(10, 0)) + history_frame.columnconfigure(0, weight=1) + history_frame.rowconfigure(0, weight=1) + + # Canvas scrollable pour l'historique + self.history_canvas = tk.Canvas(history_frame, bg=BG_COLOR, height=250) + self.history_canvas.grid(row=0, column=0, sticky="nsew") + + scrollbar = ttk.Scrollbar( + history_frame, orient=tk.VERTICAL, command=self.history_canvas.yview + ) + scrollbar.grid(row=0, column=1, sticky="ns") + self.history_canvas.configure(yscrollcommand=scrollbar.set) + + # Frame conteneur + self.history_frame = ttk.Frame(self.history_canvas) + self.history_canvas.create_window( + (0, 0), window=self.history_frame, anchor="nw" + ) + self.history_frame.bind( + "", + lambda e: self.history_canvas.configure( + scrollregion=self.history_canvas.bbox("all") + ), + ) + + # Barre de statut (en bas de la fenêtre principale) + self.status_label = ttk.Label( + self.root, text="Prêt à jouer!", relief=tk.SUNKEN, anchor=tk.W + ) + self.status_label.grid(row=4, column=0, columnspan=2, sticky="ew", pady=(5, 0)) + + def _is_dark(self, hex_color: str) -> bool: + """Détermine si une couleur hexadécimale est sombre (pour texte blanc/noir)""" + hex_color = hex_color.lstrip("#") + r, g, b = tuple(int(hex_color[i : i + 2], 16) for i in (0, 2, 4)) + luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255 + return luminance < 0.5 + + def _on_guess_circle_click(self, position: int): + """Clic sur un cercle de guess -> change sa couleur""" + self.selected_colors[position] = (self.selected_colors[position] + 1) % len( + COLORS + ) + self._update_guess_display() + + def _select_color(self, color_idx: int): + """Sélection d'une couleur dans la palette -> affecte toutes les positions""" + for i in range(4): + self.selected_colors[i] = color_idx + self._update_guess_display() + + def _update_guess_display(self): + """Met à jour l'affichage des cercles de guess""" + for i, color_idx in enumerate(self.selected_colors): + color_hex = COLORS[color_idx][1] + canvas = self.guess_buttons[i] + canvas.itemconfig(f"circle_{i}", fill=color_hex) + + def _on_validate(self): + """Valide le guess actuel""" + try: + won, feedback = self.game.make_guess(self.selected_colors.copy()) + self._add_history_row(self.selected_colors.copy(), feedback, won) + self._update_status_after_guess(won, feedback) + + if self.game.game_over: + if won: + self.status_label.config(text="🎉 Félicitations! Vous avez gagné!") + else: + self.status_label.config( + text=f"💀 Perdu! Le code était: {guess_to_colors(self.game.reveal_secret())}" + ) + self.validate_btn.config(state="disabled") + else: + # Reset selection + self.selected_colors = [0] * 4 + self._update_guess_display() + + except ValueError as e: + self.status_label.config(text=str(e)) + + def _on_new_game(self): + """Nouvelle partie""" + self.game.reset() + self._clear_history() + self.selected_colors = [0] * 4 + self._update_guess_display() + self.validate_btn.config(state="normal") + self.status_label.config(text="Nouvelle partie!") + + def _on_reveal(self): + """Révèle le code secret (triche)""" + secret = self.game.reveal_secret() + colors = guess_to_colors(secret) + self.status_label.config(text=f"Code secret: {colors}") + + def _update_status_after_guess(self, won: bool, feedback: Tuple[int, int]): + """Met à jour le message de statut après un guess""" + black, white = feedback + remaining = self.game.get_remaining_attempts() + if won: + msg = f"Bravo! Trouvé en {len(self.game.attempts)} tentatives!" + else: + msg = f"Feedback: {black} noir(s), {white} blanc(s) - {remaining} essais restants" + self.status_label.config(text=msg) + + def _clear_history(self): + """Efface l'historique visuel""" + for widget in self.history_frame.winfo_children(): + widget.destroy() + + def _add_history_row(self, guess: List[int], feedback: Tuple[int, int], won: bool): + """Ajoute une ligne dans l'historique""" + row = len(self.game.attempts) - 1 + frame = ttk.Frame(self.history_frame) + frame.grid(row=row, column=0, pady=5, sticky="ew") + + # Numéro de tentative + ttk.Label(frame, text=f"#{row+1}", width=5).grid(row=0, column=0, padx=5) + + # Cercles de couleurs du guess + guess_frame = ttk.Frame(frame) + guess_frame.grid(row=0, column=1, padx=10) + + for i, color_idx in enumerate(guess): + canvas = tk.Canvas( + guess_frame, + width=CIRCLE_DIAMETER, + height=CIRCLE_DIAMETER, + bg=BG_COLOR, + highlightthickness=0, + ) + canvas.grid(row=0, column=i, padx=2) + canvas.create_oval( + 2, + 2, + CIRCLE_DIAMETER - 2, + CIRCLE_DIAMETER - 2, + fill=COLORS[color_idx][1], + outline="black", + width=1, + ) + + # Feedback (petits cercles) + feedback_frame = ttk.Frame(frame) + feedback_frame.grid(row=0, column=2, padx=10) + + black, white = feedback + # D'abord les noirs (ordre important) + for i in range(black): + self._draw_feedback_circle(feedback_frame, i, "black") + + # Puis les blancs + for i in range(white): + self._draw_feedback_circle(feedback_frame, black + i, "white") + + # Indicateur gagné + if won: + ttk.Label( + frame, text="✓", foreground="green", font=("Helvetica", 16, "bold") + ).grid(row=0, column=3, padx=10) + + def _draw_feedback_circle(self, parent: ttk.Frame, index: int, color: str): + """Dessine un petit cercle de feedback (noir ou blanc)""" + canvas = tk.Canvas( + parent, + width=FEEDBACK_DIAMETER, + height=FEEDBACK_DIAMETER, + bg=BG_COLOR, + highlightthickness=0, + ) + col = index % 3 + row = index // 3 + canvas.grid(row=row, column=col, padx=1, pady=1) + canvas.create_oval( + 1, + 1, + FEEDBACK_DIAMETER - 1, + FEEDBACK_DIAMETER - 1, + fill=color, + outline="gray", + width=1, + ) + + def run(self): + """Lance la boucle principale de l'application""" + self.root.mainloop() diff --git a/src/main.py b/src/main.py new file mode 100644 index 0000000..3abe7b6 --- /dev/null +++ b/src/main.py @@ -0,0 +1,10 @@ +""" +Point d'entrée principal du jeu MasterMind +""" + +from gui import MasterMindGUI + +if __name__ == "__main__": + print("Lancement du MasterMind...") + app = MasterMindGUI() + app.run() diff --git a/src/requirements.txt b/src/requirements.txt new file mode 100644 index 0000000..34538a8 --- /dev/null +++ b/src/requirements.txt @@ -0,0 +1,12 @@ +# MasterMind - Dépendances Python +# ================================== +# Ce projet utilise uniquement la bibliothèque standard Python. +# Aucune installation externe n'est requise pour exécuter le jeu. + +# Python 3.7+ recommandé +# Tkinter est inclus dans la distribution standard Python + +# Note: Le fichier dot_env.py utilise python-dotenv mais n'est pas +# nécessaire au fonctionnement du jeu. Si vous souhaitez utiliser +# la gestion des variables d'environnement, décommentez ci-dessous: +# python-dotenv>=1.0.0