Simulation du problème de Monty Hall avec Tkinter

PythonBeginner
Pratiquer maintenant

Introduction

Le problème de Monty Hall est un célèbre puzzle de probabilité basé sur un scénario d'émission de jeu. Dans le jeu, un candidat est présenté avec trois portes. Derrière l'une des portes se trouve un prix (comme une voiture), tandis que les deux autres portes cachent des chèvres. Le candidat sélectionne l'une des portes. L'animateur, qui sait où se trouve le prix, ouvre ensuite l'une des deux autres portes pour révéler une chèvre. Le candidat a alors le choix de conserver son premier choix ou de changer pour l'autre porte non ouverte. La question est : Quelle est la meilleure stratégie, changer de porte ou la conserver? Ce projet vous guidera dans la construction d'une application GUI pour simuler le problème de Monty Hall en utilisant la bibliothèque Tkinter en Python.

👀 Aperçu

Monty Hall

🎯 Tâches

Dans ce projet, vous apprendrez :

  • Comment concevoir et développer une interface graphique utilisateur (GUI) en utilisant Tkinter.
  • Comment simuler le problème de Monty Hall pour comprendre ses résultats probabilistes.
  • Comment implémenter la logique du jeu en Python pour gérer les choix de l'utilisateur et révéler les résultats.
  • Comment utiliser la bibliothèque random de Python pour attribuer aléatoirement le prix derrière l'une des portes.
  • Comment réinitialiser les états du jeu pour permettre plusieurs rounds de jeu sans redémarrer l'application.

🏆 Réalisations

Après avoir terminé ce projet, vous serez en mesure de :

  • Appliquer les principes de conception GUI et les implémenter en Python avec Tkinter.
  • Comprendre l'application pratique des probabilités et de la statistique dans les simulations de jeu.
  • Implémenter la programmation événementielle et gérer les interactions utilisateur dans une application GUI.
  • Utiliser des techniques de programmation Python avancées telles que les fonctions lambda et les compréhensions de liste.
  • Reconnaître l'importance de l'expérience utilisateur (UX) dans la conception de jeu et fournir des mécanismes de feedback à l'aide de boîtes de message.

Créer les fichiers du projet

Tout d'abord, créez un fichier Python nommé monty_hall_gui.py dans le répertoire ~/project. Ensuite, ouvrez-le dans un éditeur de texte ou un environnement de développement intégré (IDE).

cd ~/project
touch monty_hall_gui.py
✨ Vérifier la solution et pratiquer

Importer les bibliothèques nécessaires

Pour commencer, nous allons importer les modules requis.

import tkinter as tk
from tkinter import messagebox
import random
from typing import List, Optional

Dans le code ci-dessus, nous importons les bibliothèques Python nécessaires. tkinter est utilisé pour la création de l'interface graphique utilisateur (GUI), messagebox pour les alertes pop-up et random pour la logique aléatoire du jeu.

✨ Vérifier la solution et pratiquer

Initialiser la classe MontyHallSimulation

Maintenant, nous allons créer la classe principale pour gérer notre simulation.

class MontyHallSimulation:
    def __init__(self, root: tk.Tk) -> None:
        """
        Initialise la simulation du problème de Monty Hall.

        :param root: La fenêtre principale de tkinter
        """
        ## Configuration de la fenêtre
        self.root = root
        self.root.title("Monty Hall Problem Simulation")
        self.root.geometry("1200x800")

Dans les instructions ci-dessus, nous commençons à définir notre classe principale pour la simulation.

✨ Vérifier la solution et pratiquer

Configurer les variables et les composants d'interface utilisateur (UI)

