Showing posts with label ruby. Show all posts
Showing posts with label ruby. Show all posts

testing a random die roll

What tests would I write? Maybe I'd start by testing it only rolls 1 to 6
def test_doesnt_roll_less_than_1_or_greater_than_6
  1200.times do
    die = roll_die
    assert 1 <= die && die <= 6
  end
end
That's a good start. But it's not nearly enough. One way to think about testing is to imagine someone is deliberately writing the code so that it passes the tests but is nevertheless completely incorrect. Take, for example, sorting an array of N integers. What tests would you write? Now suppose I tell you the incorrect implementation simply returns an array of N 42's. Are you testing the output array is a permutation of the input array?

Back to rolling the die... an implementation of roll_die() which always returned 3 would pass my first test. I need to test all of 1-6 get returned.
def test_rolls_1_to_6_at_least_once_each_in_600_rolls
  rolls = [-1,0,0,0,0,0,0]
  600.times do
    die = roll_die
    rolls[die] += 1
  end
  assert rolls[1] >= 1
  assert rolls[2] >= 1
  assert rolls[3] >= 1
  assert rolls[4] >= 1
  assert rolls[5] >= 1
  assert rolls[6] >= 1
end
Why 600 rolls? You might be thinking it's too large. That a decent roll_die() should return at least one each of 1-6 after a lot fewer than 600 rolls. Or, to put it another way, if I have to wait till the 600th roll to get my first 5 then roll_die() is looking a bit suspect. Fair enough.

The tests form a specification. If I want to specify a "tighter" implementation of roll_die() I can simply change 600 to, 200 say. The 200 is then part of the specification.

Once again, it's easy to imagine an incorrect implementation that passes all the tests. How about one that simply cycles repeatedly through 1-6...
$n = 1

def roll_die
  $n += 1
  $n %= 6
  $n + 1
end
I've tested the "die" part of "random die". I've got to the "random" part of "random die". My incorrect implementation is not very random. It's very regular. It's very ordered. Suppose I call roll_die() 1200 times, save the 1200 values in a file, and then compress the file. I should get a lot of compression. Let's try it…
dice = ""
1200.times { dice += roll_die.to_s }
File.open('dice.txt', 'w') { |f| f.write(dice) }
unzipped_size = File.size('dice.txt')
assert_equal 1200, unzipped_size
`zip dice.txt.zip dice.txt`
zipped_size = File.size('dice.txt.zip')
p zipped_size
On my macbook I get a value of 184. As expected, a lot of compression. Let's compare the compression to an implementation that isn't deliberately incorrect.
def roll_die
  [1,2,3,4,5,6].shuffle[0]
end
This gives a zipped file size of around 680. A lot less compression. I can use that as part of the specification.
def test_roll_is_random_entropically
  dice = ""
  1200.times { dice += roll_die().to_s }
  File.open('dice.txt', 'w') { |f| f.write(dice) }
  unzipped_size = File.size('dice.txt')
  assert_equal 1200, unzipped_size
  `zip dice.txt.zip dice.txt`
  zipped_size = File.size('dice.txt.zip')
  assert zipped_size > 600
end
Imagine someone is deliberately writing the code so that it passes the tests but is nevertheless completely incorrect…


Poker hands in Ruby

John Cleary (@TheRealBifter) is doing a nice project - The 12 TDD's of Xmas. Day 11 was Poker Hands. I did it in Ruby. Here's the code from traffic-light 116.

Tests first:
require './card'
require './hand'
require 'test/unit'

