Ruby

The Greed Ruby Koan

This post is an extension of my last post on the Ruby Koans. Today, I tackled the next Ruby Koan on the block, which was to score dice throws on a simplified version of the game Greed. The first thing I noticed was that the Koans that I got from EdgeCase had a typo in it. It suggested that the following was true:

def test_score_of_mixed_is_sum
    assert_equal 50, score([2,5,2,2,3])
    assert_equal 550, score([5,5,5,5])
  end

That first assert should actually total 250, as the 3 2’s are worth 200 and the 5 is worth 50. I searched the web to make sure that I wasn’t crazy and the version of this file checked in to Google Code by Tim Wingfield agreed with me, so I forged on.

The hardest part of this one for me was figuring out the best way to even work this out in English. I play the Farkle game on Facebook quite a bit, so I was really comfortable with scoring the dice, but I just couldn’t figure out a good step-by-step way to do this. I eventually decided on a Hash to tally up the number of each dice present. This choice was made even easier when I found that Hash had an each_pair method that enumerated over the collection.

After that, I tried my best to implement the logic (this code passes the tests, I don’t promise it is bug-free) and have now put this up for criticism. Last time, I learned some great lessons from Nathan Kelley who shared his code in the comments of my last post. If you have any questions, comments, criticisms, or rebukes of this code, let me know and I’d love to hear them.

I definitely am guilty of writing C# in Ruby and had to undo a lot of code where I started to go down that path, so I welcome comments from Ruby enthusiasts on my code.

require 'edgecase'

# Greed is a dice game where you roll up to five dice to accumulate
# points.  The following "score" function will be used calculate the
# score of a single roll of the dice.
#
# A greed roll is scored as follows:
#
# * A set of three ones is 1000 points
#   
# * A set of three numbers (other than ones) is worth 100 times the
#   number. (e.g. three fives is 500 points).
#
# * A one (that is not part of a set of three) is worth 100 points.
#
# * A five (that is not part of a set of three) is worth 50 points.
#
# * Everything else is worth 0 points.
#
#
# Examples:
#
# score([1,1,1,5,1]) => 1150 points
# score([2,3,4,6,2]) => 0 points
# score([3,4,5,3,3]) => 350 points
# score([1,5,1,2,4]) => 250 points
#
# More scoing examples are given in the tests below:
#
# Your goal is to write the score method.

STANDARD_TRIPLET_MULTIPLIER = 100
ONES_TRIPLET_MULTIPLIER = 1000
ONES_VALUE = 100
FIVES_VALUE = 50

def score(dice)
  # You need to write this method
  result = 0  
  return result if invalid_dice?(dice)
  
  dice_sort = {1=>0, 2=>0, 3=>0, 4=>0, 5=>0, 6=>0}
  
  # Increment the tally, using the die face as the hash key
  # by iterating over the passed in array of dice
  dice.each do | die |
      dice_sort[die] += 1 
    end
  
  # Iterate over the pairs and score them up  
  dice_sort.each_pair do |key, value|
      if non_single_score?(key)
        result += key*STANDARD_TRIPLET_MULTIPLIER if value>= 3
      else
        # I LOVE Multiple assignments
        groups_of_three, remainder = value/3, value%3
        
        result += (ONES_TRIPLET_MULTIPLIER * groups_of_three) + (ONES_VALUE * remainder) if key == 1
        result += (key * STANDARD_TRIPLET_MULTIPLIER * groups_of_three) + (FIVES_VALUE * remainder) if key == 5
      end      
  end 
      
  result      
end
    
def non_single_score?(die)
  return true if die != 1 and die != 5
end

def invalid_dice?(dice)
  return true if dice.length == 0
end

class AboutScoringAssignment < EdgeCase::Koan
  def test_score_of_an_empty_list_is_zero
    assert_equal 0, score([])
  end

  def test_score_of_a_single_roll_of_5_is_50
    assert_equal 50, score([5])
  end

  def test_score_of_a_single_roll_of_1_is_100
    assert_equal 100, score([1])
  end

  def test_score_of_mulitple_1s_and_5s_is_the_sum
    assert_equal 300, score([1,5,5,1])
  end

  def test_score_of_single_2s_3s_4s_and_6s_are_zero
    assert_equal 0, score([2,3,4,6])
  end

  def test_score_of_a_triple_1_is_1000
    assert_equal 1000, score([1,1,1])
  end

  def test_score_of_other_triples_is_100x
    assert_equal 200, score([2,2,2])
    assert_equal 300, score([3,3,3])
    assert_equal 400, score([4,4,4])
    assert_equal 500, score([5,5,5])
    assert_equal 600, score([6,6,6])
  end

  def test_score_of_mixed_is_sum
    assert_equal 250, score([2,5,2,2,3])
    assert_equal 550, score([5,5,5,5])
  end