À l'intérieur de la classe, initialisez les variables nécessaires et les éléments de l'interface utilisateur (UI).

        ## Variables pour suivre la porte sélectionnée par l'utilisateur, la porte révélée et la porte gagnante
        self.selected_door: Optional[int] = None
        self.show_door: Optional[int] = None
        self.winning_door: int = random.randint(1, 3)

        ## Variables pour suivre le nombre de victoires en fonction de la décision de changer ou de rester
        self.wins_change: int = 0
        self.wins_stay: int = 0

        ## Instructions affichées en haut
        self.label = tk.Label(root, text="Select a door!", font=("Arial", 16))
        self.label.pack(pady=5)

        ## Création d'un cadre pour les boutons des portes
        self.doors_frame = tk.Frame(root)
        self.doors_frame.pack(pady=5)

Les instructions ci-dessus vous guident dans l'initialisation de diverses variables de jeu et la configuration des éléments principaux de l'interface utilisateur pour notre simulation.

✨ Vérifier la solution et pratiquer

Configurer les images des portes et les boutons

Chargez les images des portes et configurez les boutons des portes pour permettre l'interaction de l'utilisateur.

        ## Charger les images des portes
        self.door_imgs: List[tk.PhotoImage] = [
            tk.PhotoImage(file="door_closed.png"),
            tk.PhotoImage(file="door_opened_empty.png"),
            tk.PhotoImage(file="door_opened_prize.png")
        ]

        ## Créer des boutons de porte avec des images et attacher la fonction de sélection de porte
        self.door_buttons: List[tk.Button] = [
            tk.Button(self.doors_frame, image=self.door_imgs[0], command=lambda: self.select_door(1)),
            tk.Button(self.doors_frame, image=self.door_imgs[0], command=lambda: self.select_door(2)),
            tk.Button(self.doors_frame, image=self.door_imgs[0], command=lambda: self.select_door(3))
        ]

        for button in self.door_buttons:
            button.pack(side=tk.LEFT, padx=5)

        ## Boutons pour choisir s'il faut changer de porte
        self.yes_button = tk.Button(self.root, text="Yes", command=self.switch_door)
        self.yes_button.pack_forget()

        self.no_button = tk.Button(self.root, text="No", command=self.stay_door)
        self.no_button.pack_forget()

        ## Affichage des résultats
        self.results_label = tk.Label(root, text="", font=("Arial", 14))
        self.results_label.pack(pady=5)

Dans le code ci-dessus, nous chargeons les images des portes qui représentent différents états des portes. Ensuite, nous créons et configurons trois boutons représentant les portes, qui permettent à l'utilisateur de faire son choix.

✨ Vérifier la solution et pratiquer

Implémenter la logique de sélection des portes

Tout d'abord, nous allons aborder la logique de sélection d'une porte.

    def select_door(self, door: int) -> None:
        """
        Gère l'événement lorsqu'une porte est sélectionnée.

        :param door: Le numéro de la porte sélectionnée
        """
        ## S'assurer qu'il n'y a pas de re-sélection de porte
        if self.selected_door:
            return

        self.selected_door = door

        ## Déterminer quelles portes n'ont pas été sélectionnées
        non_selected_doors = [d for d in [1, 2, 3] if d!= self.selected_door]

        ## Décider quelle porte révéler
        if self.selected_door == self.winning_door:
            show_door = random.choice(non_selected_doors)
        else:
            non_selected_doors.remove(self.winning_door)
            show_door = non_selected_doors[0]

        self.show_door = show_door
        self.door_buttons[show_door - 1].config(image=self.door_imgs[1])

        ## Demander à l'utilisateur de décider s'il veut changer de porte
        self.label.config(text="Would you like to switch?")

        ## Afficher les boutons Oui et Non
        self.yes_button.pack(side=tk.LEFT, padx=10, pady=20)
        self.no_button.pack(side=tk.RIGHT, padx=10, pady=20)

Dans le code ci-dessus, nous définissons la méthode select_door qui sera utilisée lorsque le joueur choisit une porte. Cette méthode mémorise le choix du joueur et révèle l'une des portes qui ne contient pas le prix.

✨ Vérifier la solution et pratiquer

Gérer la logique de changement de porte