class TestUntitled < Test::Unit::TestCase

  def test_start
    hand = Hand.new("2H 4S 4C 2D 4H")
    assert_equal Card.new('2',:hearts),   hand[0]
    assert_equal Card.new('4',:spades),   hand[1]
    assert_equal Card.new('4',:clubs),    hand[2]
    assert_equal Card.new('2',:diamonds), hand[3]
    assert_equal Card.new('4',:hearts),   hand[4]
  end

  def test_card_has_pips_and_suit_set_on_creation
    card = Card.new('2',:hearts)
    assert_equal '2', card.pips
    assert_equal :hearts, card.suit
    card = Card.new('T',:hearts)
    assert_equal 'T', card.pips
    assert_equal :hearts, card.suit
    card = Card.new('J',:hearts)
    assert_equal 'J', card.pips
    assert_equal :hearts, card.suit
    card = Card.new('Q',:hearts)
    assert_equal 'Q', card.pips
    assert_equal :hearts, card.suit
    card = Card.new('K',:hearts)
    assert_equal 'K', card.pips
    assert_equal :hearts, card.suit
    card = Card.new('A',:hearts)
    assert_equal 'A', card.pips
    assert_equal :hearts, card.suit
  end

  def test_hand_ranked_three_of_a_kind
    assert_equal :three_of_a_kind, Hand.new("2H 4S 4C AD 4H").rank
  end

  def test_hand_ranked_one_pair
    assert_equal :one_pair, Hand.new("2H 4S 5C JD 4H").rank
  end

  def test_hand_ranked_two_pairs
    assert_equal :two_pairs, Hand.new("2H 4S 5C 2D 4H").rank
  end

  def test_hand_ranked_flush
    assert_equal :flush, Hand.new("2H 4H 6H 8H TH").rank
  end

  def test_hand_ranked_straight
    assert_equal :straight, Hand.new("2H 3C 4H 5H 6H").rank
  end

  def test_hand_ranked_full_house
    assert_equal :full_house, Hand.new("2H 4S 4C 2D 4H").rank
  end

  def test_hand_ranked_four_of_a_kind
    assert_equal :four_of_a_kind, Hand.new("2H 4S 4C 4D 4H").rank
  end

  def test_hand_ranked_straight_flush
    assert_equal :straight_flush, Hand.new("2H 4H 3H 5H 6H").rank
  end

  def test_hand_ranked_high_card
    assert_equal :high_card, Hand.new("2C 3H 4S 8C AH").rank
  end

  def test_full_house_beats_flush
    black = Hand.new("2H 4S 4C 2D 4H")    
    white = Hand.new("2S 8S AS QS 3S")
    assert_equal 1, black <=> white
  end

  def test_higher_card_wins_if_equal_rank
    black = Hand.new("2H 3D 5S 9C KD")
    assert_equal :high_card, black.rank
    white = Hand.new("2C 3H 4S 8C AH")
    assert_equal :high_card, white.rank
    assert_equal -1, black <=> white
  end

  def test_equal_hands
    black = Hand.new("2H 3D 5S 9C KD")
    assert_equal :high_card, black.rank
    white = Hand.new("2D 3H 5C 9S KH")
    assert_equal :high_card, white.rank
    assert_equal 0, black <=> white
  end

end
Code second:
class Hand

  def initialize(cards)
    @cards = 
      cards.gsub(/\s+/, "")
           .scan(/.{2}/)
           .map{|ch| Card.new(ch[0],suit(ch[1]))}
  end

  def [](n)
    return @cards[n]
  end

  def rank
    return :straight_flush if straight? && flush?
    return :flush          if flush?
    return :straight       if straight?

    pip_tallies = pip_counts.sort.reverse
    return {
      [4,1] => :four_of_a_kind,
      [3,2] => :full_house,
      [3,1] => :three_of_a_kind,
      [2,2] => :two_pairs,
      [2,1] => :one_pair,
      [1,1] => :high_card
    }[pip_tallies[0..1]]
  end

  def <=>(other)
    keys <=> other.keys
  end

  def keys
    [ranking,pip_counts]
  end

private

  def ranking
    ranks.index(rank)
  end

  def ranks
    [
      :high_card,
      :pair,
      :two_pairs,
      :three_of_a_kind,
      :straight,
      :flush, 
      :full_house,
      :four_of_a_kind,
      :straight_flush
    ]
  end

  def pip_counts
    "23456789TJQKA"
      .chars
      .collect {|pips| pip_count(pips)}
  end

  def pip_count(pips)
    @cards.count{|card| card.pips == pips}
  end

  def pip_flags
    pip_counts.map{|n| n > 0 ? 'T' : 'F'}.join
  end

  def straight?
    pip_flags.include? 'TTTTT'
  end

  def flush?
    suit_counts.any?{|n| n == 5}
  end

  def suit_counts
    suits.collect{|suit| suit_count(suit)}
  end

  def suits  
    [:clubs,:diamonds,:hearts,:spades]
  end

  def suit_count(suit)
    @cards.count{|card| card.suit == suit}
  end

  def suit(ch)
    return suits["CDHS".index(ch)]
  end

