In the last entry I wrote a pretty simple class, SpitPile. It is responsible for enforcing the rules for playing a card in the card game Spit.
I received some feedback from Jim Hughes via the TDD yahoogroup. Jim kindly pointed out a corollary to the TDD rule "Only write code to pass a failing test". His corollary is "Always delete production code when it won't break a test". Then he suggested a place in the SpitPile where I could apply this corollary. In SpitPile#in_sequence? I have this
def in_sequence?(card) (card != top_card) and ((card - top_card).abs == 1) endWhat I noticed, with Jim's guidance, is the logical duplication in that snippet. So I removed it and the tests still passed. Mmm, feel that green bar goodness.
Here is the code for SpitPile in its entirety
class SpitPile
def initialize( first_card )
@pile = [ first_card ]
end
def play( card )
if in_sequence?(card)
@pile << card
return true
end
false
end
def to_s
@pile.join(', ')
end
def top_card
@pile.last
end
def in_sequence?( card )
(card - top_card).abs == 1
end
endand the tests
class SpitPileRules < Test::Unit::TestCase
def setup
@pile = SpitPile.new( 2 )
end
def test_accepts_card_of_next_higher_rank
assert @pile.play(3)
assert_equal "2, 3", @pile.to_s
end
def test_accepts_card_of_next_lower_rank
assert @pile.play(1)
assert_equal "2, 1", @pile.to_s
end
def test_rejects_card_of_same_rank Okay I'm stuck. I can't c
assert !@pile.play(2)
assert_equal "2", @pile.to_s
end
def test_rejects_cards_of_non_consecutive_rank
assert @pile.play(3)
assert !@pile.play(1)
assert_equal "2, 3", @pile.to_s
end
end
Onward. I think I'm going to work on a Game object to manage the flow of pay. Spit is not turn based so I'm thinking I might have to do some threading magic. That is notoriously difficult to test, but I'm not sure I'll need it yet.
Start with a test. Okay, I'm stuck. I can't think of a simple test for Game without going immediately to a Player, but I did come up with this test for Player
class SpitPlayerRules < Test::Unit::TestCase
def test_run_without_draw
player = Player.new(self)
player.hand = [ 2, 3, 4, 5, 6 ]
@pile_one = [7]
@pile_two = [7]
player.play
assert_equal [7, 6, 5, 4, 3, 2] , @pile_one
end
end
This test assumes that player will modify SpitPlayerRules@pile_one which begs the question, how? This makes me think about the interaction between Player and Game. I don't have a Game object yet, but Ruby's 'duck' typing allows me to pass the test class to the Player and have it respond to the messages that Game should respond to. Incidentally this is known as the Self-Shunt pattern. I often start with this and later refactor to use a real or mock version of the object.
What should it respond to? It seems to me that Player only needs to be able to 'spit' on a pile to pass this test. First I'll, wait, always run the test first to watch it fail. Okay it threw a NameError which means I haven't defined a Player class yet. Next it'll need a constructor with one parameter, an accessor for @hand, and a method 'play'. With that in place
class Player attr_accessor :hand def initialize( game ) end def play end endthe test is now failing as expected.
I'm thinking the simplest thing that could possibly work is
def initialize( game )
@game = game
end
def play
while(!@hand.empty?)
@game.spit_on_one(@hand.pop)
end
end
I'm assuming that game has a method 'spit_on_one'that takes a card. I'll make the SpitPlayerRules class do that for now.
def spit_on_one( card ) @pile_one << card endI'll run the tests. Green bar.
Now I can think of a few more tests that would help flesh out the interface between Game and Player.
- What happens when player makes an invalid move?
- How does Player draw from its stock pile?
- How does Player get notified of the end of game play?
- Does player notify Game when its out of cards?
#--------------------------Spit.rb
class SpitPile
def initialize( first_card )
@pile = [ first_card ]
end
def play( card )
if in_sequence?(card)
@pile << card
return true
end
false
end
def to_s
@pile.join(', ')
end
def top_card
@pile.last
end
def in_sequence?( card )
(card - top_card).abs == 1
end
end
class Player
attr_accessor :hand
def initialize( game )
@game = game
end
def play
while(!@hand.empty?)
@game.spit_on_one(@hand.pop)
end
end
end
#-----------------SpitTest.rb
require 'test/unit'
require 'spit'
class SpitPileRules < Test::Unit::TestCase
def setup
@pile = SpitPile.new( 2 )
end
def test_accepts_card_of_next_higher_rank
assert @pile.play(3)
assert_equal "2, 3", @pile.to_s
end
def test_accepts_card_of_next_lower_rank
assert @pile.play(1)
assert_equal "2, 1", @pile.to_s
end
def test_rejects_card_of_same_rank
assert !@pile.play(2)
assert_equal "2", @pile.to_s
end
def test_rejects_cards_of_non_consecutive_rank
assert @pile.play(3)
assert !@pile.play(1)
assert_equal "2, 3", @pile.to_s
end
end
class SpitPlayerRules < Test::Unit::TestCase
def test_run_without_draw
player = Player.new(self)
player.hand = [ 2, 3, 4, 5, 6 ]
@pile_one = [7]
@pile_two = [7]
player.play
assert_equal [7, 6, 5, 4, 3, 2] , @pile_one
end
# game methods
def spit_on_one( card )
@pile_one << card
end
end
0 comments:
Post a Comment