The Monty Hall game#
Suppose you’re on a game show, and you’re given the choice of three doors: Behind one door is a car; behind the others, goats.
In search of a new car, the player picks a door, say 1. The game host then opens one of the other doors, say 3, to reveal a goat and offers to let the player pick door 2 instead of door 1.
Goal: Implement this game as a web application.
If you want to look at the code at home, here are step-by-step implementations: UiO-IN3110/UiO-IN3110.github.io
import random
from functools import partial
from pydantic import BaseModel, Field
class MontyHallGame(BaseModel):
winning: int = Field(default_factory=partial(random.randint, 1, 3))
first_choice: int = None
opened: int = None
second_choice: int = None
has_won: int = None
def choose(self, choice: int):
"""The first step: Make a choice"""
self.first_choice = choice
def reveal(self):
"""The second step: host reveals a door that definitely has a goat."""
choices = {1, 2, 3}
# don't open the winning door
choices.discard(self.winning)
# don't open the door they've already chosen
choices.discard(self.first_choice)
# open a random remaining door.
# there is either 1 or 2 choices left
self.opened = random.choice(list(choices))
return self.opened
def choose_again(self, switch: bool):
"""Guest can either switch to the other door, or stay with their first choice"""
if switch:
choices = {1, 2, 3}
choices.discard(self.first_choice)
choices.discard(self.opened)
self.second_choice = choices.pop()
else:
self.second_choice = self.first_choice
self.has_won = self.second_choice == self.winning
return self.second_choice
def play(self):
"""Play one full round"""
first_choice = int(input("Pick a door (1, 2, 3): "))
self.choose(first_choice)
self.reveal()
available = {1, 2, 3}
available.discard(first_choice)
available.discard(self.opened)
remaining = available.pop()
ans = input(
f"I've opened door number {self.opened}. Would you like to switch (y/N)?"
)
second_choice = self.choose_again(switch=ans.lower().startswith("y"))
print(f"You picked door number {self.second_choice}")
if self.second_choice == self.winning:
print("You win!")
else:
print(f"Sorry, it was door number {self.winning} :(")
game = MontyHallGame()
game.play()
You picked door number 1
You win!
Now we are going to simulate a number of games, recording our win rate for the switch strategy and the stay strategy.
import random
import ipywidgets
from IPython.display import display
from tqdm.notebook import tqdm
def autoplay(samples):
switch_progress = tqdm(desc="switch", total=samples, mininterval=0.01)
stay_progress = tqdm(desc="stay", total=samples, mininterval=0.01)
for i in range(samples):
for switch, progress in zip((True, False), (switch_progress, stay_progress)):
first_choice = random.randint(1, 3)
game = MontyHallGame()
game.choose(first_choice)
game.reveal()
game.choose_again(switch=switch)
if game.has_won:
progress.update(1)
switch_progress.close()
stay_progress.close()
autoplay(100_000)
Tasks:
create a ‘welcome’ page
serve a form for “pick a door”
handle form submission for picking a door
select a door to open
serve a form to make final pick
serve a result
Initial FastAPI boilerplate:
from fastapi import FastAPI, Form, Request
from fastapi.responses import HTMLResponse
from fastapi.templating import Jinja2Templates
app = FastAPI()
templates = Jinja2Templates(directory="templates")
@app.get("/", response_class=HTMLResponse)
def welcome():
...
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, debug=True)