Ensuite, nous allons nous plonger dans la logique du changement de choix de porte.

    def switch_door(self) -> None:
        """Gère l'événement lorsque l'utilisateur décide de changer de porte."""
        ## Trouver la porte qui n'a pas été sélectionnée et n'a pas été révélée
        remaining_doors = [d for d in [1, 2, 3] if d!= self.selected_door and d!= self.show_door]
        new_door = remaining_doors[0]

        ## Vérifier s'il y a une victoire
        if new_door == self.winning_door:
            self.wins_change += 1
            self.show_win(True)
        else:
            self.show_win(False)

Ici, la fonction switch_door gère ce qui se passe lorsque le joueur choisit de changer de porte après que le présentateur ait révélé une chèvre derrière l'une des portes non sélectionnées.

✨ Vérifier la solution et pratiquer

Gérer la logique de maintien de porte

Maintenant, examinons la logique pour maintenir le choix initial de porte.

    def stay_door(self) -> None:
        """Gère l'événement lorsque l'utilisateur décide de conserver son choix initial de porte."""
        ## Vérifier s'il y a une victoire
        if self.selected_door == self.winning_door:
            self.wins_stay += 1
            self.show_win(True)
        else:
            self.show_win(False)

Dans ce extrait de code, la fonction stay_door gère le résultat lorsque le joueur décide de conserver son choix initial de porte.

✨ Vérifier la solution et pratiquer

Illustrer la victoire ou la défaite

Ensuite, affichez si le joueur a remporté la partie ou non.

    def show_win(self, did_win: bool) -> None:
        """
        Affiche les résultats après avoir révélé toutes les portes.

        :param did_win: Booléen indiquant si l'utilisateur a gagné ou non
        """
        ## Mettre à jour les images des portes en fonction des résultats
        for i, button in enumerate(self.door_buttons):
            if i + 1 == self.winning_door:
                button.config(image=self.door_imgs[2])
            else:
                button.config(image=self.door_imgs[1])

        ## Afficher le message de victoire/défaite
        if did_win:
            messagebox.showinfo("Congratulations!", "You won!")
        else:
            messagebox.showinfo("Sorry!", "Better luck next time!")

        ## Mettre à jour les statistiques de victoire
        self.results_label.config(text=f"Wins (Switch): {self.wins_change}   Wins (Stay): {self.wins_stay}")

        ## Préparer la prochaine manche
        self.reset_game()

Cette méthode show_win révèle les résultats. Si la porte finale choisie par le joueur cache le prix, il est déclaré gagnant ; sinon, on lui souhaite bonne chance pour la prochaine fois.

✨ Vérifier la solution et pratiquer

Réinitialiser la mécanique du jeu

Enfin, assurez-vous de pouvoir réinitialiser le jeu pour une nouvelle manche.

    def reset_game(self) -> None:
        """Réinitialise le jeu pour une nouvelle manche."""
        self.selected_door = None
        self.winning_door = random.randint(1, 3)
        for button in self.door_buttons:
            button.config(image=self.door_imgs[0])
        self.label.config(text="Select a door!")
        self.yes_button.pack_forget()
        self.no_button.pack_forget()

Cette fonction, reset_game, redémarre l'état du jeu, permettant aux joueurs de tenter une nouvelle manche.

✨ Vérifier la solution et pratiquer

Exécuter l'application

Enfin, initialisons et exécutons notre application.

if __name__ == "__main__":
    root = tk.Tk()
    sim = MontyHallSimulation(root)
    root.mainloop()

Maintenant que nous avons terminé toutes les étapes, nous pouvons exécuter le code en utilisant la commande suivante :

cd ~/project
python monty_hall_gui.py
Monty Hall
✨ Vérifier la solution et pratiquer

Résumé

Félicitations! Vous venez de créer une simulation basée sur une interface graphique (GUI) du problème de Monty Hall en utilisant Tkinter. Pour exécuter la simulation, exécutez le script monty_hall_gui.py et interagissez avec l'interface graphique. N'oubliez pas que le jeu met en évidence la nature contre-intuitive de la probabilité, et que la meilleure stratégie est toujours de changer de porte!