Введение
Задача Монти Холла - это известная вероятностная головоломка, основанная на сценарии игрового шоу. В игре участнику показывают три двери. За одной из дверей находится приз (например, автомобиль), а за двумя другими - козы. Участник выбирает одну из дверей. Ведущий, который знает, где находится приз, затем открывает одну из двух других дверей, за которой скрыта коза. Затем участнику дается возможность либо оставить свой первоначальный выбор, либо переключиться на другую неоткрытую дверь. Вопрос: какова наилучшая стратегия - переключиться или оставить свой выбор? Этот проект научит вас создавать графическое приложение (GUI), которое моделирует задачу Монти Холла с использованием библиотеки Tkinter в Python.
👀 Предварительный просмотр

🎯 Задачи
В рамках этого проекта вы научитесь:
- Проектировать и разрабатывать графический интерфейс (GUI) с использованием Tkinter.
- Симулировать задачу Монти Холла, чтобы понять ее вероятностные исходы.
- Реализовывать игровую логику на Python для обработки выборов пользователя и отображения результатов.
- Использовать библиотеку random в Python для случайного размещения приза за одной из дверей.
- Сбрасывать игровые состояния, чтобы можно было играть в несколько раундов без перезапуска приложения.
🏆 Достижения
После завершения этого проекта вы сможете:
- Применять принципы дизайна GUI и реализовывать их на Python с использованием Tkinter.
- Понять практическое применение теории вероятностей и статистики в игровых симуляциях.
- Реализовывать событийно-ориентированное программирование и обрабатывать взаимодействия пользователя в графическом приложении.
- Использовать продвинутые техники программирования на Python, такие как лямбда-функции и списковые включения.
- Понимать важность пользовательского опыта (UX) в дизайне игр и предоставлять механизмы обратной связи с использованием диалоговых окон.
Создание файлов проекта
Сначала создайте файл Python с именем monty_hall_gui.py в директории ~/project. Затем откройте его в текстовом редакторе или интегрированной среде разработки (IDE).
cd ~/project
touch monty_hall_gui.py
Импорт необходимых библиотек
Для начала импортируем необходимые модули.
import tkinter as tk
from tkinter import messagebox
import random
from typing import List, Optional
В приведенном выше коде мы импортируем необходимые библиотеки Python. tkinter используется для создания графического интерфейса (GUI), messagebox - для всплывающих уведомлений, а random - для реализации случайной игровой логики.
Инициализация класса MontyHallSimulation
Теперь мы создадим основной класс для управления нашей симуляцией.
class MontyHallSimulation:
def __init__(self, root: tk.Tk) -> None:
"""
Initialize the Monty Hall Simulation.
:param root: The main tkinter window
"""
## Window Configuration
self.root = root
self.root.title("Monty Hall Problem Simulation")
self.root.geometry("1200x800")
В приведенных выше инструкциях мы начинаем определять наш основной класс для симуляции.
Настройка переменных и компонентов пользовательского интерфейса (UI)
Внутри класса инициализируем необходимые переменные и элементы пользовательского интерфейса (UI).
## Variables to keep track of the user's selected door, the door that's shown, and the winning door
self.selected_door: Optional[int] = None
self.show_door: Optional[int] = None
self.winning_door: int = random.randint(1, 3)
## Variables to keep track of win counts based on decision to change or stay
self.wins_change: int = 0
self.wins_stay: int = 0
## Instructions displayed at the top
self.label = tk.Label(root, text="Select a door!", font=("Arial", 16))
self.label.pack(pady=5)
## Creating frame for door buttons
self.doors_frame = tk.Frame(root)
self.doors_frame.pack(pady=5)
Вышеприведенные инструкции помогут вам инициализировать различные игровые переменные и настроить основные элементы пользовательского интерфейса для нашей симуляции.
Настройка изображений дверей и кнопок
Загрузите изображения дверей и настройте кнопки дверей для обеспечения взаимодействия с пользователем.
## Load door images
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")
]
## Create door buttons with images and attach select door function
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)
## Buttons for choosing whether to switch doors
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()
## Displaying results
self.results_label = tk.Label(root, text="", font=("Arial", 14))
self.results_label.pack(pady=5)
В приведенном выше коде мы загружаем изображения дверей, которые представляют различные состояния дверей. Затем мы создаем и настраиваем три кнопки, представляющие двери, которые позволяют пользователю сделать выбор.
Реализация логики выбора двери
Сначала мы рассмотрим логику выбора двери.
def select_door(self, door: int) -> None:
"""
Handle the event when a door is selected.
:param door: The number of the door selected
"""
## Ensure no re-selection of doors
if self.selected_door:
return
self.selected_door = door
## Determine which doors were not selected
non_selected_doors = [d for d in [1, 2, 3] if d!= self.selected_door]
## Decide which door to reveal
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])
## Prompt the user to decide whether to switch
self.label.config(text="Would you like to switch?")
## Display the yes and no buttons
self.yes_button.pack(side=tk.LEFT, padx=10, pady=20)
self.no_button.pack(side=tk.RIGHT, padx=10, pady=20)
В приведенном выше коде мы определяем метод select_door, который будет использоваться, когда игрок выбирает дверь. Метод запоминает выбор игрока и открывает одну из дверей, не содержащих приз.
Обработка логики переключения дверей
Далее мы рассмотрим логику смены выбора двери.
def switch_door(self) -> None:
"""Handle the event when the user decides to switch doors."""
## Find the door that was not selected and not revealed
remaining_doors = [d for d in [1, 2, 3] if d!= self.selected_door and d!= self.show_door]
new_door = remaining_doors[0]
## Check for a win
if new_door == self.winning_door:
self.wins_change += 1
self.show_win(True)
else:
self.show_win(False)
Здесь функция switch_door управляет тем, что происходит, когда игрок решает сменить дверь после того, как ведущий открывает дверь с козой среди невыбранных дверей.
Реализация логики сохранения выбора двери
Теперь рассмотрим логику сохранения первоначального выбора двери.
def stay_door(self) -> None:
"""Handle the event when the user decides to stay with the initial door selection."""
## Check for a win
if self.selected_door == self.winning_door:
self.wins_stay += 1
self.show_win(True)
else:
self.show_win(False)
В этом фрагменте код функции stay_door контролирует результат, когда игрок решает оставить свой первоначальный выбор двери.
Показать результат (победа или поражение)
Далее покажем, выиграл ли игрок или нет.
def show_win(self, did_win: bool) -> None:
"""
Display the results after revealing all doors.
:param did_win: Boolean indicating whether the user won or not
"""
## Update door images based on the results
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])
## Show win/lose message
if did_win:
messagebox.showinfo("Congratulations!", "You won!")
else:
messagebox.showinfo("Sorry!", "Better luck next time!")
## Update the win statistics
self.results_label.config(text=f"Wins (Switch): {self.wins_change} Wins (Stay): {self.wins_stay}")
## Prepare for the next round
self.reset_game()
Метод show_win показывает результаты. Если в конечном выборе двери игрока скрыт приз, он объявляется победителем; в противном случае ему желают больше удачи в следующий раз.
Сброс игровой механики
Наконец, убедитесь, что вы можете перезапустить игру для новой партии.
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()
Эта функция reset_game сбрасывает состояние игры, позволяя игрокам начать новую партию.
Запуск приложения
Наконец, инициализируем и запустим наше приложение.
if __name__ == "__main__":
root = tk.Tk()
sim = MontyHallSimulation(root)
root.mainloop()
Теперь, когда мы завершили все шаги, мы можем запустить код с помощью следующей команды:
cd ~/project
python monty_hall_gui.py

Резюме
Поздравляем! Вы только что создали графическую симуляцию (GUI) задачи Монти Холла с использованием Tkinter. Чтобы запустить симуляцию, выполните скрипт monty_hall_gui.py и взаимодействуйте с графическим интерфейсом. Помните, что эта игра демонстрирует парадоксальную природу вероятности, и лучшая стратегия - всегда менять дверь!