end
class Card

  def initialize(pips,suit)
    @pips,@suit = pips,suit
  end

  def ==(other)
    pips == other.pips && suit == other.suit
  end

  def pips
    @pips
  end

  def suit
    @suit
  end

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

Phone Numbers in Ruby

John Cleary (@TheRealBifter) is doing a nice project - The 12 TDD's of Xmas. Day 10 was the Phone number prefix problem. I did it in Ruby. Here's the code from traffic-light 14.

Tests first (very minimal - I'm a bit pressed for time):
require './consistent'
require 'test/unit'

class TestUntitled < Test::Unit::TestCase

  def test_consistent_phone_list
    list = { 
      'Bob' => '91125426',
      'Alice' => '97625992',
    }
    assert consistent(list)
  end

  def test_inconsistent_phone_list
    list = { 
      'Bob' => '91125426',
      'Alice' => '97625992',
      'Emergency' => '911'
    }
    assert !consistent(list)
  end

end
Code second:
def consistent(list)
  list.values.sort.each_cons(2).none? { |pair| prefix(*pair) }
end

def prefix(lhs,rhs)
  rhs.start_with? lhs
end
The each_cons from the previous problem proved very handy here. As did none? I really feel I'm starting to get the hang of ruby. You can replay my entire progression (warts and all) on Cyber-Dojo (naturally).

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).

assert_not_diff

The output of Ruby's Test::Unit::TestCase's
assert_equal(expected, actual) when expected and actual are not the same is often not as useful as it could be. For example:
expected = [1,99,2,3,{:a=>34,:b=>43}]
actual = [1,2,3,4,{:a=>324,:c=>555,:b=>43},5]
assert_equal expected, actual
produces:
<[1, 99, 2, 3, {:a=>34, :b=>43}]> expected but was
<[1, 2, 3, 4, {:a=>324, :c=>555, :b=>43}, 5]>.
For large objects which differ only slightly the output that tells you where they differ quickly gets lost in the mass of output telling you where they don't.

Perhaps what's happened here is a sort of Primitive Obsession.
It's as though assert_equal assumes it will only ever be called with primitives.

So I put together a little bit of code to help out. The simplest solution I can think of uses json and diff, so I thought I'd call it assert_not_diff
require 'json'
require 'Tempfile'

def assert_not_diff(lhs,rhs)
  if lhs != rhs
    puts `diff -y #{file_for lhs} #{file_for rhs}`
  end
end

def file_for(obj)
  exp = Tempfile.new("bk", "/tmp").open
  exp.write(JSON.pretty_generate(obj))
  exp.close
  exp.path
end

expected = [1,99,2,3,{:a=>34,:b=>43}]
actual = [1,2,3,4,{:a=>324,:c=>555,:b=>43},5]
assert_not_diff expected, actual
This produces:
[                  [
  1,                 1,
  99,            <
  2,                 2,
  3,                 3,
                 >   4,
  {                  {
    "a": 34,     |     "a": 324,
                 >     "c": 555,
    "b": 43            "b": 43
  }              |   },
                 >   5
]                  ]
Which I think is a lot more useful.
Hope this proves useful to someone!

Bare bones ruby unit testing

This morning I spent a happy hour exploring a little of ruby's Test::Unit::TestCase. I started with this:
require 'test/unit'

class MyTest < Test::Unit::TestCase

  def test_one_plus_one_equals_two
    assert_equal 2, 1+1.1
  end

end
I wanted to see how little I needed to write my own, super-minimal implementation of Test::Unit::TestCase...
require 'test/unit'

class MyTest < MyTestCase

  def test_one_plus_one_equals_two
    assert_equal 2, 1+1.1
  end

