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

🎯 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
randomde 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
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.
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.
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.
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.
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.
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.
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.
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.
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.
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

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.



