5. Learn BDD Playing Dice!
YOUR BEHAVIOR IS HAVE FUN
First Edition
by Valério Farias de Carvalho
6. by Valério Farias de Carvalho. This document is licensed under Creative Commons 3.0 Attribution License.
First edition: July 2010
Valério Farias
http://www.valeriofarias.com
Twitter: @valeriofarias
Cover image from http://www.flickr.com/photos/missturner/
7. Learn BDD Playing Dice!
INTRODUCTION
Hello Ruby enthusiast! Hello BDD enthusiast! Welcome to this journey line by line, test by test, using the
Behaviour Driven Development technique in a interesting project: War Dice Simulation!. I wrote this tiny book
to learn BDD and RSpec for myself. I would want to start with a little example. And if was possible with a
funny example too. Then I thought: Why not playing dice! Why not playing war dice! Then there you are:
Learning BDD playing dice!
The app that we'll create together uses two classes: Dice and WarDice. The first chapter I start to construct
the RSpec file of Dice class and the Dice class simultaneously and step by step until all of the tests become
green.
The second chapter I go on with the WarDice class, that is a simulation of each dice of war game!
Finally, the third chapter I use the classes in a funny shoes application.
The philosophy of this book is to learn making funny things. In one word: Experimentation. Then I hope you
enjoy this simple but also instructive book.
The tests used in this book aren't a silver bullet. They are only a way between some others. How I told, I
made to learn RSpec. You can send suggests, clone the project, modify it and codify in other ways. Who
knows we'll playing together in the 2.0 version of this book :).
The complete source code of application you can download in https://github.com/valeriofarias/shoes-war-
dice/tree
7
8. Learn BDD Playing Dice! - Your behavior is have fun
Now, just read, codify, test, share and have fun!
8
9. Chapter 1: Making a Dice class using BDD
Chapter 1
Making a Dice class using BDD
GETTING STARTED
Before start to programming let's install the essential packages. First install the rspec:
gem install rspec
If you want more usability, install the package zentest:
gem install ZenTest
Now install the package autotest-notification. This gem set the autotest (ZenTest) to send messages to
software as Growl, LibNotify, and Snarl, displaying a window with the results. Read the readme file of the
project to know how to install: http://github.com/carlosbrando/autotest-notification/. With this three gems, our
journey become funny!
9
10. Learn BDD Playing Dice! - Your behavior is have fun
To complete the bag, go to the shoes application page and read how to install in your operating system. Yes,
shoes is very fun! We'll end our play with it. Access http://github.com/shoes/shoes/downloads or
http://github.com/shoes/shoes. To learn how to use and execute shoes read the ebook Nobody knows Shoes
available in http://github.com/shoes/shoes/downloads.
Now let's start our travel test by test in the BDD world!
CREATING THE RSPEC FILE
First create the dice folder and the lib and spec folders inside it. Then you have to create the file dice_spec.rb
inside the spec folder. Well! What you waiting! Let's write the first requirement in that file. Now we start to
play!
require "rubygems"
require "spec"
require "lib/dice"
describe Dice do
it "should have numbers 1 to 6"
end
To execute this test open the terminal end enter in the dice folder and use the test command:
cd dice
spec spec/dice_spec
/
In our example with autotest, just digit the next command:
10
11. Chapter 1: Making a Dice class using BDD
autospec
Now the test execute automatically every time that the files was saved. Come back to our example. The
output of this first test broke because we need create the Dice class in the lib/dice.rb file.
spec --autospec spec/dice_spec.rb
/ rb
./spec/dice_spec.rb
/ / rb:6: uninitialized constant Dice (NameError)
WRITE THE DICE CLASS
To fix the initial error, just write the Dice class:
class Dice
end
Output: show the pending requirement.
Pending:
Dice should have only numbers 1 to 6 (Not Yet Implemented)
./spec/dice_spec.rb
/ / rb:7
Finished in 0.04099 seconds
1 example, 0 failures, 1 pending
THE NUMBERS IN THE DICE
The first requirement is to delimit the numbers of dice: 1 to 6:
11
12. Learn BDD Playing Dice! - Your behavior is have fun
describe Dice do
it "should have only numbers 1 to 6" do
dice = Dice new
Dice.new
(1..6).should include dice.play )
should include( play
end
end
Output:
F
1)
NoMethodError in 'Dice should have only numbers 1 to 6'
undefined method `play' for #<Dice:0xb7b6986c>
./spec/dice_spec.rb:9:
Finished in 0.073369 seconds
1 example, 1 failure
CREATE THE PLAY METHOD IN DICE.RB
To fix the previous failure let's write the play method:
class Dice
def play
end
end
Output: still failure because the play method returns nil.
12
13. Chapter 1: Making a Dice class using BDD
F
1)
'Dice should have only numbers 1 to 6' FAILED
expected 1..6 to include nil
./spec/dice_spec.rb
/ / rb:9:
Finished in 0.031104 seconds
1 example, 1 failure
NUMBER OUTSIDE THE RANGE 1..6
Let's experiment to put a number outside of the range 1..6 to see what happens:
class Dice
def play
10
end
end
Output: the test still failure because the number is outside the range.
F
1)
'Dice should have numbers 1 to 6' FAILED
expected 1..6 to include 10
./spec/dice_spec.rb
/ / rb:9:
Finished in 0.03021 seconds
13
14. Learn BDD Playing Dice! - Your behavior is have fun
1 example, 1 failure
NUMBER INSIDE THE RANGE 1..6
Now, let's put a number between 1-6, and finally the Dice class passes the test.
class Dice
def play
6
end
end
Output:
.
Finished in 0.027648 seconds
1 example, 0 failures
RANDOM NUMBERS
For while it's ok. Now, let's work with the random numbers requirement. I'll put also some requirements that I
remember, but not too much. I'll put x in the beginning of 'it' statement to rspec ignore then. this is just a trick
;).
require "rubygems"
require "spec"
14
15. Chapter 1: Making a Dice class using BDD
require "lib/dice"
describe Dice do
it "should have only numbers 1 to 6" do
dice = Dice new
Dice.new
(1..6).should include dice.play )
should include( play
end
# Three rand groups of 1000 numbers must be different each other
it "should show the numbers randomly" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play
collect{ play }
group2 = (1..1_000).collect dice.play
collect{ play }
group3 = (1..1_000).collect dice.play
collect{ play }
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
xit "should store the last number after the dice was played."
xit "should play the dice when dice object was initialized"
end
Output:
.F
1)
'Dice should show the numbers randomly' FAILED
expected false got true
false,
./spec/dice_spec.rb
/ / rb:18:
Finished in 0.093321 seconds
15
16. Learn BDD Playing Dice! - Your behavior is have fun
2 examples, 1 failure
GENERATE RANDOM NUMBER AND REFACTORY
Now I have to generate random numbers and also have to refactory the first requirement that limit the
numbers 1 to 6. This requirement must be tested with a variety of numbers instead of only one how it's right
now. To play a little bit with ruby, I'll modify also the require code to simplify the number of lines.
class Dice
def play
rand
rand(6)
end
end
%w{ rubygems spec lib/dice }.each {|lib| require lib}
each
describe Dice do
it "should show only numbers 1 to 6" do
dice = Dice new
Dice.new
group = (1..1_000).collect dice.play }.join
collect{ play join
group.should_not be_nil
should_not
group.should_not be_empty
should_not
should_not include('-') # Negative numbers aren't permitted
group.should_not include
group.should_not include
should_not include('0')
group.should_not include
should_not include('7')
group.should_not include
should_not include('8')
group.should_not include
should_not include('9')
end
16
17. Chapter 1: Making a Dice class using BDD
# Three rand groups of 1000 numbers must be different each other
it "should show the numbers randomly" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play
collect{ play }
group2 = (1..1_000).collect dice.play
collect{ play }
group3 = (1..1_000).collect dice.play
collect{ play }
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
xit "should store the last number after the dice was played."
xit "should play the dice when dice object was initialized"
end
Output: rand(6) generate also zeros, the the test failure.
F.
1)
'Dice should show only numbers 1 to 6' FAILED
expected "4025132222510540002142312555235104432520522022430445143425254
51533145101530430012510120055232441422435123332040350424441302405340420
50050324205500223120330524430331015422350203015044053545205524012055023
10100333140520435320541010244153022003403143022550340451124322335450431
5335402445045511" not to include "0"
./spec/dice_spec.rb
/ / rb:10:
Finished in 0.046589 seconds
2 examples, 1 failure
17
18. Learn BDD Playing Dice! - Your behavior is have fun
FIXING THE RANDOM NUMBER FAILURE
Finally let's put the command rand(6) + 1 to limit the range 1 to 6 and let's refactore the requirement of the
range to use regexp. Now the test pass :).
class Dice
def play
rand
rand(6) + 1
end
end
%w{ rubygems spec lib/dice }.each {|lib| require lib}
each
describe Dice do
it "should have only numbers 1 to 6" do
dice = Dice new
Dice.new
group = (1..1_000).collect dice.play }.join
collect{ play join
group.should match
should match(/^[1-6]*[1-6]$/)
end
# Three rand groups of 1000 numbers must be different each other
it "should show the numbers randomly" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play
collect{ play }
group2 = (1..1_000).collect dice.play
collect{ play }
group3 = (1..1_000).collect dice.play
collect{ play }
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
xit "should store the last number after the dice was played."
18
19. Chapter 1: Making a Dice class using BDD
xit "should play the dice when dice object was initialized"
end
STORE THE DICE NUMBER
Let's work in the next requirement: "should store the last number after the dice was played". I'll create the
method show_number and I'll put a constant to test pass.
%w{rubygems spec lib/dice}.each {|lib| require lib}
each
describe Dice do
it "should have only numbers 1 to 6" do
dice = Dice new
Dice.new
group = (1..1_000).collect dice.play }.join
collect{ play join
group.should match
should match(/^[1-6]*[1-6]$/)
end
# Three groups of 1000 random numbers must be different each other
it "should show the numbers randomly" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play }
collect{ play
group2 = (1..1_000).collect dice.play }
collect{ play
group3 = (1..1_000).collect dice.play }
collect{ play
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
it "should store the last number after the dice was played." do
dice = Dice new
Dice.new
dice.play
play
19
20. Learn BDD Playing Dice! - Your behavior is have fun
dice.show_number to_s.should match
show_number.to_s should match(/^[1-6]*[1-6]$/)
end
xit "should play the dice when dice object was initialized."
end
class Dice
def play
rand
rand(6) + 1
end
def show_number
3
end
end
CHANGING CONSTANTS BY VARIABLES
This is an important rule in BDD. Now you can change the constant by a instance variable in the dice class.
The test'll pass.
class Dice
def play
@number = rand
rand(6) + 1
end
def show_number
@number
end
end
20
21. Chapter 1: Making a Dice class using BDD
PLAY THE DICE WHEN THE CLASS WAS INITIALIZED
Now I want that the dice play when it's initializing. The next test'll break.
%w{rubygems spec lib/dice}.each {|lib| require lib}
each
describe Dice do
it "should have only numbers 1 to 6" do
dice = Dice new
Dice.new
group = (1..1_000).collect dice.play }.join
collect{ play join
group.should match
should match(/^[1-6]*[1-6]$/)
end
# Three groups of 1000 random numbers must be different each other
it "should show the numbers randomly" do
dice = Dice new
Dice.new
group1 = (1..1_000).collect dice.play }
collect{ play
group2 = (1..1_000).collect dice.play }
collect{ play
group3 = (1..1_000).collect dice.play }
collect{ play
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
(group2 == group3).should be_false
should
end
it "should store the last number after the dice was played." do
dice = Dice new
Dice.new
dice.play
play
dice.show_number to_s.should match
show_number.to_s should match(/^[1-6]*[1-6]$/)
end
it "should play the dice when dice object was initialized." do
Dice new.show_number to_s.should match
Dice.new show_number.to_s should match(/^[1-6]*[1-6]$/)
21
22. Learn BDD Playing Dice! - Your behavior is have fun
end
end
Output:
...F
1)
'Dice should play the dice when dice object was initialized.' FAILED
expected "" to match /^[1-6]*[1-6]$/
./spec/dice_spec.rb
/ / rb:29:
Finished in 0.12371 seconds
4 examples, 1 failure
USING THE INITIALIZE METHOD
Now I finalize the dice class putting the initialize method with a message for the play method. The test
pass.
class Dice
def initialize
play
end
def play
@number = rand
rand(6) + 1
end
22
23. Chapter 1: Making a Dice class using BDD
def show_number
@number
end
end
LET'S REFACTORY
Now I also can make a little refactory in the dice_spec.rb. I'll put a block before(:each) to simplify the code. I'll
make another refactory in the third requirement: "should store the last number after the dice was played".
Now he works with a lot of numbers. The logic is if the last number is stored then two groups of 100 numbers
stored are different each other.
%w{rubygems spec lib/dice}.each {|lib| require lib}
each
describe Dice do
before :each do
before(:each)
@dice = Dice new
Dice.new
end
it "should have only numbers 1 to 6" do
group = (1..1_000).collect @dice.play }.join
collect{ play join
group.should match
should match(/^[1-6]*[1-6]$/)
end
# Three groups of 1000 random numbers must be different each other
it "should show the numbers randomly" do
group1 = (1..1_000).collect @dice.play }
collect{ play
group2 = (1..1_000).collect @dice.play }
collect{ play
group3 = (1..1_000).collect @dice.play }
collect{ play
(group1 == group2).should be_false
should
(group1 == group3).should be_false
should
23
24. Learn BDD Playing Dice! - Your behavior is have fun
(group2 == group3).should be_false
should
end
it "should store the last number after the dice was played." do
group1 = (1..100).collect do
collect
@dice.play
play
@dice.show_number
show_number
end
group2 = (1..100).collect do
collect
@dice.play
play
@dice.show_number
show_number
end
(group1 == group2).should be_false
should
end
it "should play the dice when dice object was initialized." do
@dice.show_number to_s.should match
show_number.to_s should match(/^[1-6]*[1-6]$/)
end
end
PLAY WITH THE DICE CLASS
Now that the class is finished. Let's play with it in the irb!
>> require 'dice'
=> true
>> dice = Dice new
Dice.new
=> #<Dice:0xb7a64188 @number=5>
>> dice.show_number
show_number
=> 5
24
25. Chapter 1: Making a Dice class using BDD
>> dice.class
class
=> Dice
Using the dice only one time and abandon it
>> Dice new.show_number
Dice.new show_number
=> 2
Play the dice a lot of times:
>> 20.times print dice.play }
times{ play
21636456135236136236=> 20
Three dice:
>> yellowdice = [Dice new, Dice new, Dice new]
Dice.new Dice.new Dice.new
=> [#<Dice:0xb7a3e3d4 @number=3>, #<Dice:0xb7a3e3ac @number=5>, #<Dice:0xb7a3e384 @number=5>]
>> yellowdice.each |dice| puts dice.show_number }
each{ show_number
3
5
5
=> [#<Dice:0xb7a3e3d4 @number=3>, #<Dice:0xb7a3e3ac @number=5>, #<Dice:0xb7a3e384 @number=5>]
Play again the same dice
>> yellowdice.each |dice| puts dice.play }
each{ play
5
2
5
=> [#<Dice:0xb7a3e3d4 @number=5>, #<Dice:0xb7a3e3ac @number=2>, #<Dice:0xb7a3e384 @number=5>]
What's the bigger value of the three dice of last play?
25
26. Learn BDD Playing Dice! - Your behavior is have fun
>> puts yellowdice.collect |item| item.show_number }.max
collect{ show_number max
5
=> nil
And about the less value?
>> puts yellowdice.collect |item| item.show_number }.min
collect{ show_number min
2
=> nil
Wait. The last example I used 3 dice?!? Ahaaa! This looks like the dice of war game. Let's create the war
game dice class in the next chapter.
26
27. Chapter 2: Reproducing the War Game Dice
Chapter 2
Reproducing the War Game Dice
DESCRIBING THE WAR GAME
Now I finally finished the Dice class! But I want something more excitant! I want reproduce in a ruby class the
dice of war game. That's right! That 6 dice. 3 red and 3 yellow that represent attack and defence respectively.
I'll use two arrays: reddice and yellowdice to store the dice values. I remember now that the use of the dice
depend on the army number of attacker and defender. But in this experiment I'll think only in the handling of
dice in the game. Then the dice can be handle using 1, 2 or 3 red dice versus 1, 2 or 3 yellow dice. I'll have to
compare the bigger red value with the bigger yellow value and consequently the next values using this logic
(from bigger to less values). In draw case the yellow win. The red only win when the number was bigger then
the yellow.
Well, I think that's it. Let's work now!
27
28. Learn BDD Playing Dice! - Your behavior is have fun
TESTING ARRAY COMPARISON
Well, I remembered that we need to use array comparison in the dice of war game, but In my first test with
array comparison I had problems. You can see this problem in the next test:
describe Array do
it "should be comparable" do
([8,9,10] > [1,2,3]).should be_true
should
end
end
Output:
....F
1)
NoMethodError in 'Array should be comparable'
undefined method `>' for [8, 9, 10]:Array
./spec/dice_spec.rb:43:
Finished in 0.039464 seconds
5 examples, 1 failure
INCLUDE COMPARABLE MODULE
To solve the previous issue, just include the Comparable module in the dice.rb file. I'll put in a simplify form
purposely to play with ruby possibilities :).
class Array; include Comparable; end
28
29. Chapter 2: Reproducing the War Game Dice
Now the test pass.
NUMBER OF DICE
Now I'll work on the first requirement: The attack and defence should use 1, 2 or 3 dice. I'll put intentionally
a number different of 1, 2, 3 to cause an error.
describe Wardice do
it "The attack and defence should use 1, 2 or 3 dice" do
wardice = Wardice new(0, 3)
Wardice.new
wardice.red to_s.should match
red.to_s should match(/^[1-3]$/)
wardice.yellow to_s.should match
yellow.to_s should match(/^[1-3]$/)
end
it "Should compare from bigger to less values and save in array result"
end
class Wardice
attr_reader :red :yellow
red,
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
end
end
Output:
'Wardice The attack and defence should use 1, 2 or 3 dice' FAILED
expected "0" to match /^[1-3]$/
./spec/dice_spec.rb
/ / rb:50:
29
30. Learn BDD Playing Dice! - Your behavior is have fun
Finished in 0.032203 seconds
8 examples, 1 failure, 2 pending
SOLVING THE NUMBER ISSUE
If the class was initialized with numbers differents of the range (1..3) then the variables will be filled with a
rand number between the range. I'll make also a little refactory in the spec file to put a situation that a number
of dice is correct. You can see below the solution.
class WarDice
attr_reader :red :yellow
red,
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
@red = rand
rand(3) + 1 if @red.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@yellow = rand
rand(3) + 1 if @yellow.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
end
end
describe WarDice do
it "The attack and defence should use 1, 2 or 3 dice" do
wardice = Wardice new(0, 7)
Wardice.new
wardice.red to_s.should match
red.to_s should match(/^[1-3]$/)
wardice.yellow to_s.should match
yellow.to_s should match(/^[1-3]$/)
wardice2 = Wardice new(2, 3)
Wardice.new
wardice2.red should == 2
red.should
wardice2.yellow should == 3
yellow.should
end
30
31. Chapter 2: Reproducing the War Game Dice
it "Should compare from bigger to less values and save in array result"
end
DECREASE SORT ARRAY
This is a important requirement, but I remembered only now: wardice Should provide yellow and red dice
results with an array in decreasing sort. You can see the solution below:
describe WarDice do
it "The attack and defence should use 1, 2 or 3 dice" do
wardice = WarDice new(0, 7)
WarDice.new
wardice.red to_s.should match
red.to_s should match(/^[1-3]$/)
wardice.yellow to_s.should match
yellow.to_s should match(/^[1-3]$/)
wardice2 = WarDice new(2, 3)
WarDice.new
wardice2.red should == 2
red.should
wardice2.yellow should == 3
yellow.should
end
it "Should provide yellow and red dice results with an array in decreasing sort" do
wardice = Wardice new(3, 3)
Wardice.new
wardice.reddice is_a?(Array).should be_true
reddice.is_a? should
wardice.yellowdice is_a?(Array).should be_true
yellowdice.is_a? should
wardice.reddice sort{|x, y| y <=> x }.should == wardice.reddice
reddice.sort should reddice
wardice.yellowdice sort{|x, y| y <=> x }.should == wardice.yellowdice
yellowdice.sort should yellowdice
end
it "Should compare from bigger to less values and save in array result"
end
31
32. Learn BDD Playing Dice! - Your behavior is have fun
class wardice
attr_reader :red :yellow :reddice :yellowdice
red, yellow, reddice,
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
@red = rand
rand(3) + 1 if @red.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@yellow = rand
rand(3) + 1 if @yellow.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@reddice = []
@yellowdice = []
@dice = Dice new
Dice.new
@red.times
times{|row| @reddice[row] = [@dice.play }
play]
@yellow.times |row| @yellowdice[row] = [@dice.play }
times{ play]
end
end
Output:
'wardice Should provide yellow and red dice results with an array in decreasing sort' FAILED
expected: [[5], [2], [4]],
got: [[5], [4], [2]] (using ==
==)
./spec/dice_spec.rb
/ / rb:63:
Finished in 0.035218 seconds
9 examples, 1 failure, 2 pending
32
33. Chapter 2: Reproducing the War Game Dice
SOLVING SORTING ARRAY ISSUE
class WarDice
attr_reader :red :yellow :reddice :yellowdice
red, yellow, reddice,
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
@red = rand
rand(3) + 1 if @red.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@yellow = rand
rand(3) + 1 if @yellow.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@reddice = []
@yellowdice = []
@dice = Dice new
Dice.new
@red.times
times{|row| @reddice[row] = [@dice.play }
play]
@yellow.times |row| @yellowdice[row] = [@dice.play }
times{ play]
@reddice.sort!
sort!{|x,y| y <=> x }
@yellowdice.sort!
sort!{|x,y| y <=> x }
end
end
Now the test pass.
COMPARING VALUES
Let's work now in the last requirement: Should compare from bigger to less values and save in array
result
describe Wardice do
it "The attack and defence should use 1, 2 or 3 dice" do
33
34. Learn BDD Playing Dice! - Your behavior is have fun
wardice = Wardice new(0, 7)
Wardice.new
wardice.red to_s.should match
red.to_s should match(/^[1-3]$/)
wardice.yellow to_s.should match
yellow.to_s should match(/^[1-3]$/)
wardice2 = Wardice new(2, 3)
Wardice.new
wardice2.red should == 2
red.should
wardice2.yellow should == 3
yellow.should
end
it "Should provide yellow and red dice results with an array in decreasing sort" do
wardice = Wardice new(3, 3)
Wardice.new
wardice.reddice is_a?(Array).should be_true
reddice.is_a? should
wardice.yellowdice is_a?(Array).should be_true
yellowdice.is_a? should
wardice.reddice sort{|x, y| y <=> x }.should == wardice.reddice
reddice.sort should reddice
wardice.yellowdice sort{|x, y| y <=> x }.should == wardice.yellowdice
yellowdice.sort should yellowdice
end
it "Should compare from bigger to less values and save in array result" do
wardice = Wardice new(3, 2)
Wardice.new
wardice.reddice first.should > wardice.reddice last
reddice.first should reddice.last
wardice.attack
attack
wardice.result
result[0].should == "Red Win" if wardice.reddice
should reddice[0] > wardice.yellowdice
yellowdice[0]
wardice.result
result[0].should == "Yellow Win" if wardice.reddice
should reddice[0] <= wardice.yellowdice
yellowdice[0]
end
end
The final WarDice class:
class Wardice
attr_reader :red :yellow :reddice :yellowdice :result
red, yellow, reddice, yellowdice,
34
35. Chapter 2: Reproducing the War Game Dice
def initialize red, yellow)
initialize(
@red, @yellow = red, yellow
@red = rand
rand(3) + 1 if @red.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@yellow = rand
rand(3) + 1 if @yellow.to_s grep(/^[1-3]$/).empty?
to_s.grep empty?
@reddice = []
@yellowdice = []
@result = []
@dice = Dice new
Dice.new
@red.times
times{|row| @reddice[row] = [@dice.play }
play]
@yellow.times |row| @yellowdice[row] = [@dice.play }
times{ play]
@reddice.sort!
sort!{|x, y| y <=> x }
@yellowdice.sort!
sort!{|x, y| y <=> x }
end
def attack
@reddice.each_with_index do |item, index|
each_with_index
next if @yellowdice[index].nil?
nil?
reddice = item
yellowdice = @yellowdice[index]
if reddice > yellowdice
@result << "Red Win"
else
@result << "Yellow Win"
end
end
end
end
35
36. Learn BDD Playing Dice! - Your behavior is have fun
PLAY WITH THE WARDICE CLASS
Open irb and write the next sequence:
>> require 'dice'
=> true
Initialize the WarDice class. The first parameter is the number of red dice and the second is the number of
yellow dice:
>> wardice = WarDice new(2, 3)
WarDice.new
=> #<WarDice:0xb7b0b410 @yellowdice=[[6], [1], [1]], @reddice=[[3], [1]], @dice=#<Dice:0xb7b0ae0c
@number=6>, yellow3, result["Yellow Win", "Yellow Win"], red2
= >
Show numbers in yellow and red dice:
>> puts wardice.reddice
reddice
3
1
=> nil
>> puts wardice.yellowdice
yellowdice
6
1
1
=> nil
Show result:
>> puts wardice.result
result
Yellow Win
Yellow Win
=> nil
36
37. Chapter 2: Reproducing the War Game Dice
All output in one command:
>> war.reddice each_with_index{ |item, index| puts "Red:#{item} Yellow:#{war.yellowdice
reddice.each_with_index yellowdice[index]}
- #{war.result
result[index]}"}
Red:3 Yellow:6 - Yellow Win
Red:1 Yellow:1 - Yellow Win
=> [[3], [1]]
Now we have the tools to play. Then the next chapter we'll use this classes to make a nice graphic
application using Shoes.
37
38. Learn BDD Playing Dice! - Your behavior is have fun
Chapter 3
Playing dice with shoes
A LITTLE TEST WITH DICE CLASS AND SHOES
Now let's create a graphic interface using Shoes that will use the class WarDice. But first let's' make a simple
test with the class Dice. You have to create a new file diceshoes.rb, then paste the code below inside it and
save the file at the same directory of dice.rb file. Finally, execute it with shoes to see the result.
Observe that I just included the dice.rb file with a require in the beginning of the code below:
require 'dice'
Shoes app :title => "Test with dice class", :width => 500, :height => 500 do
Shoes.app
background aliceblue
para "Welcome! This is an example using Dice class.", :weight => "bold"
dice = Dice new
Dice.new
38
39. Chapter 3: Playing dice with shoes
# Print dice numbers with random colors
1_000.times do
times
r, g, b = rand, rand, rand
para dice.play :stroke => rgb **3, g**
play, rgb(r** **3, b**
**3), :size => "large"
end
end
Output:
THE WAR DICE SHOES CODE
Finally the last application. Copy the following code in a new file and save with the name wardiceshoes.rb in
the lib folder. I put some remarks across the code to facilitate the understanding. Have a nice experiment.
39
40. Learn BDD Playing Dice! - Your behavior is have fun
require 'dice'
Shoes app :title => "Dice of War game", :width => 500, :height => 500 do
Shoes.app
background gradient black, teal )
gradient(
# List with number of red dice: 1, 2 or 3
para "Red", :stroke => tomato
@numberreddice = list_box :items => ["1", "2", "3"],
:width => 70, :choose => "3" do |list|
end
para " X ", :stroke => snow
# List with number of yellow dice: 1, 2 or 3
@numberyellowdice = list_box :items => ["1", "2", "3"],
:width => 70, :choose => "3" do |list|
end
para "Yellow", :stroke => yellow
# Define an aleatory position
@a = @b = @c = []
(40..200).step
step(10){ |x| @a << x }
(230..450).step
step(10){ |y| @b << y }
(80..450).step
step(10){ |z| @c << z }
|
# Variables that will store values of wardice object
@reddice = @yellowdice = @resulttext = []
button "Attack", :width => 80 do
# Clear the screen
@dice.each |d| d.remove }
each{ remove
@resulttext.each |a| a.remove }
each{ remove
40
41. Chapter 3: Playing dice with shoes
# The wardice object is initializing
wardice = WarDice new( @numberreddice.text to_i, @numberyellowdice.text to_i )
WarDice.new text.to_i text.to_i
@reddice = wardice.reddice
reddice
@yellowdice = wardice.yellowdice
yellowdice
@result = wardice.result
result
# Every dice is drawn in random position
@reddice.each |item| draw @a[rand
each{ draw( rand(@a.length
length)], @c[rand
rand(@c.length
length)], item.to_s to_i, 1, true ) }
to_s.to_i
@yellowdice.each |item| draw @b[rand
each{ draw( rand(@b.length
length)], @c[rand
rand(@c.length
length)], item.to_s to_i, 2, true )}
to_s.to_i
end
button "Verify", :width => 80 do
# Clear the screen
@dice.each |d| d.remove }
each{ remove
@resulttext.each |a| a.remove }
each{ remove
# Initial position of dice
leftyellow = 250
leftred = 150
topred = topyellow = 100
# Every dice are drawn in a defined position
@reddice.each do |item|
each
draw leftred, topred, item.to_s to_i, 1, false )
draw( to_s.to_i
topred += 100
end
@yellowdice.each do |item|
each
draw leftyellow, topyellow, item.to_s to_i, 2, false )
draw( to_s.to_i
topyellow += 100
end
41
42. Learn BDD Playing Dice! - Your behavior is have fun
# Initial position of result
leftresult = 300
topresult = 80
# The results are drawn in defined positions
@result.each_with_index do |item, index|
each_with_index
@resulttext[index] = para item.to_s :stroke => snow, :top => topresult, :left => leftresult
to_s,
topresult += 100
end
end
# Method draw was based in the Pretty Dice project written by Ed Heil
@dice = []
def draw left, top, number, color, rotate )
draw(
imagewidth = 60
imageheight = 60
i = image imagewidth, imageheight,
image(
:top => top - imagewidth / 2,
:left => left - imagewidth / 2,
:shadow => 10, :center => true ) do
if color == 1
strokecolor = red
fillrectanglecolor = tomato
filldotscolor = darkred
else
strokecolor = yellow
fillrectanglecolor = lightgoldenrodyellow
42
43. Chapter 3: Playing dice with shoes
filldotscolor = chocolate
end
sw = 1
strokewidth sw
stroke strokecolor
fill fillrectanglecolor
inset = 2
inset2 = 8
rect inset, inset, imagewidth-inset-sw, imageheight-inset-sw, 10 )
rect( - - - -
fill filldotscolor
ovalradius = 10
low = inset2
high = imagewidth - inset2 - ovalradius
mid = ( imagewidth - ovalradius ) / 2
oval mid, mid, ovalradius ) if number % 2 == 1
oval(
if number > 1
oval low, low, ovalradius )
oval(
oval high, high, ovalradius )
oval(
end
if number > 3
oval low, high, ovalradius )
oval(
oval high, low, ovalradius )
oval(
end
if number > 5
43
44. Learn BDD Playing Dice! - Your behavior is have fun
oval mid, low, ovalradius )
oval(
oval mid, high, ovalradius )
oval(
end
end # end of image block
i.rotate rand 359 ) ) if rotate
rotate( rand(
@dice << i
end
end
PLAYING WITH WAR DICE SHOES APP
Below you can see the War Dice app in action. You must choose the number of red and yellow dice in
respective list box. Then click in Attack button to play dice. Now just have fun!
44
45. Chapter 3: Playing dice with shoes
Now click in verify button to know who wins!
45