Simulación del Problema de Monty Hall con Tkinter

PythonBeginner
Practicar Ahora

Introducción

El problema de Monty Hall es un famoso acertijo de probabilidad basado en un escenario de un programa de televisión. En el juego, un concursante se presenta con tres puertas. Detrás de una de las puertas hay un premio (como un automóvil), mientras que detrás de las otras dos puertas hay cabras. El concursante selecciona una de las puertas. El presentador, que sabe dónde está el premio, luego abre una de las otras dos puertas para mostrar una cabra. Luego, se le da al concursante la opción de quedarse con su elección original o cambiar a la otra puerta sin abrir. La pregunta es: ¿Cuál es la mejor estrategia, cambiar o quedarse? Este proyecto lo guiará a través de la construcción de una aplicación GUI para simular el problema de Monty Hall utilizando la biblioteca Tkinter en Python.

👀 Vista previa

Monty Hall

🎯 Tareas

En este proyecto, aprenderá:

  • Cómo diseñar y desarrollar una interfaz gráfica de usuario (GUI) utilizando Tkinter.
  • Cómo simular el problema de Monty Hall para entender sus resultados probabilísticos.
  • Cómo implementar la lógica del juego en Python para manejar las elecciones del usuario y mostrar los resultados.
  • Cómo utilizar la biblioteca random de Python para asignar aleatoriamente el premio detrás de una de las puertas.
  • Cómo restablecer los estados del juego para permitir múltiples rondas de juego sin reiniciar la aplicación.

🏆 Logros

Después de completar este proyecto, podrá:

  • Aplicar principios de diseño de GUI e implementarlos en Python con Tkinter.
  • Comprender la aplicación práctica de la probabilidad y la estadística en simulaciones de juegos.
  • Implementar programación dirigida por eventos y manejar interacciones del usuario en una aplicación GUI.
  • Utilizar técnicas avanzadas de programación en Python, como funciones lambda y comprensiones de listas.
  • Reconocer la importancia de la experiencia del usuario (UX) en el diseño de juegos y proporcionar mecanismos de retroalimentación utilizando cuadros de mensaje.

Crear archivos de proyecto

Primero, crea un archivo de Python llamado monty_hall_gui.py en ~/project. Luego, ábrelo en un editor de texto o en un entorno de desarrollo integrado (IDE, por sus siglas en inglés).

cd ~/project
touch monty_hall_gui.py
✨ Revisar Solución y Practicar

Importar bibliotecas necesarias

Para comenzar, importaremos los módulos necesarios.

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

En el código anterior, estamos importando las bibliotecas de Python necesarias. tkinter es para la creación de GUI, messagebox es para las alertas emergentes y random es para la lógica aleatoria del juego.

✨ Revisar Solución y Practicar

Inicializar la clase MontyHallSimulation

Ahora, crearemos la clase principal para gestionar nuestra simulación.

class MontyHallSimulation:
    def __init__(self, root: tk.Tk) -> None:
        """
        Inicializa la simulación del problema de Monty Hall.

        :param root: La ventana principal de tkinter
        """
        ## Configuración de la ventana
        self.root = root
        self.root.title("Monty Hall Problem Simulation")
        self.root.geometry("1200x800")

En las instrucciones anteriores, comenzamos a definir nuestra clase principal para la simulación.

✨ Revisar Solución y Practicar

Configurar variables y componentes de la interfaz de usuario (UI)

Dentro de la clase, inicializa las variables necesarias y los elementos de la interfaz de usuario (UI).

        ## Variables para realizar un seguimiento de la puerta seleccionada por el usuario, la puerta que se muestra y la puerta ganadora
        self.selected_door: Optional[int] = None
        self.show_door: Optional[int] = None
        self.winning_door: int = random.randint(1, 3)

        ## Variables para realizar un seguimiento de la cantidad de victorias basadas en la decisión de cambiar o quedarse
        self.wins_change: int = 0
        self.wins_stay: int = 0

        ## Instrucciones mostradas en la parte superior
        self.label = tk.Label(root, text="Select a door!", font=("Arial", 16))
        self.label.pack(pady=5)

        ## Creación del marco para los botones de las puertas
        self.doors_frame = tk.Frame(root)
        self.doors_frame.pack(pady=5)

Las instrucciones anteriores te guían a través de la inicialización de varias variables del juego y la configuración de los elementos principales de la interfaz de usuario para nuestra simulación.

✨ Revisar Solución y Practicar

Configurar imágenes de puertas y botones

Carga las imágenes de las puertas y configura los botones de las puertas para permitir la interacción del usuario.

        ## Cargar imágenes de las puertas
        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")
        ]

        ## Crear botones de las puertas con imágenes y asociar la función de selección de puerta
        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)

        ## Botones para elegir si cambiar de puerta
        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()

        ## Mostrar resultados
        self.results_label = tk.Label(root, text="", font=("Arial", 14))
        self.results_label.pack(pady=5)