end
After 204 traffic lights in cyber-dojo I ended up with this...
require 'assertion_failed_error'

class MyTestCase

  def self.test_names
    public_instance_methods.select{|name| name =~ /^test_/}
  end
  
  def assert_equal( expected, actual )
    message = 
      "#{expected.inspect} expected but was\n" +
      "#{actual.inspect}\n"
    assert_block(message) { expected == actual }
  end

  def assert_block( message )
    if (! yield)
      raise AssertionFailedError.new(message.to_s)
    end
  end

end

at_exit do
  ::ObjectSpace.each_object(Class) do |klass|
    if (klass < MyTestCase)
      klass.test_names.each do |method_name| 
        begin
          klass.new.send method_name
        rescue AssertionFailedError => error
          print "#{klass.name}:#{method_name}:\n" +
                "#{error.message}"
        end
      end
    end
  end
end
class AssertionFailedError < RuntimeError; end
which allowed me write...
require 'my_test_case'

class MyTest < MyTestCase

  def test_one_plus_one_equals_two
    assert_equal 2, 1+1.1
  end

end
and finally, I added this...
class MyTestCase
  ...
  def self.test( name, &block )
    define_method("test_#{name}".to_sym, &block)
  end
  ...
end
which allowed me to rewrite the test as...
require 'my_test_case'

class MyTest < MyTestCase

  test "1+1 == 2" do
    assert_equal 2, 1+1.1
  end

end
Fun :-)

Micro refactoring - from here to there

One of my Mastery book snippets from a few days ago reads:

Masters ... are zealots of practice, connoisseurs of the small, incremental step.

I was doing the roman numerals kata in ruby and something related to small incremental steps occurred to me. Here's three tests:

def test_to_roman
  assert_equal "I", to_roman(1)
  assert_equal "II", to_roman(2)
  assert_equal "III", to_roman(3)
end

and here's the first code snippet:

def to_roman(n) 
  roman = ''
  if n == 3
    roman = 'III' 
  end
  if n == 2
    roman = 'II'
  end
  if n == 1
    roman = 'I'
  end
  roman
end

I can refactor these three if statements into a single while statement, in very small steps, as follows:

def to_roman(n) 
  roman = ''
  if n >= 3
    roman += 'I'
    n -= 1
  end
  if n >= 2
    roman += 'I'
    n -= 1
  end
  if n >= 1
    roman += 'I'
    n -= 1 
  end
  roman
end

and then:

def to_roman(n) 
  roman = ''
  if n >= 1
    roman += 'I'
    n -= 1
  end
  if n >= 1
    roman += 'I'
    n -= 1
  end
  if n >= 1
    roman += 'I'
    n -= 1 
  end
  roman
end

and then:

def to_roman(n) 
  roman = ''
  while n >= 1
    roman += 'I'
    n -= 1
  end
  roman
end

Ok. Now here's the original again:

def to_roman(n) 
  roman = ''
  if n == 3
    roman = 'III'
  end
  if n == 2
    roman = 'II'
  end
  if n == 1
    roman = 'I'
  end
  roman
end

And this time I refactor in a different direction, towards an array lookup:

def to_roman(n) 
  units = [ '', 'I', 'II', 'III' ]
  roman = ''
  if n == 3
    roman = units[3]
  end
  if n == 2
    roman = units[2]
  end
  if n == 1
    roman = units[1]
  end
  roman
end

And then:

def to_roman(n) 
  units = [ '', 'I', 'II', 'III' ]
  roman = ''
  if n == 3
    roman = units[n]
  end
  if n == 2
    roman = units[n]
  end
  if n == 1
    roman = units[n]
  end
  roman
end

And then:

def to_roman(n) 
  units = [ '', 'I', 'II', 'III' ]
  roman = ''
  if n == 3
    roman = units[n]
  end
  if n == 2
    roman = units[n]
  end
  if n == 1
    roman = units[n]
  end
  roman
  units[n]
end

And then:

def to_roman(n) 
  units = [ '', 'I', 'II', 'III' ]
  units[n]
end

