I'm traveling this week with another family (our neighbors and BFFs), including two pre-teen kids who were amusing themselves in the car by playing a variant of the classic Monty Hall game. In this game, a game show host hides prizes behind three doors. One of them is a brand new car; the other two are mangy old goats. The player (who strongly prefers the car) picks a door. The host then opens one of the other two doors, revealing a goat, and then asks the player if they want to stick with their first choice, or switch to the other door.
The vast majority of people who have not encountered this puzzle before — including a great many mathematicians and other Ph.D.'s — incorrectly think that the remaining two doors have an equal chance of hiding a car, and so it doesn't matter if you switch. This is incorrect; your odds of winning are twice as high if you switch! But the human mind has a great deal of trouble accepting this. (Even though pigeons, apparently, can learn this with little trouble.)
Or so the math says. But does this counterintuitive strategy really work? Let's find out!
Build a robot to play Monty Hall
In this column, we're going to bang out a little program using MiniScript to automatically play this game many times, using any strategy you choose. It'll keep track of how often the game is won, so you can see for yourself what strategy does best.
You can run this code in Mini Micro, or using command-line MiniScript, or even the Try-It! page. Whichever you choose, start by defining a couple of functions to make the player's decisions in the game.
// initialPick: return which door (0, 1, or 2)
// you want to guess initially.
initialPick = function
// Let's pick one at random.
return floor(rnd * 3)
end function
// switch: return true to switch, or false to stay
// with your initial pick, given the current state
// like "?", "goat", "PICK".
switch = function(state)
// Let's always switch!
return true
end function
Note that MiniScript, like most modern programming languages, starts indexing at 0 — so our door numbers are 0, 1, and 2.
The versions above pick an initial door at random, and then always switch after some non-car door is revealed. But feel free to change these functions however you like! You could have initialPick
always choose door 0, if that makes sense to you. Have switch
always return false
to never change your initial pick, or even use input
to ask the user (although in that case, it's not really a robot playing the game!).
Now, let's write a function to play one round of the game, making use of the functions above for the player's decisions.
// doOneGame: run the game once, and return true
// if the player wins, false if the player loses.
doOneGame = function
prizes = ["goat", "goat", "goat"]
carPos = floor(rnd * 3)
prizes[carPos] = "CAR!"
knownState = ["?", "?", "?"]
// Let the player pick a door
print "Doors: " + knownState.join(", ")
pick = initialPick
knownState[pick] = "PICK"
print "You pick #" + pick + "; Doors: " + knownState.join(", ")
// Now, let the host reveal a goat
while true
revealPos = floor(rnd * 3)
if revealPos != pick and prizes[revealPos] == "goat" then break
end while
knownState[revealPos] = prizes[revealPos]
print "Host opens #" + revealPos + "; Doors: " + knownState.join(", ")
// See if the player wants to switch to the other door
print "Do you switch? ", "..."
if switch(knownState) then
pick = knownState.indexOf("?")
print "YES! You switch to " + " #" + pick + "."
else
print "No."
end if
// Reveal whether they won the car
print "Prizes: " + prizes.join(", ")
print "You get a " + prizes[pick], ""
if prizes[pick] == "goat" then
print " ...You lose!"
return false
else
print " ...YOU WIN!"
return true
end if
end function
This looks a little complicated, but it's understandable if you go through it step by step. We keep two lists: one (prizes
) is the actual prizes, where each element is either "goat" or "CAR!". The other one (knownState
) represents what the player actually knows about the three doors. Each element here is either "?", "PICK" (indicating the player's choice, though they still don't know what's behind it), or "goat" (a non-car door revealed by the host).
So this code has the player pick a door, and updates the known state by replacing "?" with "PICK" at that position. Then the host chooses another door — one that's not the player's pick, but also not the car — and reveals that. Then the player is given a chance to switch to the other door (the only one still marked "?" in the known state). Finally, all the prizes are revealed, and we report whether the player successfully picked the door with the car.
Make a main loop
You could run the code at this point and play the game once by just adding
doOneGame
to the end of your program. In fact, do that! It's always a good idea to test your code early and often.
But then you'll want a way to play the game over and over, so you can really get a feel for how your strategy performs in the long run. So, let's replace that single doOneGame
call with this main loop:
played = 0
won = 0
auto = false
while true
print
played += 1
if doOneGame then won += 1
print "Won: " + won + "/" + played + " (" +
round(100 * won / played) + "%)"
if auto then
wait 0.5
else
inp = input("Play again, Autoplay, or Quit: ").upper[:1]
if inp == "A" then
auto = true
else if inp == "Q" then
exit
end if
end if
end while
This keeps a count of how many games have been played
and won
, and after each game, displays the updated statistics. Then, if we're not already in auto-play mode, it asks the user what to do next. The player can enter "A" to start auto-play mode, "Q" to quit, or anything else to play one more time. Once auto-play mode has been triggered, it skips this input, and automatically plays again after a short delay. (Feel free to change or remove wait 0.5
if you want it to go faster or slower.)
Enlightenment Through Simulation
I've always said, you don't really understand something unless you can simulate it. (For example: I can't simulate quantum mechanics!)
This applies even to a relatively simple problem like this. According to Wikipedia, Paul Erdős, one of the most prolific mathematicians in history, remained unconvinced of this always-switch strategy until he was shown a computer simulation of it.
With this short MiniScript program, you can play around with different strategies, study individual games to see what's going on, and then let it run for hundreds of games to see the average result. You'll come to understand the game much more deeply this way than you would with any other method.
This same approach can be used for many other problems too. Physics, chemistry, math, lotteries, fictional magic systems, or anything else — if you can describe the rules clearly enough to write code for it, then you can simulate it. The simulations will show emergent behavior which may not be obvious from a simple study of the rules, and that's where things get fun and interesting.
What do you think? Do you believe you should always switch in the Monty Hall game? What other systems would be fun to simulate? Leave your thoughts in the comments below!