Monty Hall Problem in Ruby

13 Dec 2010

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:

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 patchclassArraydefshuffle!size.downto(1){|n|pushdelete_at(rand(n))}selfendendclassPlayerattr_accessor:nameattr_accessor:choiceattr_accessor:playedattr_accessor:wonattr_accessor:lostdefinitialize(name)self.name=nameself.played=0self.won=0self.lost=0enddefpick_door(door_number)self.choice=door_number#puts "PLAYER PICKS: #{self.choice}"enddefwinself.won+=1self.played+=1enddefloseself.lost+=1self.played+=1enddefpercentagenum=(self.won.to_f/self.played.to_f)*100sprintf('%.2f',num)+"%"endendclassGameattr_accessor:doorsattr_accessor:door_statesdefinitialize# set up doorsself.doors=Array.newself.doors=["goat","goat","car"]self.doors.shuffle!# set up door statesself.door_states=Array.newself.door_states=["closed","closed","closed"]end# main loopdefrun(player_1,player_2)# Player 1 randomly picks a door# Player 2 uses this door for simplicityplayer_choice=rand(3)player_1.pick_door(player_choice)player_2.pick_door(player_choice)# Host picks a door with a goat and reveals doorgoats=[]self.doors.each_index{|d|goats<<difdoors[d]=="goat"}goats.deleteplayer_choice# if player won already then there are two goat doors, host picks oneifgoats.length>1goats.delete_atrand(2)end# first is redundant but safedoor_states[goats.first]="open"# Player 1 switches between two remaining doors# Player 2 does not switch so do nothingremaining=[]self.door_states.each_index{|d|remaining<<difdoor_states[d]=="closed"}remaining.deleteplayer_choiceplayer_1.choice=remaining.first# first is redundant but safe# Log resultifwin?(player_1)player_1.winelseplayer_1.loseendifwin?(player_2)player_2.winelseplayer_2.loseendend# did player win?defwin?(player)ifself.doors[player.choice]=="car"trueelsefalseendend# print out the game statisticsdefstats(p1,p2)# terminal clear codeprint%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}"endend# Create playersplayer_1=Player.new("Player 1")player_2=Player.new("Player 2")# Create new game showwhileplayer_1.played<1000g=Game.newg.run(player_1,player_2)g.stats(player_1,player_2)# for terminal sanity if terminal blinks too much#sleep 0.02end

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.