소개
몬티 홀 문제 (Monty Hall problem) 는 게임 쇼 시나리오를 기반으로 한 유명한 확률 퍼즐입니다. 게임에서 참가자는 세 개의 문을 마주합니다. 문 중 하나 뒤에는 상품 (예: 자동차) 이 있고, 나머지 두 개의 문 뒤에는 염소가 숨겨져 있습니다. 참가자는 문 중 하나를 선택합니다. 상품이 어디에 있는지 알고 있는 진행자는 나머지 두 문 중 하나를 열어 염소를 보여줍니다. 그런 다음 참가자는 원래 선택을 유지하거나 다른 열리지 않은 문으로 바꿀 수 있는 옵션이 주어집니다. 질문은 다음과 같습니다. 어떤 전략이 가장 좋을까요? 바꾸는 것이 좋을까요, 아니면 유지하는 것이 좋을까요? 이 프로젝트는 Python 의 Tkinter 라이브러리를 사용하여 몬티 홀 문제를 시뮬레이션하는 GUI 애플리케이션을 구축하는 과정을 안내합니다.
👀 미리보기

🎯 과제
이 프로젝트에서는 다음을 배우게 됩니다.
- Tkinter 를 사용하여 그래픽 사용자 인터페이스 (GUI) 를 설계하고 개발하는 방법.
- 몬티 홀 문제의 확률적 결과를 이해하기 위해 시뮬레이션하는 방법.
- 사용자 선택을 처리하고 결과를 표시하기 위해 Python 에서 게임 로직을 구현하는 방법.
- Python 의 random 라이브러리를 사용하여 문 중 하나 뒤에 상품을 무작위로 할당하는 방법.
- 애플리케이션을 다시 시작하지 않고 여러 라운드의 플레이를 허용하기 위해 게임 상태를 재설정하는 방법.
🏆 성과
이 프로젝트를 완료하면 다음을 수행할 수 있습니다.
- GUI 디자인 원칙을 적용하고 Tkinter 를 사용하여 Python 에서 구현합니다.
- 게임 시뮬레이션에서 확률 및 통계의 실제 적용을 이해합니다.
- 이벤트 기반 프로그래밍을 구현하고 GUI 애플리케이션에서 사용자 상호 작용을 처리합니다.
- 람다 함수 및 리스트 컴프리헨션과 같은 고급 Python 프로그래밍 기술을 활용합니다.
- 게임 디자인에서 사용자 경험 (UX) 의 중요성을 인식하고 메시지 상자를 사용하여 피드백 메커니즘을 제공합니다.
프로젝트 파일 생성
먼저, ~/project 디렉토리 안에 monty_hall_gui.py라는 Python 파일을 생성합니다. 그런 다음 텍스트 편집기 또는 통합 개발 환경 (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:
"""
Monty Hall 시뮬레이션을 초기화합니다.
:param root: 메인 tkinter 윈도우
"""
## 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)
위 지침은 다양한 게임 변수를 초기화하고 시뮬레이션을 위한 주요 UI 요소를 설정하는 과정을 안내합니다.
문 이미지 및 버튼 설정
문 이미지를 로드하고 사용자 상호 작용을 허용하도록 문 버튼을 구성합니다.
## 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

요약
축하합니다! Tkinter 를 사용하여 몬티 홀 문제의 GUI 기반 시뮬레이션을 방금 만들었습니다. 시뮬레이션을 실행하려면 monty_hall_gui.py 스크립트를 실행하고 그래픽 인터페이스와 상호 작용하십시오. 기억하세요, 이 게임은 확률의 직관에 반하는 본질을 보여주며, 최선의 전략은 항상 문을 바꾸는 것입니다!



