I was reading a thread on stackoverflow about randomness and cryptography security and there was a comment that started me down a series of articles talking about the Monty Hall problem:
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. You pick a door, say No. 1, and the host, who knows what’s behind the doors, opens another door, say No. 3, which has a goat. He then says to you, “Do you want to pick door No. 2?” Is it to your advantage to switch your choice?
The take away from the probability puzzle is that you should switch. It’s completely unintuitive and I didn’t really get it even after reading about it for some time until I watched this youtube video. It’s a bit slow paced but it’s the best explanation and visuals out there.
So even after grok’ing the concept, I wanted to prove it. So I wrote a ruby program to simulate the game show. Here’s the results from one run:
Monty Hall Games Played: 1000
Player 1 (switches): 675/1000 : 67.50%
Player 2 (stays) : 325/1000 : 32.50%
The code is pretty simple and quick. Of note is that I sort of use the same game and player_2 just picks the same door of the same game using the same rand(3) number. So this is like two people playing at the same time and one always switches. I used the sleep statement near the bottom to keep the terminal from blinking. Seems to not blink on Mac or Linux but I remember having some weird behavior somewhere.
#!/usr/bin/ruby
# a simulation of the monty hall problem</p>
# monkey patch
class Array
def shuffle!
size.downto(1) { |n| push delete_at(rand(n)) }
self
end
end
class Player
attr_accessor :name
attr_accessor :choice
attr_accessor :played
attr_accessor :won
attr_accessor :lost
def initialize(name)
self.name = name
self.played = 0
self.won = 0
self.lost = 0
end
def pick_door(door_number)
self.choice = door_number
#puts "PLAYER PICKS: #{self.choice}"
end
def win
self.won += 1
self.played += 1
end
def lose
self.lost += 1
self.played += 1
end
def percentage
num = (self.won.to_f/self.played.to_f) * 100
sprintf('%.2f', num) + "%"
end
end
class Game
attr_accessor :doors
attr_accessor :door_states
def initialize
# set up doors
self.doors = Array.new
self.doors = [ "goat", "goat", "car" ]
self.doors.shuffle!
# set up door states
self.door_states = Array.new
self.door_states = [ "closed", "closed", "closed" ]
end
# main loop
def run(player_1, player_2)
# Player 1 randomly picks a door
# Player 2 uses this door for simplicity
player_choice = rand(3)
player_1.pick_door(player_choice)
player_2.pick_door(player_choice)
# Host picks a door with a goat and reveals door
goats = []
self.doors.each_index {|d| goats << d if doors[d] == "goat"}
goats.delete player_choice
# if player won already then there are two goat doors, host picks one
if goats.length > 1
goats.delete_at rand(2)
end
# first is redundant but safe
door_states[goats.first] = "open"
# Player 1 switches between two remaining doors
# Player 2 does not switch so do nothing
remaining = []
self.door_states.each_index {|d| remaining << d if door_states[d] == "closed"}
remaining.delete player_choice
player_1.choice = remaining.first # first is redundant but safe
# Log result
if win?(player_1)
player_1.win
else
player_1.lose
end
if win?(player_2)
player_2.win
else
player_2.lose
end
end
# did player win?
def win?(player)
if self.doors[player.choice] == "car"
true
else
false
end
end
# print out the game statistics
def stats(p1, p2)
# terminal clear code
print %x{clear}
puts "Monty Hall Games Played: #{p1.played}"
puts "#{p1.name} (switches): #{p1.won}/#{p1.played} : #{p1.percentage}"
puts "#{p2.name} (stays) : #{p2.won}/#{p2.played} : #{p2.percentage}"
end
end
# Create players
player_1 = Player.new("Player 1")
player_2 = Player.new("Player 2")
# Create new game show
while player_1.played < 1000
g = Game.new
g.run(player_1, player_2)
g.stats(player_1, player_2)
# for terminal sanity if terminal blinks too much
#sleep 0.02
end
It was a good exercise. I was playing around with ruby-ncurses but it became too much of a hassle. Maybe some other project will need a TUI, I do still want to play with ncurses or something similar.