Simulação do Problema de Monty Hall Usando Tkinter

PythonBeginner
Pratique Agora

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

Monty Hall

🎯 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
✨ Verificar Solução e Praticar

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.

✨ Verificar Solução e Praticar

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.

✨ Verificar Solução e Praticar

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.

✨ Verificar Solução e Praticar

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.

✨ Verificar Solução e Praticar

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.

✨ Verificar Solução e Praticar

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.

✨ Verificar Solução e Praticar

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.

✨ Verificar Solução e Praticar

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.

✨ Verificar Solução e Praticar

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.

✨ Verificar Solução e Praticar

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
Monty Hall
✨ Verificar Solução e Praticar

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!