Monty Hall in Ruby

John Cleary (@TheRealBifter) is doing a nice project - The 12 TDD's of Xmas. Day 4 was the Monty Hall problem. I did it in Ruby. Here's the code from traffic-light 111.

Tests first:
require './monty_hall'
require 'test/unit'

class TestMontyHall < Test::Unit::TestCase

  def test_either_goat_door_is_opened_when_you_choose_the_car_door
    check_either_goat([:car, :goat, :goat],
      { :chosen_door => 0, 
        :goat_doors  => [1, 2] 
      })
    check_either_goat([:goat, :car, :goat],
      { :chosen_door => 1, 
        :goat_doors  => [0, 2]
      })
    check_either_goat([:goat, :goat, :car],
      { :chosen_door => 2, 
        :goat_doors  => [0, 1]
      })
  end

  def test_other_goat_door_is_opened_when_you_choose_a_goat_door
    prizes = [:car, :goat, :goat]
    check_other_goat(prizes, 
      { :chosen_door  => 1, 
        :opened_door  => 2, 
        :offered_door => 0
      })
    check_other_goat(prizes, 
      { :chosen_door  => 2,  
        :opened_door  => 1, 
        :offered_door => 0 
      })

    prizes = [:goat, :car, :goat]
    check_other_goat(prizes, 
      { :chosen_door  => 0, 
        :opened_door  => 2, 
        :offered_door => 1
      })
    check_other_goat(prizes, 
      { :chosen_door  => 2, 
        :opened_door  => 0, 
        :offered_door => 1
      })

    prizes = [:goat, :goat, :car]
    check_other_goat(prizes, 
      { :chosen_door  => 0, 
        :opened_door  => 1, 
        :offered_door => 2
      })
    check_other_goat(prizes, 
      { :chosen_door  => 1,  
        :opened_door  => 0, 
        :offered_door => 2
      })
  end

  def test_strategy_of_sticking_with_chosen_door
    wins = Array.new(big) { MontyHall.new() }
                .count { |game| game.chosen_door == game.car_door }
    puts "Win car(sticking with chosen door):#{wins}/#{big}"
  end

  def test_strategy_of_switching_to_offered_door
    wins = Array.new(big) { MontyHall.new() }
                .count { |game| game.offered_door == game.car_door }
    puts "Win car(switching to offered door):#{wins}/#{big}"
  end

  #- - - - - - - - - - - - - - - - - - - - - - - -

  def check_either_goat(prizes, expected)
    chosen_door = expected[:chosen_door]
    goat_doors = expected[:goat_doors]
    check_params(prizes, chosen_door, goat_doors[0], goat_doors[1])
    
    goat_counts = [0,0]
    100.times do |n|
      game = MontyHall.new(prizes,chosen_door)

      opened_door = game.opened_door
      offered_door = game.offered_door

      assert_equal chosen_door, game.chosen_door
      assert_equal goat_doors.sort, [opened_door,offered_door].sort
      assert_equal doors, [chosen_door,opened_door,offered_door].sort
      assert_equal :car , prizes[chosen_door]
      assert_equal :goat, prizes[opened_door]
      assert_equal :goat, prizes[offered_door]     

      [0,1].each do |n|
        goat_counts[n] += (offered_door == goat_doors[n] ? 1 : 0)
      end
    end
    [0,1].each { |n| assert goat_counts[n] > 25 }
  end

  def check_other_goat(prizes, expected)
    chosen_door = expected[:chosen_door]
    opened_door = expected[:opened_door]
    offered_door = expected[:offered_door]

    check_params(prizes, chosen_door, opened_door, offered_door)

    game = MontyHall.new(prizes, chosen_door)

    assert_equal  chosen_door, game.chosen_door
    assert_equal  opened_door, game.opened_door
    assert_equal offered_door, game.offered_door

    assert_equal :goat, prizes[ chosen_door]
    assert_equal :goat, prizes[ opened_door]
    assert_equal :car , prizes[offered_door]
  end

  def check_params(prizes, door1, door2, door3)
    assert_equal 3, prizes.length
    prizes.each { |prize| assert [:goat,:car].include? prize }
    assert_equal doors, [door1,door2,door3].sort
  end

  def doors
    [0,1,2]
  end

  def big
    1000
  end

end
Code second:
class MontyHall

  def initialize(prizes = [:goat,:goat,:car].shuffle, 
                 chosen_door = doors.shuffle[0])
    @prizes = prizes
    @chosen_door = chosen_door
    @car_door = prizes.find_index { |prize| prize == :car }
    if prizes[chosen_door] == :car
      @opened_door = goat_doors.shuffle[0] 
    end
    if prizes[chosen_door] == :goat
      @opened_door = (goat_doors - [chosen_door])[0] 
    end
    @offered_door = (doors - [chosen_door, opened_door])[0]
  end

  def chosen_door
    @chosen_door
  end

  def car_door
    @car_door
  end

  def opened_door
    @opened_door
  end

  def offered_door
    @offered_door
  end

private 

  def doors
    [0,1,2]
  end

  def goat_doors
    doors.select { |door| @prizes[door] == :goat }
  end

end
You can replay my entire progression (warts and all) on Cyber-Dojo (naturally).

Bowling Game in Ruby