In this case I can refactor from a sequence of if statements in two directions equally easy - towards a while or towards an array lookup. Ok. Now consider the situation if I was starting from a switch instead of a sequence of ifs:

def to_roman(n) 
  case n
  when 3 then 'III'
  when 2 then 'II'
  when 1 then 'I'
  end
end

From this I can easily refactor towards an array lookup but refactoring towards a while is perhaps not quite so straightforward.

I'm suggesting that a construct is useful not just in it's own right but also in relation to all the other constructs it might get refactored to or from. How easily can I move from one construct to another? Do some constructs live only on one-way roads? Do some lead you down more dead-ends than others?

I'm reminded of the family on holiday in the West Country who got thoroughly lost. Spotting a farmer leaning on a gate they stop the car, wind down the window and ask

Excuse me. Can you tell me how to get to Nempnett Thrubwell?

The old farmer looks at them and says

Well.... you don't want to start from here.


Yahtzee in ruby in cyber-dojo

I've been doing the Yahtzee kata in cyber-dojo to improve my Ruby. During the 247 increments I learned about...
  • any?
  • send
  • [lo...hi]
  • reduce
  • :+
  • module names start with a capital
It's also on pastie if you like a black background. You can use Cyber-Dojo to step through the diffs of every single increment!
module Yahtzee

  def self.score dice, category, n=nil
    return -1 if invalid(dice, category)
    if category == :value
      value(dice, n)
    else
      send(category, dice)
    end
  end

  def self.chance dice
    dice.reduce(:+)
  end

  def self.yahtzee dice
    dice.uniq.length == 1 ? 50 : 0
  end

  def self.value dice, n
    dice.count{|die| die == n } * n
  end

  def self.one_pair dice
    die(tally(dice).find{|n| pair?n }) * 2
  end

  def self.two_pair dice
    score_tally dice, [2,2]
  end

  def self.three_of_a_kind dice
    score_tally dice, [3]
  end

  def self.four_of_a_kind dice
    score_tally dice, [4]
  end

  def self.full_house dice
    score_tally dice, [3,2]
  end
  
  def self.small_straight dice
    dice.sort == [1,2,3,4,5] ? 15 : 0
  end

  def self.large_straight dice
    dice.sort == [2,3,4,5,6] ? 20 : 0
  end

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

  def self.categories
    [:value, :chance, :yahtzee, :one_pair, :two_pair,
     :three_of_a_kind, :four_of_a_kind, :full_house,
     :small_straight, :large_straight
    ]
  end

  def self.invalid dice, category
    dice == nil \
      or dice.length != 5 \
        or dice.any?{|die| die < 1 or die > 6 } \
          or !categories.include? category
  end

  def self.score_tally dice, pattern
    starts?(pattern, tallies(dice)) \
      ? sum(dice, pattern.length) \
      : 0
  end

  def self.starts? short, long
    long[0...short.length] == short
  end

  def self.tallies dice
    tally(dice).map{|n| count(n) }
  end

  def self.rolls dice
    tally(dice).map{|n| die(n) }
  end

  def self.tally dice
    dice.uniq.map {|die| [dice.count(die),die] }.sort.reverse 
  end

  def self.sum dice, n
    sum2(rolls(dice)[0...n], dice)
  end

  def self.sum2 scorers, dice
    dice.select {|die| scorers.include?die }.reduce(:+)
  end

  def self.pair? n
    count(n) == 2
  end

  def self.count n
    n[0]
  end

  def self.die n
    n ? n[1] : 0
  end

end
I challenged myself to write the code in a functional style with no local variables - it's a bit 'dense' in places as a result.

cyber-dojo seems to work as planned!

I'm hoping to run at least one cyber-dojo as a birds-of-a-feather session at the upcoming accu conference. cyber-dojo allows you to perform code-katas using a web browser front end. To make sure it all works as planned I connected a client and a server together via a switch+router. It came together fairly painlessly! cyber-dojo is written in ruby and rails served by passenger and apache2 all running in an Ubuntu VirtualBox inside the MacBook on the right. The client on the left is Firefox on an MSI netbook also running Ubuntu.