The Monty Hall Problem, a side-by-side simulation
I saw a cool version of the Monty Hall game here recently: https://monty.donk.systems.
This is really cool! But I had an itch I wanted to scratch: rather than manually test out the probabilities, I wanted to run two games side-by-side: one where a switch happened and one where a switch didn't happen. Then, running those over and over, we should see the overall win percentages converge to 66.7% for switching and 33.3% for not switching.
So I coded up a simulation here to scratch my itch!
Controls #
To control the simulation, you can start or stop here. You can also adjust the speed.
x3
Score #
Cumulative stats as the games go on.
| Scenario | Plays | Wins | Win Percent |
|---|---|---|---|
| Switch | 0 | 0 | - |
| No switch | 0 | 0 | - |
Switch simulation #
If we switch cards after the first goat reveal.
No switch simulation #
If we don't switch.
Code #
If interested, the code for this simulation can be viewed here:
HTML #
<section class="board" id="switch-board">
<div class="doors">
<div class="door">
<div class="guess"></div>
<div class="card"></div>
</div>
<div class="door">
<div class="guess"></div>
<div class="card"></div>
</div>
<div class="door">
<div class="guess"></div>
<div class="card"></div>
</div>
</div>
<div class="status"></div>
</section>
<section class="board" id="switch-board">
<div class="doors">
<div class="door">
<div class="guess"></div>
<div class="card"></div>
</div>
<div class="door">
<div class="guess"></div>
<div class="card"></div>
</div>
<div class="door">
<div class="guess"></div>
<div class="card"></div>
</div>
</div>
<div class="status"></div>
</section>
JavaScript #
let running = false;
const playBtn = document.querySelector("#play-btn");
const speedControl = document.querySelector("#speed-control");
const speedDisplay = document.querySelector("#speed-display");
let speed = parseInt(speedControl.value);
const randi = (n) => Math.floor(Math.random() * n);
const sleep = (s) =>
new Promise((res) => setTimeout(res, (s * 1000) / Math.max(1, speed)));
let playsSwitch = 0;
let winsSwitch = 0;
let playsNoSwitch = 0;
let winsNoSwitch = 0;
const playsEl = document.querySelector("#plays");
const winsEl = document.querySelector("#wins");
const winPctEl = document.querySelector("#win-pct");
const nsPlaysEl = document.querySelector("#ns-plays");
const nsWinsEl = document.querySelector("#ns-wins");
const nsWinPctEl = document.querySelector("#ns-win-pct");
const pct = (w, p) => (p ? ((100 * w) / p).toFixed(1) + "%" : "-");
const updateScoreboard = () => {
playsEl.textContent = playsSwitch;
winsEl.textContent = winsSwitch;
winPctEl.textContent = pct(winsSwitch, playsSwitch);
nsPlaysEl.textContent = playsNoSwitch;
nsWinsEl.textContent = winsNoSwitch;
nsWinPctEl.textContent = pct(winsNoSwitch, playsNoSwitch);
};
class MontyBoard {
constructor(rootId, isSwitch) {
this.root = document.getElementById(rootId);
this.isSwitch = isSwitch;
this.guessEls = Array.from(this.root.querySelectorAll(".guess"));
this.cardEls = Array.from(this.root.querySelectorAll(".card"));
this.statusEl = this.root.querySelector(".status");
this._looping = false;
}
clear() {
this.guessEls.forEach((el) => (el.textContent = ""));
this.cardEls.forEach((el) => (el.textContent = ""));
}
status(t) {
this.statusEl.textContent = t;
}
async playOnce() {
if (!running) return;
this.status("\u00A0");
this.clear();
const doors = ["G", "G", "G"];
const winIndex = randi(3);
doors[winIndex] = "C";
await sleep(0.5);
this.status("Initial guess");
await sleep(0.5);
let guessIndex = randi(3);
this.guessEls[guessIndex].textContent = "✔";
await sleep(0.5);
this.status("Revealing a goat");
await sleep(0.5);
const revealChoices = [0, 1, 2].filter(
(i) => i !== guessIndex && i !== winIndex
);
const doorToReveal = revealChoices[randi(revealChoices.length)];
this.cardEls[doorToReveal].textContent = "G";
await sleep(0.5);
this.status(this.isSwitch ? "Switching choice" : "Not switching choice");
await sleep(0.5);
if (this.isSwitch) {
this.guessEls[guessIndex].textContent = "";
guessIndex = [0, 1, 2].find(
(i) => i !== guessIndex && i !== doorToReveal
);
this.guessEls[guessIndex].textContent = "✔";
}
await sleep(0.5);
this.status("Reveal");
await sleep(0.5);
this.cardEls.forEach((el, i) => (el.textContent = doors[i]));
const win = guessIndex === winIndex;
if (this.isSwitch) {
playsSwitch++;
if (win) winsSwitch++;
} else {
playsNoSwitch++;
if (win) winsNoSwitch++;
}
this.status(win ? "Reveal (WIN!)" : "Reveal (LOSE!)");
updateScoreboard();
await sleep(2);
}
start() {
if (this._looping) return;
this._looping = true;
const loop = async () => {
while (running) {
await this.playOnce();
}
this._looping = false;
};
loop();
}
}
const boards = [
new MontyBoard("switch-board", true),
new MontyBoard("no-switch-board", false),
];
playBtn.addEventListener("click", () => {
running = !running;
playBtn.textContent = running ? "Stop simulation" : "Start simulation";
if (running) boards.forEach((b) => b.start());
});
speedControl.addEventListener("input", (e) => {
speed = parseInt(e.target.value) || 1;
speedDisplay.textContent = "x" + speed;
});
updateScoreboard();
Enjoy this post? Please subscribe to my RSS feed on Feedly or add my RSS XML file to another reader!