7
\$\begingroup\$

The Greed game, from Ruby Koans, where you roll up to 5 dice:

A set of three ones is 1000 points. A set of three numbers (other than ones) is worth 100 times the number.

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.

I broke my solution into four methods.

def score(dice)
 (1..6).reduce(0) { |sum, n| sum + points(dice, n) }
end
def points(dice, num)
 ((dice.count(num) / 3) * triple(num)) + ((dice.count(num) % 3) * bonus(num))
end
def triple(num)
 num == 1 ? 1000 : (num * 100)
end
def bonus(num)
 [1, 5].include?(num) ? 50 + (50 * (num % 5)) : 0
end

I suspect there's a way to avoid having to pass the dice array into points(), but I can't figure it out. Maybe using a yield somewhere?

asked Apr 26, 2014 at 6:43
\$\endgroup\$

1 Answer 1

6
\$\begingroup\$

I think your code is very nice, succinct, and ruby-like.

Two minor issues -

Your bonus code is a bit over-sophisticated, which makes it not very readable, and quite brittle. Although it won't fit in one line - a case solution will be more suitable here:

def bonus(num)
 case num
 when 1 then 100;
 when 5 then 50;
 else 0;
 end
end

Also, in score I personally think that separating the score calculation from its aggregation is more easy on the eyes:

(1..6).map { |n| points(dice, n) }.inject(:+)

although, I admit, it is more a matter of taste than anything else...


As for your suspicion about not having to pass dice to points, I don't see a straight-forward way of doing that, or a proper motivation for doing it.

Perhaps you want to be able to simplify your block into a straight forward method call (using a syntax like .map(&some_method)) - for that you can use proc's curry method, but it actually results in a very awkward code, which does not seem to be worth it:

def score(dice)
 (1..6).map(&lambda(&method(:points)).curry[dice]).inject(:+)
end

Perhaps you want to decouple the point calculation methods from the dice array altogether. This can be done easily enough by passing dice.count(n) instead of dice, as that is all the points actually cares about, although this solution will break as soon as point calculation will need more information...

def score(dice)
 (1..6).reduce(0) { |sum, n| sum + points(n, dice.count(n)) }
end
def points(num, count)
 ((count / 3) * triple(num)) + ((count % 3) * bonus(num))
end
answered Apr 26, 2014 at 7:57
\$\endgroup\$
1
  • \$\begingroup\$ I agree on all counts! When I figured out a way to implement bonus() with modulus in a one-liner, I couldn't help myself. The case statement definitely makes more sense though. Thanks for the review. \$\endgroup\$ Commented Apr 26, 2014 at 13:59

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.