En el código anterior, estamos cargando las imágenes de las puertas que representan diferentes estados de las puertas. Luego, creamos y configuramos tres botones que representan las puertas, lo que permite al usuario hacer su elección.

✨ Revisar Solución y Practicar

Implementar la lógica de selección de puertas

Primero, abordaremos la lógica para seleccionar una puerta.

    def select_door(self, door: int) -> None:
        """
        Maneja el evento cuando se selecciona una puerta.

        :param door: El número de la puerta seleccionada
        """
        ## Asegurarse de que no se vuelva a seleccionar una puerta
        if self.selected_door:
            return

        self.selected_door = door

        ## Determinar qué puertas no fueron seleccionadas
        non_selected_doors = [d for d in [1, 2, 3] if d!= self.selected_door]

        ## Decidir qué puerta revelar
        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])

        ## Pedir al usuario que decida si cambiar de puerta
        self.label.config(text="Would you like to switch?")

        ## Mostrar los botones de sí y no
        self.yes_button.pack(side=tk.LEFT, padx=10, pady=20)
        self.no_button.pack(side=tk.RIGHT, padx=10, pady=20)

En el código anterior, definimos el método select_door que se utilizará cuando un jugador elija una puerta. El método recordará la selección del jugador y revelará una de las puertas que no contiene el premio.

✨ Revisar Solución y Practicar

Manejar la lógica de cambio de puerta

A continuación, profundizamos en la lógica del intercambio de la elección de puerta.

    def switch_door(self) -> None:
        """Handle the event when the user decides to switch doors."""
        ## Encontrar la puerta que no fue seleccionada y no fue revelada
        remaining_doors = [d for d in [1, 2, 3] if d!= self.selected_door and d!= self.show_door]
        new_door = remaining_doors[0]

        ## Verificar si se ganó
        if new_door == self.winning_door:
            self.wins_change += 1
            self.show_win(True)
        else:
            self.show_win(False)

Aquí, la función switch_door gestiona lo que sucede cuando un jugador decide cambiar de puerta después de que el presentador revele una cabra detrás de una de las puertas no seleccionadas.

✨ Revisar Solución y Practicar

Abordar la lógica de retención de puerta

Ahora, abordemos la lógica para mantener la elección inicial de puerta.

    def stay_door(self) -> None:
        """Handle the event when the user decides to stay with the initial door selection."""
        ## Verificar si se ganó
        if self.selected_door == self.winning_door:
            self.wins_stay += 1
            self.show_win(True)
        else:
            self.show_win(False)

En este fragmento de código, la función stay_door supervisa el resultado cuando el jugador decide mantenerse con su elección inicial de puerta.

✨ Revisar Solución y Practicar

Ilustrar la victoria o la derrota

Posteriormente, muestra si el jugador ganó o no.

    def show_win(self, did_win: bool) -> None:
        """
        Muestra los resultados después de revelar todas las puertas.

        :param did_win: Booleano que indica si el usuario ganó o no
        """
        ## Actualizar las imágenes de las puertas según los resultados
        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])

        ## Mostrar mensaje de victoria/derrota
        if did_win:
            messagebox.showinfo("Congratulations!", "You won!")
        else:
            messagebox.showinfo("Sorry!", "Better luck next time!")

        ## Actualizar las estadísticas de victorias
        self.results_label.config(text=f"Wins (Switch): {self.wins_change}   Wins (Stay): {self.wins_stay}")

        ## Preparar para la siguiente ronda
        self.reset_game()

Este método show_win revela los resultados. Si la elección final de puerta del jugador esconde el premio, se anuncia como ganador; de lo contrario, se le desea mejor suerte para la próxima vez.

✨ Revisar Solución y Practicar

Restablecer la mecánica del juego

Finalmente, asegúrate de poder reinicializar el juego para otra ronda.

    def reset_game(self) -> None:
        """Reset the game for a new round."""
        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()

Esta función, reset_game, reinicia el estado del juego, permitiendo a los jugadores intentar otra ronda.

✨ Revisar Solución y Practicar

Ejecutar la aplicación

Finalmente, inicialicemos y ejecutemos nuestra aplicación.

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

Ahora que hemos completado todos los pasos, podemos ejecutar el código utilizando el siguiente comando:

cd ~/project
python monty_hall_gui.py
Monty Hall
✨ Revisar Solución y Practicar

Resumen

¡Felicidades! Acabas de crear una simulación basada en interfaz gráfica de usuario (GUI, por sus siglas en inglés) del problema de Monty Hall utilizando Tkinter. Para ejecutar la simulación, ejecuta el script monty_hall_gui.py e interactúa con la interfaz gráfica. Recuerda, el juego muestra la naturaleza contraintuitiva de la probabilidad, y la mejor estrategia es siempre cambiar de puerta.