Introdução
O problema de Monty Hall é um famoso quebra-cabeça de probabilidade baseado em um cenário de programa de jogos. No jogo, um concorrente é apresentado a três portas. Atrás de uma das portas está um prêmio (como um carro), enquanto as outras duas portas escondem cabras. O concorrente seleciona uma das portas. O apresentador, que sabe onde está o prêmio, então abre uma das outras duas portas para revelar uma cabra. O concorrente recebe então a opção de ficar com sua escolha original ou mudar para a outra porta não aberta. A questão é: Qual é a melhor estratégia, mudar ou ficar? Este projeto irá guiá-lo através da construção de uma aplicação GUI para simular o problema de Monty Hall usando a biblioteca Tkinter em Python.
👀 Pré-visualização

🎯 Tarefas
Neste projeto, você aprenderá:
- Como projetar e desenvolver uma interface gráfica do usuário (GUI) usando Tkinter.
- Como simular o problema de Monty Hall para entender seus resultados probabilísticos.
- Como implementar a lógica do jogo em Python para lidar com as escolhas do usuário e revelar os resultados.
- Como usar a biblioteca random do Python para alocar aleatoriamente o prêmio atrás de uma das portas.
- Como redefinir os estados do jogo para permitir várias rodadas de jogo sem reiniciar a aplicação.
🏆 Conquistas
Após concluir este projeto, você será capaz de:
- Aplicar princípios de design de GUI e implementá-los em Python com Tkinter.
- Entender a aplicação prática da probabilidade e estatística em simulações de jogos.
- Implementar programação orientada a eventos e lidar com interações do usuário em uma aplicação GUI.
- Utilizar técnicas avançadas de programação Python, como funções lambda e list comprehensions (compreensões de lista).
- Reconhecer a importância da experiência do usuário (UX) no design de jogos e fornecer mecanismos de feedback usando caixas de mensagem.
Criar Arquivos do Projeto
Primeiro, crie um arquivo Python chamado monty_hall_gui.py em ~/project. Em seguida, abra-o em um editor de texto ou em um ambiente de desenvolvimento integrado (IDE).
cd ~/project
touch monty_hall_gui.py
Importar as Bibliotecas Necessárias
Para começar, importaremos os módulos necessários.
import tkinter as tk
from tkinter import messagebox
import random
from typing import List, Optional
No código acima, estamos importando as bibliotecas Python necessárias. tkinter é para a criação da GUI, messagebox é para alertas pop-up, e random é para a lógica do jogo randomizada.
Inicializar a Classe MontyHallSimulation
Agora, criaremos a classe principal para gerenciar nossa simulação.
class MontyHallSimulation:
def __init__(self, root: tk.Tk) -> None:
"""
Inicializa a Simulação do Problema de Monty Hall.
:param root: A janela principal do tkinter
"""
## Configuração da Janela
self.root = root
self.root.title("Simulação do Problema de Monty Hall")
self.root.geometry("1200x800")
Nas instruções acima, começamos a definir nossa classe principal para a simulação.
Configurar Variáveis e Componentes da Interface do Usuário (UI)
Dentro da classe, inicialize as variáveis necessárias e os elementos da UI.
## Variáveis para rastrear a porta selecionada pelo usuário, a porta que é mostrada e a porta vencedora
self.selected_door: Optional[int] = None
self.show_door: Optional[int] = None
self.winning_door: int = random.randint(1, 3)
## Variáveis para rastrear as contagens de vitórias com base na decisão de mudar ou ficar
self.wins_change: int = 0
self.wins_stay: int = 0
## Instruções exibidas no topo
self.label = tk.Label(root, text="Selecione uma porta!", font=("Arial", 16))
self.label.pack(pady=5)
## Criando frame para os botões das portas
self.doors_frame = tk.Frame(root)
self.doors_frame.pack(pady=5)
As instruções acima guiam você através da inicialização de várias variáveis do jogo e da configuração dos principais elementos da UI para nossa simulação.
Configurar as Imagens das Portas e Botões
Carregue as imagens das portas e configure os botões das portas para permitir a interação do usuário.
## Carregar imagens das portas
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")
]
## Criar botões das portas com imagens e anexar a função de seleção de porta
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)
## Botões para escolher se deve trocar de porta
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()
## Exibindo resultados
self.results_label = tk.Label(root, text="", font=("Arial", 14))
self.results_label.pack(pady=5)
No código acima, estamos carregando as imagens das portas que representam diferentes estados das portas. Em seguida, criamos e configuramos três botões representando as portas, que permitem ao usuário fazer sua escolha.
Implementar a Lógica de Seleção de Portas
Primeiramente, abordaremos a lógica para selecionar uma porta.
def select_door(self, door: int) -> None:
"""
Lidar com o evento quando uma porta é selecionada.
:param door: O número da porta selecionada
"""
## Assegurar que não haja re-seleção de portas
if self.selected_door:
return
self.selected_door = door
## Determinar quais portas não foram selecionadas
non_selected_doors = [d for d in [1, 2, 3] if d != self.selected_door]
## Decidir qual porta 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])
## Solicitar ao usuário que decida se deseja trocar
self.label.config(text="Você gostaria de trocar?")
## Exibir os botões sim e não
self.yes_button.pack(side=tk.LEFT, padx=10, pady=20)
self.no_button.pack(side=tk.RIGHT, padx=10, pady=20)
No código acima, definimos o método select_door, que será usado quando um jogador escolher uma porta. O método lembrará a seleção do jogador e revelará uma das portas que não contém o prêmio.
Gerenciar a Lógica de Troca de Portas
Em seguida, mergulhamos na lógica de troca das escolhas de portas.
def switch_door(self) -> None:
"""Lidar com o evento quando o usuário decide trocar de porta."""
## Encontrar a porta que não foi selecionada e não foi 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 se houve vitória
if new_door == self.winning_door:
self.wins_change += 1
self.show_win(True)
else:
self.show_win(False)
Aqui, a função switch_door gerencia o que acontece quando um jogador opta por trocar de porta após o apresentador revelar uma cabra atrás de uma das portas não selecionadas.
Abordar a Lógica de Retenção de Portas
Agora, vamos abordar a lógica para reter a escolha inicial da porta.
def stay_door(self) -> None:
"""Lidar com o evento quando o usuário decide ficar com a seleção inicial da porta."""
## Verificar se houve vitória
if self.selected_door == self.winning_door:
self.wins_stay += 1
self.show_win(True)
else:
self.show_win(False)
Neste trecho, a função stay_door supervisiona o resultado quando o jogador decide permanecer com a sua escolha inicial da porta.
Ilustrar Vitória ou Derrota
Posteriormente, ilustre se o jogador garantiu uma vitória ou não.
def show_win(self, did_win: bool) -> None:
"""
Exibir os resultados após revelar todas as portas.
:param did_win: Booleano indicando se o usuário venceu ou não
"""
## Atualizar as imagens das portas com base nos 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 mensagem de vitória/derrota
if did_win:
messagebox.showinfo("Congratulations!", "You won!")
else:
messagebox.showinfo("Sorry!", "Better luck next time!")
## Atualizar as estatísticas de vitória
self.results_label.config(text=f"Wins (Switch): {self.wins_change} Wins (Stay): {self.wins_stay}")
## Preparar para a próxima rodada
self.reset_game()
Este método show_win revela os resultados. Se a seleção final da porta do jogador esconde o prêmio, ele é anunciado como vencedor; caso contrário, deseja-se boa sorte para a próxima vez.
Reiniciar Mecânicas do Jogo
Finalmente, certifique-se de que você pode reiniciar o jogo para outra rodada.
def reset_game(self) -> None:
"""Reiniciar o jogo para uma nova rodada."""
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 função, reset_game, reinicia o estado do jogo, permitindo que os jogadores tentem outra rodada.
Executar a Aplicação
Finalmente, vamos inicializar e executar nossa aplicação.
if __name__ == "__main__":
root = tk.Tk()
sim = MontyHallSimulation(root)
root.mainloop()
Agora que concluímos todas as etapas, podemos executar o código usando o seguinte comando:
cd ~/project
python monty_hall_gui.py

Resumo
Parabéns! Você acabou de criar uma simulação baseada em GUI do problema de Monty Hall usando Tkinter. Para executar a simulação, execute o script monty_hall_gui.py e interaja com a interface gráfica. Lembre-se, o jogo demonstra a natureza contraintuitiva da probabilidade, e a melhor estratégia é sempre trocar de porta!



