To dive right in… I’ve known for a long time that it is a good idea to write unit-tests before starting to code. In the AbT Linux project my buddy Eric has defined a lot of tests for the framework. Somehow I never gotten around to doing things the proper way… i.e., I never bothered to learn how to write these tests myself. Bad boy.
Today I started hacking at a little tool to generate Bridge hands. Not a big deal. My first worry was how to get a list of all cards that are available in the deck. My first thought was: let’s make a list of suits, a list of cards and then compute the carthesian product. Something like this:
[ruby]
suits = %w{clubs diamonds hearts spades}
vals = %w{A K Q J T 9 8 7 6 5 4 3 2}
suits.each{ |suit|
vals.each{ |val|
puts suit + ” ” + val
}
}
[/ruby]
This kind of works, but is hardly elegant. A co-worker pointed out that it is bad design… and I agreed. Anyway, a little lesson on OO-design followed. I should really brush up on my OO
He also offered to help me get started with unit-testing… The funny thing is that he’s kind of new to Ruby but, having years of Smalltalk experience helps in this case. Big time!
Anyway, we defined some basic tests and did some initial coding. We worked on this for about an hour or so… Part of the test-file looks like this (I expanded it a bit later on, but this gives a clear idea of how I got started)
[ruby]
# test whether a new hand has 0 cards
def test_new_hand
h = Hand.new()
assert_equal(0, h.cards.size, ‘zero cards in a new hand’)
end
# test whether:
# – a new deck has 52 cards
# – a new deck has 4 suits
# – each suit has 13 cards
def test_new_deck
d = Deck.new()
assert_equal(4, d.suits.size, ‘a new deck has 4 suits’)
assert_equal(52, d.cards.size, ‘a new deck has 4×13=52 cards’)
d.suits.each{ |s|
assert_equal(13, s.cards.size, ‘a new deck has 13 cards per suit’)
}
end
[/ruby]
When I first ran the testsuite I got a bunch of errors and failed assertions. The point is to just start hacking until they’re all gone
It took me a couple of hours but it seems to work for now. Here’s part of the code:
[ruby]
# =class Deck
#
# A deck of cards has 52 cards divіded over 4 suits. When setting up a new
# instance of this class the 4 suits are initialized after which the 13 cards
# are added to each suit. Methods for getting an list of cards, the suits
# (holding cards) or getting a random card are provided for. Finally the +deal+
# method is used to generate a bridge deal (note: may have to refactor this
# when catering for other types of games)
class Deck
# setup the four suits and add 13 cards to each suit
def initialize()
@suits = [Suit.new("clubs"), Suit.new("diamonds"), Suit.new("hearts"),
Suit.new("spades")]
@suits.each{ |suit|
%w{A K Q J T 9 8 7 6 5 4 3 2}.each{ |value|
suit << Card.new(suit.name, value)
}
}
end
# return all cards presently in the deck
def cards
return @suits.inject([]) { |list,suit| list += suit.cards }
end
# return all suits (which hold cards) presently in the deck
def suits
return @suits
end
# generate a bridge deal, consisting of 4 hands which contain cards
# consisting of 13 randomly selected cards from the deck. This only works if
# no cards have been removed from the deck yet.
def deal
if cards.size != 52
raise "not enough cards in the deck to create a deal"
exit
end
@hands = [Hand.new(), Hand.new(), Hand.new(), Hand.new()]
@hands.each{ |hand|
13.times{
hand << self.getRandomCard
}
}
return @hands
end
# get and remove a random card (if any) from the deck
def getRandomCard
cards = self.cards
card = cards.slice( rand( cards.size) )
self.removeCard(card)
return card
end
# remove a card from the deck
def removeCard(card)
suit = @suits.select{ |s| s.name == card.suit}[0]
suit.removeCard(card)
end
end
[/ruby]
The code needs some refactoring since 'suits' and 'hands' are both essentially collections of cards... I'll worry about that some other time. My first priority was to get 0 failsures:
bas@Librarian { ~/Work/bridgehands }$ ruby bridgeTests.rb
Loaded suite bridgeTests
Started
...
Finished in 0.005609 seconds.3 tests, 9 assertions, 0 failures, 0 errors
Since I started doing things in a more professional manner I decided to also include RDoc comments and put the entire thing in a subversion repository. Must say that (a) I’ve learned a lot today and (b) that I’m kind of proud





