Archive for July, 2010

The Rock-Scissors-Paper OCP Dojo

Monday, July 12th, 2010

Last time at the Milano XP User Group we had our first retrospective. We gathered lots of useful insights. One of the selected actions was “if you propose a Kata, you must publish your solution“.

Guilty as charged: when I proposed Rock-Scissors-Paper for an evening of coding, we got an acute case of analysis-paralysis. It was a humbling evening: we didn’t manage to implement the first requirement, “rock beats scissors.” I provide here my solution. It’s brand new code, written today, so it benefits from the experience.

This is the list of requirements. I didn’t get it right immediately.

Context: we want to implement a gaming engine for the game rock-scissors-paper. We don’t care about players or rounds; we only want to implement the “beats” relation.

  1. – rock beats scissors
  2. – scissors does not beat rock
  3. – rock does not beat rock
  4. – scissors beats paper
  5. – paper beats rock

The clarifying statement should kill most of the discussion that we had that evening. We are not interested in modelling players or rounds or winning. Only the “beats” relations.

This kata was meant to be executed with the OCP Dojo rules. I will use Ruby today, because I want to take a break from Java. But the solution will not use any trick that can’t be done in Java. I promise :)

Rock beats scissors

This one is easy. That is, easy if you decide how to implement the “rock” and the “scissors”. I decided for the simplest thing, that is a Ruby “symbol”. In Java I would have used a string.

require "test/unit"

class Rsp
  def beats(first, second)
    true
  end
end

class TestRsp < Test::Unit::TestCase
  def setup
    @rsp = Rsp.new
  end

  def test_rock_beats_scissors
    assert @rsp.beats(:rock, :scissors)
  end
end  

Note that I have substituted the “factory” of the OCP rules with a simple setup method. The @-prefixed variable is Ruby’s way to define a member variable.

Scissors does not beat rock

This one almost escaped me at first. If your requirements only mention “X beats Y”, then return true will always be a valid implementation! Also, note that we don’t need to represent “ties”. Winning or tieing are not part of our requirements; but it should be clear that the “beats” relation can be used to determine victory or tie.

So this is the famous “second test” that in most OCP Dojos forces the creation of design. If it were not for the rules, I’d be tempted to write something as boring as

  return true if first.equals(:scissors) and second.equals(:rock)

inside the Rsp#beats method. Instead I decide I want to solve the problem with a rule-based style. Let’s delegate the decision of “beating” to a rule.

class Rule
  def initialize first, second
    @first, @second = first, second
  end

  def beats(first, second)
    @first == first and @second == second
  end
end

class Rsp
  def initialize(rule)
    @rule = rule
  end

  def beats(first, second)
    return true if @rule.beats(first, second)
    false
  end
end

class TestRsp < Test::Unit::TestCase

  def setup
    @rsp = Rsp.new Rule.new(:rock, :scissors)
  end

  def test_rock_beats_scissors
    assert @rsp.beats(:rock, :scissors)
  end

  def test_scissors_does_not_beat_rock
    assert_false @rsp.beats(:scissors, :rock)
  end
end    
  

Now if we wanted to be strict I’d have to show that I can make the first test pass and the second test fail by simply passing in a different Rule. I could do that by defining

class AlwaysTrueRule
  def beats(a, b); return true; end
end

This way if I pass an instance of AlwaysTrueRule, it will make the first test pass and the second fail. If I pass in the correct rule, both tests will pass.

Rock does not beat rock

Now this works with no modification needed to the code. From this point on, it will be clear that we don’t need to test all cases where “beats” does not hold. The implementation makes it obvious that only the cases that are explicitly covered by the rules pass.

Scissors beats paper

This forced me to change the single rule to a list of rules.

class Rsp
  def initialize(rules)
    @rules = rules
  end

  def beats(first, second)
    for rule in @rules
      return true if rule.beats(first, second)
    end
    false
  end
end

class TestRsp < Test::Unit::TestCase

  def setup
    @rsp = Rsp.new [
      Rule.new(:rock, :scissors), 
      Rule.new(:scissors, :paper),
      ]
  end
  
  # ...
  
  def test_scissors_beats_paper
    assert @rsp.beats(:scissors, :paper)
  end
end  

The rest

At this point the design is more or less complete. The remaining test, “paper beats rock” only needs to add a new Rule to the list. No “coding” needed, only configuration of existing objects.

The test of a design is how well it resists to changes. I would say that at this point, implementing Rock-scissors-paper-spock-lizard or even RSP-15 would be trivial.

I can imagine even wierder variations, for instance:

  • Peace beats anything other than peace
  • Random has a 0.5 chance of beating anything other than random
  • Thrice beats anything other than thrice on the third time it’s played

and I think that by writing a custom Rule, these could also be easily done. Therefore I think that the design so far is a success. The amazing thing is that I did all the coding in 1 pomodoro (25 minutes)! That includes taking notes and saving intermediate versions, for blogging about later.

Why did we take so long when we tried this at the coding dojo? I think that the trick is in defining the scope more precisely:

We don’t care about players or rounds; we only want to implement the “beats” relation.

By thinking of “beats” as a mathematical relation, that is, a subset of Stuff × Stuff for you mathematically inclined, we simplify our job. Also, giving up on the idea of defining a Rock class helps.