Last week I ended with the following questions
- 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?
Today I'll pick start by picking a question and translating it into a test. I'll focus on the stock pile question.
#SpitPlayerRules def test_player_draws_when_hand_is_short @stock = [2] player = Player.new(self) player.hand = [3, 4, 5, 6] @pile_one = [7] @pile_two = [7] player.play assert @stock.empty?, "player did not draw as expected" endRemember that SpitPlayerRules is using the self shunt pattern so it is a double (think stunt double) for the still theoretical Game object. I've primed the Player under test with a hand that is one card short. Also I've assumed that somehow that Player will be able to draw from the @stock that the test/Game double has. In our implementation we need to consider how the Game communicates to the Player that the stock pile is empty. I ran that and it failed predictably.
Here is the old Player#play
def play
while(!@hand.empty?)
@game.spit_on_one(@hand.pop)
end
end
Should we draw before or after we spit? For now I'll say before, and implement the draw as quickly as I can
def play
while(!@hand.empty?)
if @hand.size < 5 and @game.draw?
@hand << @game.draw
end
@game.spit_on_one(@hand.pop)
end
end
Before this can run we need to implement the two methods draw and draw? on our Game double.
#SpitPlayerRules def draw @stock.pop end def draw? ! @stock.empty? endI'll just run the test and ... D'oh I need to make @stock an empty array in #test_run_without_draw to make it pass. Good that was fairly easy. Now on to the text test.
Wait! The TDD mantra is red (check), green (check), REFACTOR. Okay is there anything we can do to make the code simpler? What is Simple Code? There are several formulations of guidelines for simplicity in code here. The first guideline is "runs all tests", and we are good there. The next is "contains no duplication". While the Player class is acceptable. Its tests are not. The duplication is the set up the of the Player under test and the Game double's state. I'll factor that out into setup method.
#SpitPlayerRules def setup @player = Player.new(self) #Game double's state @pile_one = [7] @pile_two = [7] @stock = [] end def test_run_without_draw @player.hand = [ 2, 3, 4, 5, 6 ] @player.play assert_equal [7, 6, 5, 4, 3, 2] , @pile_one end def test_player_draws_when_hand_is_short @stock << 2 @player.hand = [3, 4, 5, 6] @player.play assert @stock.empty?, "player did not draw as expected" end
The third guideline is "expresses all the ideas you want to express". Looking at Player#play I think there are two ideas that are contained in that one method: draw and spit. I'll extract a method for each concept.
#Player
def play
while(!@hand.empty?)
draw
spit
end
end
def draw
if @hand.size < 5 and @game.draw?
@hand << @game.draw
end
end
def spit
@game.spit_on_one(@hand.pop)
end
The final guideline "minimizes classes and methods". It makes me think twice about the decision to extract #spit. I'm a little uncertain if the #spit method is pulling its weight. On the one hand it is only one line long. On the other hand it makes explicit the action in the card game and provides a place for any more advanced decision making at that point in the game. I think I'll keep it for now.
I'm going to stop here because I'm trying to spend only an hour on this entry. I think we've made some progress here. The interaction between Player and Game is starting to show up in the SpitPlayerRules class. We covered some guidelines for simple code and applied them. And we answered one of the questions we asked last week. So until next time I'll leave you with a listing of the current state of things.
#--------------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?)
draw
spit
end
end
def draw
if @hand.size < 5 and @game.draw?
@hand << @game.draw
end
end
def spit
@game.spit_on_one(@hand.pop)
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 setup
@player = Player.new(self)
#Game double's state
@pile_one = [7]
@pile_two = [7]
@stock = []
end
def test_run_without_draw
@player.hand = [ 2, 3, 4, 5, 6 ]
@player.play
assert_equal [7, 6, 5, 4, 3, 2] , @pile_one
end
def test_player_draws_when_hand_is_short
@stock << 2
@player.hand = [3, 4, 5, 6]
@player.play
assert @stock.empty?, "player did not draw as expected"
end
# game methods
def spit_on_one( card )
@pile_one << card
end
def draw
@stock.pop
end
def draw?
! @stock.empty?
end
end
0 comments:
Post a Comment