end

9 comments The Greed Ruby Koan

I found the same error, here is how I went about solving the scoring exercise. These koans are nice for learning Ruby.

I had the same idea of using a hash to count them up


def score(dice)
result = 0
counter = {}
(1..6).each { |x| counter[x] = 0 }
dice.each { |d| counter[d] += 1 }
if counter[1] >= 3
result += 1000
counter[1] -= 3
end

(2..6).each do |x|
if counter[x] >= 3
result += (100 * x)
counter[x] -= 3
end
end

result += (counter[1] * 100)
result += (counter[5] * 50)
end

I implemented it in C#, but here is my solution.
http://github.com/downloads/JonKruger/solid-principles/SWE101%20Greed%20-%20Completed.zip

I took a different approach when writing the implementation code. I tried to make the code readable so that it would be really easy to figure out what it’s doing. For example, what happens when I get a change to the rules and I (or someone else) has to change my code? How easy is it to figure out what to change?

I tried to avoid complicated math formulas for this reason. Sure, you can make it work that way in less lines of code, but at the expense of readability.

I still have to try it in Ruby someday and see what I come up with! I’ll probably end up writing C# in Ruby like you said. 🙂

Whoa, delete the last comment, horrible formatting…

Instead of doing a hash with counts, I sorted the dice array and then took length-of-3 subarrays to see if I could find a three-some. On each subarray, I used the array.uniq trick that was suggested in the comments of your last post about the Triangles koan.

Here’s my code: http://pastie.org/1051380

FYI, you can replace line 42 with this:


dice_sort = Hash.new(0)

This will give each item in the hash a default value of 0, so that lines 46-48 will work properly.

Scott Coil says:

Here was my solution.

def score(dice)
# You need to write this method
points = 0
dice.sort!
while dice.size > 0 do
if dice[0] == dice[2]
if dice[0] == 1
points += 1000
dice.shift(3)
next
end
points += dice[0] * 100
dice.shift(3)
end
die = dice.shift
points += 50 if die == 5
points += 100 if die == 1
end
return points
end

Dale says:

This is my solution, curiously in some parts similar to some of the others give. I think it is quite clean, but I’m only learning so I’m open to criticisms and/or other suggestions.

http://pastebin.com/Aa9NbhDS

wildrhombus says:

This is how I did it. I’m new at ruby so I welcome any comments

if( dice.size > 5 )
return 0
end

result = 0;
10.times { |i|
count = dice.count(i)
if( count >= 3 )
if( i == 1 )
result += 1000
else
result += i*100
end
count -= 3
end

case i
when 1
result += count*100
when 5
result += count*50
end
}
return result

d2 says:

Several examples here won’t scale beyond 5 dice, but the scoring rules imply that 6 dice wake up the chance for 2000 points for 6 x 1’s, etc.

Modulo and integer division can fix the scoring easily in routines where they’re coded for bins/counts.

Doing this stretch further into the koan is a nice extra stretch. I’d actually come here to look for sandwich code or other prior-Koan lessons put to work, since I just resorted to old lame count-the-ones, count-the-twos flat code. Mine’s easily read, but has that ‘lots of similar lines’ smell to it, and wouldn’t scale well for a ‘greed’ game played with N-sided dice. … which’d be another good ‘extra’ stretch beyond the Koan.

Lukasz says:

This is my solution:

def score(dice)
result = 0
dice.uniq.each { |element|
result += element * 100 if dice.count(element) >= 3 and element != 1 and element != 5
(1..dice.count(element)).to_a.each{ |elem| result += elem % 3 == 0 ? 800:100} if element == 1
(1..dice.count(element)).to_a.each{ |elem| result += elem % 3 == 0 ? 400:50} if element == 5
}
result
end

Leave a Reply

Your email address will not be published. Required fields are marked *