はじめに
モンティ・ホール問題は、ゲームショーのシナリオに基づく有名な確率パズルです。このゲームでは、参加者に 3 つのドアが提示されます。そのうち 1 つのドアの後ろには賞品(例えば車)があり、残りの 2 つのドアの後ろにはヤギが隠されています。参加者はドアの 1 つを選びます。賞品の位置を知っている司会者は、残りの 2 つのドアのうち 1 つを開けてヤギを見せます。その後、参加者は最初の選択を維持するか、開かれていないもう一方のドアに切り替えるかを選ぶことができます。問題は、「切り替えるか、そのままにするか、どちらが最善の戦略なのか?」です。このプロジェクトでは、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:
"""
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)
上記の手順では、様々なゲーム変数の初期化と、シミュレーションの主要な 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)
上記のコードでは、異なるドアの状態を表す画像を読み込んでいます。その後、3 つのドアを表すボタンを作成して設定し、ユーザーが選択できるようにしています。
ドア選択ロジックを実装する
まず、ドアを選択するためのロジックを実装します。
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 メソッドを定義しています。このメソッドは、プレイヤーの選択を記憶し、賞品が入っていないドアの 1 つを開きます。
ドア切り替えロジックを処理する
次に、ドアの選択を切り替えるロジックについて説明します。
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 関数が、司会者が選択されていないドアの 1 つの後ろにヤギを見せた後、プレイヤーがドアを切り替えることを選んだときに何が起こるかを管理します。
ドア保持ロジックを実装する
次に、最初に選んだドアを維持するロジックについて見ていきましょう。
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 スクリプトを実行し、グラフィカルインターフェイスと対話します。覚えておいてください、このゲームは確率の直感に反する性質を示しており、最善の戦略は常にドアを切り替えることです!