John Cleary (@TheRealBifter) is doing a nice project - The 12 TDD's of Xmas. Day 9 was the Bowling Game problem. I did it in Ruby. Here's the code from traffic-light 100.

Tests first:
require './score'
require 'test/unit'

class TestScore < Test::Unit::TestCase

  def test_score_uninteresting_game
    #           7     7     7     7    7 = 35
    #        6     6     6     6     6   = 30
    balls = "51|52|51|52|51|52|51|52|51|52"
    assert_equal 65, score(balls)
  end

  def test_score_all_frames_5_spare
    #           15    15    15    15    15 = 75
    #        15    15    15    15    15    = 75
    balls = "5/|5/|5/|5/|5/|5/|5/|5/|5/|5/|5"
    assert_equal 150, score(balls)
  end

  def test_perfect_score
    #          30  30  30  30  30 = 150
    #        30  30  30  30  30   = 150
    balls = "X|X|X|X|X|X|X|X|X|X|XX"
    assert_equal 300, score(balls)
  end

  def test_10_strikes_then_33
    #          30  30  30  30  16 = 136
    #        30  30  30  30  23   = 143
    balls = "X|X|X|X|X|X|X|X|X|X|33"
    assert_equal 279, score(balls)
  end

  def test_game_with_spare_in_middle_of_strikes
    #          20   30  30  30  30    = 140
    #        25  20   30  30  30      = 135
    balls = "X|X|5/|X|X|X|X|X|X|X|XX" 
    assert_equal 275, score(balls)
  end

  def test_game_with_strike_in_middle_of_spares
    #           15    20   15    13    20  = 83
    #        13    11    20   14    12     = 70
    balls = "2/|3/|5/|1/|X|3/|5/|4/|3/|2/|X"
    assert_equal 153, score(balls)
  end

  def test_game_with_zero_balls
    #           8     7     7     7     7 = 36
    #        6     5     6     6     6    = 29
    balls = "51|62|50|52|51|52|51|52|51|52"
    assert_equal 65, score(balls)
  end

  def test_game_with_dash_as_zero_balls
    #           8     7     7     7     8 = 37
    #        6     5     6     6     6    = 29
    balls = "51|62|5-|52|51|52|51|52|51|62"
    assert_equal 66, score(balls)
  end

end
Code second:
def score(balls)
  frames = (balls).split("|")
  while frames.length != 12
    frames << "0"
  end
  frames.each_cons(3).collect{ |frame| 
    frame_score(frame)
  }.inject(:+)
end

def frame_score(frames)
  if strike? frames[0]
    10 + strike_bonus(frames[1..2])
  elsif spare? frames[0]
    10 + ball_score(frames[1][0]) 
  else
    frames[0].chars.collect{ |ball| 
      ball_score(ball)
    }.inject(:+)
  end
end

def strike_bonus(frames)
  if frames[0] == "XX"
    20
  elsif strike? frames[0]
    10 + ball_score(frames[1][0])
  elsif spare? frames[0]
    10
  else
    ball_score(frames[0][0]) + ball_score(frames[0][1])
  end
end

def ball_score(ball)
  if strike? ball
    10
  else
    ball.to_i
  end
end

def strike?(frame)
  frame == "X"
end

def spare?(frame)
  frame[-1] == "/"
end
It took me a while to realize that each_slice should have been each_cons. You can replay my entire progression (warts and all) on Cyber-Dojo (naturally).

Losing my virginity

is an excellent book by Richard Branson (isbn 978-0-7535-1955-4). As usual I'm going to quote from a few pages:
During January and February 1966, Jonny and I began to talk about how to change the school rules. We were fifteen years old, but we believed that we could make a difference. My parents had brought me up to think that we could change the world, so when I looked at how Stowe was run I felt sure that I could do it better.
Above all, you want to create something you are proud of.
Life in the basement was the kind of all-embracing glorious chaos in which I thrived and have thrived ever since.
Hearing others' stories made me realise how lucky I was in my relationships with my own parents. They had never judged me, and always supported me, always praised the good things rather than criticised the bad things.
Look, she said, I wouldn't lend you the money if I didn't want to. What's money for anyway? It's to make things happen.
I rely far more on gut instinct than researching huge amounts of statistics. This might be because, due to my dyslexia, I distrust numbers, which I feel can be twisted to prove anything.
Another man had to hand over his three-year-old daughter to his nanny and say goodbye to her. I just hugged him. There was nothing else I could do. We both had tears in our eyes. I was a father too.
It was clear that Lord King treated me with a contempt that would rub off on how everyone at British Airways felt they could treat Virgin Atlantic.
Fun is at the core of the way I like to do business and it has been key to everything I've done from the outset. More than any other element, fun is the secret of Virgin's success.
Even though I'm often asked to define my 'business philosophy', I generally won't do so, because I don't believe it can be taught as if it were a recipe.
My vision for Virgin has never been rigid and changes constantly, like the company itself.
Our priorities are the opposite of our large competitors'. Convention dictates that a company should look after its shareholders first, its customers next, and last of all worry about its employees. Virgin does the opposite. For us, our employees matter the most. It just seems common sense to me that, if you start off with a happy, well-motivated workforce, you're much more likely to have happy customers. And in due course the resulting profits will make your shareholders happy.
It is my belief that most 'necessary evils' are far more evil than necessary.
It's just a matter of scale, but first you have to believe you can make it happen.