learn bdd-playing-dice-book
DESCRIPTION
A ebook to learn BDD, Ruby and Shoes having funTRANSCRIPT
1
Learn BDD Playing Dice!
YOUR BEHAVIOR IS HAVE FUN
First Edition
Learn BDD Playing Dice!
YOUR BEHAVIOR IS HAVE FUN
First Edition
by Valério Farias de Carvalho
by Valério Farias de Carvalho. This document is licensed under Creative Commons 3.0 Attribution License.
First edition: July 2010
Valério Fariashttp://www.valeriofarias.comTwitter: @valeriofarias
Cover image from http://www.flickr.com/photos/missturner/
INTRODUCTION
Hello Ruby enthusiast! Hello BDD enthusiast! Welcome to this journey line by line, test by test, using theBehaviour Driven Development technique in a interesting project: War Dice Simulation!. I wrote this tiny bookto learn BDD and RSpec for myself. I would want to start with a little example. And if was possible with afunny 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 constructthe RSpec file of Dice class and the Dice class simultaneously and step by step until all of the tests becomegreen.
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 youenjoy 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, Imade to learn RSpec. You can send suggests, clone the project, modify it and codify in other ways. Whoknows 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
Learn BDD Playing Dice!
7
Now, just read, codify, test, share and have fun!
Learn BDD Playing Dice! - Your behavior is have fun
8
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 tosoftware as Growl, LibNotify, and Snarl, displaying a window with the results. Read the readme file of theproject to know how to install: http://github.com/carlosbrando/autotest-notification/. With this three gems, ourjourney become funny!
Chapter 1: Making a Dice class using BDD
9
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 orhttp://github.com/shoes/shoes. To learn how to use and execute shoes read the ebook Nobody knows Shoesavailable 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.rbinside the spec folder. Well! What you waiting! Let's write the first requirement in that file. Now we start toplay!
requirerequire "rubygems"requirerequire "spec"
requirerequire "lib/dice"
describe Dice dodoit "should have numbers 1 to 6"
endend
To execute this test open the terminal end enter in the dice folder and use the test command:
cd dicespec spec//dice_spec
In our example with autotest, just digit the next command:
Learn BDD Playing Dice! - Your behavior is have fun
10
autospec
Now the test execute automatically every time that the files was saved. Come back to our example. Theoutput of this first test broke because we need create the Dice class in the lib/dice.rb file.
spec ----autospec spec//dice_spec.rbrb.//spec//dice_spec.rbrb:6: uninitialized constant Dice (NameError)
WRITE THE DICE CLASS
To fix the initial error, just write the Dice class:
classclass Diceendend
Output: show the pending requirement.
Pending:
Dice should have only numbers 1 to 6 (Not Yet Implemented).//spec//dice_spec.rbrb:7
Finished inin 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:
Chapter 1: Making a Dice class using BDD
11
describe Dice dodoit "should have only numbers 1 to 6" dodo
dice == DiceDice.newnew(1..6).shouldshould includeinclude( dice.playplay )
endendendend
Output:
F
1)NoMethodError inin '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:
classclass Dicedefdef playplayendend
endend
Output: still failure because the play method returns nil.
Learn BDD Playing Dice! - Your behavior is have fun
12
F
1)'Dice should have only numbers 1 to 6' FAILEDexpected 1..6 to includeinclude nilnil.//spec//dice_spec.rbrb:9:
Finished inin 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:
classclass Dicedefdef playplay
10endend
endend
Output: the test still failure because the number is outside the range.
F
1)'Dice should have numbers 1 to 6' FAILEDexpected 1..6 to includeinclude 10.//spec//dice_spec.rbrb:9:
Finished inin 0.03021 seconds
Chapter 1: Making a Dice class using BDD
13
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.
classclass Dicedefdef playplay
6endend
endend
Output:
.
Finished inin 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 Iremember, but not too much. I'll put x in the beginning of 'it' statement to rspec ignore then. this is just a trick;).
requirerequire "rubygems"requirerequire "spec"
Learn BDD Playing Dice! - Your behavior is have fun
14
requirerequire "lib/dice"
describe Dice dodoit "should have only numbers 1 to 6" dodo
dice == DiceDice.newnew(1..6).shouldshould includeinclude( dice.playplay )
endend
# Three rand groups of 1000 numbers must be different each otherit "should show the numbers randomly" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
xit "should store the last number after the dice was played."xit "should play the dice when dice object was initialized"
endend
Output:
.F
1)'Dice should show the numbers randomly' FAILEDexpected falsefalse, got truetrue.//spec//dice_spec.rbrb:18:
Finished inin 0.093321 seconds
Chapter 1: Making a Dice class using BDD
15
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 thenumbers 1 to 6. This requirement must be tested with a variety of numbers instead of only one how it's rightnow. To play a little bit with ruby, I'll modify also the require code to simplify the number of lines.
classclass Dicedefdef playplay
randrand(6)endend
endend
%w{ rubygems spec lib/dice }.eacheach {|lib| requirerequire lib}
describe Dice dodoit "should show only numbers 1 to 6" dodo
dice == DiceDice.newnewgroup == (1..1_000).collectcollect{ dice.playplay }.joinjoingroup.should_notshould_not be_nilgroup.should_notshould_not be_emptygroup.should_notshould_not includeinclude('-') # Negative numbers aren't permittedgroup.should_notshould_not includeinclude('0')group.should_notshould_not includeinclude('7')group.should_notshould_not includeinclude('8')group.should_notshould_not includeinclude('9')
endend
Learn BDD Playing Dice! - Your behavior is have fun
16
# Three rand groups of 1000 numbers must be different each otherit "should show the numbers randomly" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
xit "should store the last number after the dice was played."xit "should play the dice when dice object was initialized"
endend
Output: rand(6) generate also zeros, the the test failure.
F.
1)'Dice should show only numbers 1 to 6' FAILEDexpected "40251322225105400021423125552351044325205220224304451434252545153314510153043001251012005523244142243512333204035042444130240534042050050324205500223120330524430331015422350203015044053545205524012055023101003331405204353205410102441530220034031430225503404511243223354504315335402445045511" notnot to includeinclude "0".//spec//dice_spec.rbrb:10:
Finished inin 0.046589 seconds
2 examples, 1 failure
Chapter 1: Making a Dice class using BDD
17
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 therange to use regexp. Now the test pass :).
classclass Dicedefdef playplay
randrand(6) ++ 1endend
endend
%w{ rubygems spec lib/dice }.eacheach {|lib| requirerequire lib}
describe Dice dodoit "should have only numbers 1 to 6" dodo
dice == DiceDice.newnewgroup == (1..1_000).collectcollect{ dice.playplay }.joinjoingroup.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
# Three rand groups of 1000 numbers must be different each otherit "should show the numbers randomly" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
xit "should store the last number after the dice was played."
Learn BDD Playing Dice! - Your behavior is have fun
18
xit "should play the dice when dice object was initialized"endend
STORE THE DICE NUMBER
Let's work in the next requirement: "should store the last number after the dice was played". I'll create themethod show_number and I'll put a constant to test pass.
%w{rubygems spec lib/dice}.eacheach {|lib| requirerequire lib}
describe Dice dodo
it "should have only numbers 1 to 6" dododice == DiceDice.newnewgroup == (1..1_000).collectcollect{ dice.playplay }.joinjoingroup.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
# Three groups of 1000 random numbers must be different each otherit "should show the numbers randomly" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
it "should store the last number after the dice was played." dododice == DiceDice.newnewdice.playplay
Chapter 1: Making a Dice class using BDD
19
dice.show_numbershow_number.to_sto_s.shouldshould matchmatch(/^[1-6]*[1-6]$/)endend
xit "should play the dice when dice object was initialized."endend
classclass Dicedefdef playplay
randrand(6) ++ 1endend
defdef show_numbershow_number3
endendendend
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.
classclass Dicedefdef playplay
@number == randrand(6) ++ 1endend
defdef show_numbershow_number@number
endendendend
Learn BDD Playing Dice! - Your behavior is have fun
20
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}.eacheach {|lib| requirerequire lib}
describe Dice dodo
it "should have only numbers 1 to 6" dododice == DiceDice.newnewgroup == (1..1_000).collectcollect{ dice.playplay }.joinjoingroup.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
# Three groups of 1000 random numbers must be different each otherit "should show the numbers randomly" dodo
dice == DiceDice.newnewgroup1 == (1..1_000).collectcollect{ dice.playplay }group2 == (1..1_000).collectcollect{ dice.playplay }group3 == (1..1_000).collectcollect{ dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false(group2 ==== group3).shouldshould be_false
endend
it "should store the last number after the dice was played." dododice == DiceDice.newnewdice.playplaydice.show_numbershow_number.to_sto_s.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
it "should play the dice when dice object was initialized." dodoDiceDice.newnew.show_numbershow_number.to_sto_s.shouldshould matchmatch(/^[1-6]*[1-6]$/)
Chapter 1: Making a Dice class using BDD
21
endend
endend
Output:
...F
1)'Dice should play the dice when dice object was initialized.' FAILEDexpected "" to match /^[1-6]*[1-6]$/.//spec//dice_spec.rbrb:29:
Finished inin 0.12371 seconds
4 examples, 1 failure
USING THE INITIALIZEINITIALIZE METHOD
Now I finalize the dice class putting the initialize method with a message for the play method. The testpass.
classclass Dicedefdef initializeinitialize
playendend
defdef playplay@number == randrand(6) ++ 1
endend
Learn BDD Playing Dice! - Your behavior is have fun
22
defdef show_numbershow_number@number
endendendend
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'llmake 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 numbersstored are different each other.
%w{rubygems spec lib/dice}.eacheach {|lib| requirerequire lib}
describe Dice dodobeforebefore(::eacheach) dodo
@dice == DiceDice.newnewendend
it "should have only numbers 1 to 6" dodogroup == (1..1_000).collectcollect{ @dice.playplay }.joinjoingroup.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
# Three groups of 1000 random numbers must be different each otherit "should show the numbers randomly" dodo
group1 == (1..1_000).collectcollect{ @dice.playplay }group2 == (1..1_000).collectcollect{ @dice.playplay }group3 == (1..1_000).collectcollect{ @dice.playplay }(group1 ==== group2).shouldshould be_false(group1 ==== group3).shouldshould be_false
Chapter 1: Making a Dice class using BDD
23
(group2 ==== group3).shouldshould be_falseendend
it "should store the last number after the dice was played." dodogroup1 == (1..100).collectcollect dodo
@[email protected]_numbershow_number
endendgroup2 == (1..100).collectcollect dodo
@[email protected]_numbershow_number
endend(group1 ==== group2).shouldshould be_false
endend
it "should play the dice when dice object was initialized." [email protected]_numbershow_number.to_sto_s.shouldshould matchmatch(/^[1-6]*[1-6]$/)
endend
endend
PLAY WITH THE DICE CLASS
Now that the class is finished. Let's play with it in the irb!
>>>> requirerequire 'dice'=> truetrue>>>> dice == DiceDice.newnew=> #<Dice:0xb7a64188 @number=5>>>>> dice.show_numbershow_number=> 5
Learn BDD Playing Dice! - Your behavior is have fun
24
>>>> dice.classclass=> Dice
Using the dice only one time and abandon it
>>>> DiceDice.newnew.show_numbershow_number=> 2
Play the dice a lot of times:
>>>> 20.timestimes{ print dice.playplay }21636456135236136236=> 20
Three dice:
>>>> yellowdice == [DiceDice.newnew, DiceDice.newnew, DiceDice.newnew]=> [#<Dice:0xb7a3e3d4 @number=3>, #<Dice:0xb7a3e3ac @number=5>, #<Dice:0xb7a3e384 @number=5>]>>>> yellowdice.eacheach{ |dice| puts dice.show_numbershow_number }355=> [#<Dice:0xb7a3e3d4 @number=3>, #<Dice:0xb7a3e3ac @number=5>, #<Dice:0xb7a3e384 @number=5>]
Play again the same dice
>>>> yellowdice.eacheach{ |dice| puts dice.playplay }525=> [#<Dice:0xb7a3e3d4 @number=5>, #<Dice:0xb7a3e3ac @number=2>, #<Dice:0xb7a3e384 @number=5>]
What's the bigger value of the three dice of last play?
Chapter 1: Making a Dice class using BDD
25
>>>> puts yellowdice.collectcollect{ |item| item.show_numbershow_number }.maxmax5=> nilnil
And about the less value?
>>>> puts yellowdice.collectcollect{ |item| item.show_numbershow_number }.minmin2=> nilnil
Wait. The last example I used 3 dice?!? Ahaaa! This looks like the dice of war game. Let's create the wargame dice class in the next chapter.
Learn BDD Playing Dice! - Your behavior is have fun
26
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 thedice 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 dicedepend on the army number of attacker and defender. But in this experiment I'll think only in the handling ofdice 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 tocompare 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 thenthe yellow.
Well, I think that's it. Let's work now!
Chapter 2: Reproducing the War Game Dice
27
TESTING ARRAY COMPARISON
Well, I remembered that we need to use array comparison in the dice of war game, but In my first test witharray comparison I had problems. You can see this problem in the next test:
describe Array dodoit "should be comparable" dodo
([8,9,10] >> [1,2,3]).shouldshould be_trueendend
endend
Output:
....F
1)NoMethodError inin '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 COMPARABLECOMPARABLE MODULE
To solve the previous issue, just include the Comparable module in the dice.rb file. I'll put in a simplify formpurposely to play with ruby possibilities :).
classclass Array; includeinclude Comparable; endend
Learn BDD Playing Dice! - Your behavior is have fun
28
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 intentionallya number different of 1, 2, 3 to cause an error.
describe Wardice dodoit "The attack and defence should use 1, 2 or 3 dice" dodo
wardice == WardiceWardice.newnew(0, 3)wardice.redred.to_sto_s.shouldshould matchmatch(/^[1-3]$/)wardice.yellowyellow.to_sto_s.shouldshould matchmatch(/^[1-3]$/)
endend
it "Should compare from bigger to less values and save in array result"endend
classclass Wardiceattr_readerattr_reader ::redred, ::yellowyellow
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow
endendendend
Output:
'Wardice The attack and defence should use 1, 2 or 3 dice' FAILEDexpected "0" to match /^[1-3]$/.//spec//dice_spec.rbrb:50:
Chapter 2: Reproducing the War Game Dice
29
Finished inin 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 arand number between the range. I'll make also a little refactory in the spec file to put a situation that a numberof dice is correct. You can see below the solution.
classclass WarDiceattr_readerattr_reader ::redred, ::yellowyellow
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow@red == randrand(3) ++ 1 ifif @red.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?@yellow == randrand(3) ++ 1 ifif @yellow.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?
endendendend
describe WarDice dodoit "The attack and defence should use 1, 2 or 3 dice" dodo
wardice == WardiceWardice.newnew(0, 7)wardice.redred.to_sto_s.shouldshould matchmatch(/^[1-3]$/)wardice.yellowyellow.to_sto_s.shouldshould matchmatch(/^[1-3]$/)
wardice2 == WardiceWardice.newnew(2, 3)wardice2.redred.shouldshould ==== 2wardice2.yellowyellow.shouldshould ==== 3
endend
Learn BDD Playing Dice! - Your behavior is have fun
30
it "Should compare from bigger to less values and save in array result"endend
DECREASE SORT ARRAY
This is a important requirement, but I remembered only now: wardice Should provide yellow and red diceresults with an array in decreasing sort. You can see the solution below:
describe WarDice dodoit "The attack and defence should use 1, 2 or 3 dice" dodo
wardice == WarDiceWarDice.newnew(0, 7)wardice.redred.to_sto_s.shouldshould matchmatch(/^[1-3]$/)wardice.yellowyellow.to_sto_s.shouldshould matchmatch(/^[1-3]$/)
wardice2 == WarDiceWarDice.newnew(2, 3)wardice2.redred.shouldshould ==== 2wardice2.yellowyellow.shouldshould ==== 3
endend
it "Should provide yellow and red dice results with an array in decreasing sort" dodowardice == WardiceWardice.newnew(3, 3)wardice.reddicereddice.is_a?is_a?(Array).shouldshould be_truewardice.yellowdiceyellowdice.is_a?is_a?(Array).shouldshould be_true
wardice.reddicereddice.sortsort{|x, y| y <=><=> x }.shouldshould ==== wardice.reddicereddicewardice.yellowdiceyellowdice.sortsort{|x, y| y <=><=> x }.shouldshould ==== wardice.yellowdiceyellowdice
endend
it "Should compare from bigger to less values and save in array result"
endend
Chapter 2: Reproducing the War Game Dice
31
classclass wardiceattr_readerattr_reader ::redred, ::yellowyellow, ::reddicereddice, ::yellowdiceyellowdice
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow@red == randrand(3) ++ 1 ifif @red.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?@yellow == randrand(3) ++ 1 ifif @yellow.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?
@reddice == []@yellowdice == []
@dice == [email protected]{|row| @reddice[row] == [@dice.playplay] }@yellow.timestimes{ |row| @yellowdice[row] == [@dice.playplay] }
endendendend
Output:
'wardice Should provide yellow and red dice results with an array in decreasing sort' FAILEDexpected: [[5], [2], [4]],
got: [[5], [4], [2]] (using ====).//spec//dice_spec.rbrb:63:
Finished inin 0.035218 seconds
9 examples, 1 failure, 2 pending
Learn BDD Playing Dice! - Your behavior is have fun
32
SOLVING SORTING ARRAY ISSUEclassclass WarDice
attr_readerattr_reader ::redred, ::yellowyellow, ::reddicereddice, ::yellowdiceyellowdice
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow@red == randrand(3) ++ 1 ifif @red.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?@yellow == randrand(3) ++ 1 ifif @yellow.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?
@reddice == []@yellowdice == []
@dice == [email protected]{|row| @reddice[row] == [@dice.playplay] }@yellow.timestimes{ |row| @yellowdice[row] == [@dice.playplay] }
@reddice.sort!sort!{|x,y| y <=><=> x }@yellowdice.sort!sort!{|x,y| y <=><=> x }
endendendend
Now the test pass.
COMPARING VALUES
Let's work now in the last requirement: Should compare from bigger to less values and save in arrayresult
describe Wardice dodoit "The attack and defence should use 1, 2 or 3 dice" dodo
Chapter 2: Reproducing the War Game Dice
33
wardice == WardiceWardice.newnew(0, 7)wardice.redred.to_sto_s.shouldshould matchmatch(/^[1-3]$/)wardice.yellowyellow.to_sto_s.shouldshould matchmatch(/^[1-3]$/)
wardice2 == WardiceWardice.newnew(2, 3)wardice2.redred.shouldshould ==== 2wardice2.yellowyellow.shouldshould ==== 3
endend
it "Should provide yellow and red dice results with an array in decreasing sort" dodowardice == WardiceWardice.newnew(3, 3)wardice.reddicereddice.is_a?is_a?(Array).shouldshould be_truewardice.yellowdiceyellowdice.is_a?is_a?(Array).shouldshould be_true
wardice.reddicereddice.sortsort{|x, y| y <=><=> x }.shouldshould ==== wardice.reddicereddicewardice.yellowdiceyellowdice.sortsort{|x, y| y <=><=> x }.shouldshould ==== wardice.yellowdiceyellowdice
endend
it "Should compare from bigger to less values and save in array result" dodowardice == WardiceWardice.newnew(3, 2)wardice.reddicereddice.firstfirst.shouldshould >> wardice.reddicereddice.lastlastwardice.attackattack
wardice.resultresult[0].shouldshould ==== "Red Win" ifif wardice.reddicereddice[0] >> wardice.yellowdiceyellowdice[0]wardice.resultresult[0].shouldshould ==== "Yellow Win" ifif wardice.reddicereddice[0] <=<= wardice.yellowdiceyellowdice[0]
endendendend
The final WarDice class:
classclass Wardiceattr_readerattr_reader ::redred, ::yellowyellow, ::reddicereddice, ::yellowdiceyellowdice, ::resultresult
Learn BDD Playing Dice! - Your behavior is have fun
34
defdef initializeinitialize(red, yellow)@red, @yellow == red, yellow@red == randrand(3) ++ 1 ifif @red.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?@yellow == randrand(3) ++ 1 ifif @yellow.to_sto_s.grepgrep(/^[1-3]$/).empty?empty?
@reddice == []@yellowdice == []@result == []
@dice == [email protected]{|row| @reddice[row] == [@dice.playplay] }@yellow.timestimes{ |row| @yellowdice[row] == [@dice.playplay] }
@reddice.sort!sort!{|x, y| y <=><=> x }@yellowdice.sort!sort!{|x, y| y <=><=> x }
endend
defdef [email protected]_with_indexeach_with_index dodo |item, index|
nextnext ifif @yellowdice[index].nil?nil?reddice == itemyellowdice == @yellowdice[index]
ifif reddice >> yellowdice@result <<<< "Red Win"
elseelse@result <<<< "Yellow Win"
endendendend
endendendend
Chapter 2: Reproducing the War Game Dice
35
PLAY WITH THE WARDICE CLASS
Open irb and write the next sequence:
>>>> requirerequire 'dice'=> truetrue
Initialize the WarDice class. The first parameter is the number of red dice and the second is the number ofyellow dice:
>>>> wardice == WarDiceWarDice.newnew(2, 3)=> #<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.reddicereddice31=> nilnil>>>> puts wardice.yellowdiceyellowdice611=> nilnil
Show result:
>>>> puts wardice.resultresultYellow WinYellow Win=> nilnil
Learn BDD Playing Dice! - Your behavior is have fun
36
All output in one command:
>>>> war.reddicereddice.each_with_indexeach_with_index{ |item, index| puts "Red:#{item} Yellow:#{war.yellowdiceyellowdice[index]}- #{war.resultresult[index]}"}Red:3 Yellow:6 -- Yellow WinRed: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 graphicapplication using Shoes.
Chapter 2: Reproducing the War Game Dice
37
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 simpletest with the class Dice. You have to create a new file diceshoes.rb, then paste the code below inside it andsave 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:
requirerequire 'dice'ShoesShoes.appapp ::titletitle => "Test with dice class", ::widthwidth => 500, ::heightheight => 500 dodo
background aliceblue
para "Welcome! This is an example using Dice class.", ::weightweight => "bold"
dice == DiceDice.newnew
Learn BDD Playing Dice! - Your behavior is have fun
38
# Print dice numbers with random colors1_000.timestimes dodo
r, g, b == rand, rand, randpara dice.playplay, ::strokestroke => rgbrgb(r****3, g****3, b****3), ::sizesize => "large"
endendendend
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 inthe lib folder. I put some remarks across the code to facilitate the understanding. Have a nice experiment.
Chapter 3: Playing dice with shoes
39
requirerequire 'dice'
ShoesShoes.appapp ::titletitle => "Dice of War game", ::widthwidth => 500, ::heightheight => 500 dodobackground gradientgradient( black, teal )
# List with number of red dice: 1, 2 or 3para "Red", ::strokestroke => tomato@numberreddice == list_box ::itemsitems => ["1", "2", "3"],
::widthwidth => 70, ::choosechoose => "3" dodo |list|endend
para " X ", ::strokestroke => snow
# List with number of yellow dice: 1, 2 or 3@numberyellowdice == list_box ::itemsitems => ["1", "2", "3"],
::widthwidth => 70, ::choosechoose => "3" dodo |list|endendpara "Yellow", ::strokestroke => yellow
# Define an aleatory position@a == @b == @c == []
(40..200).stepstep(10){ |x| @a <<<< x }(230..450).stepstep(10){ |y| @b <<<< y }(80..450).stepstep(10){ ||z|| @c <<<< z }
# Variables that will store values of wardice object@reddice == @yellowdice == @resulttext == []
button "Attack", ::widthwidth => 80 dodo
# Clear the [email protected]{ |d| d.removeremove }@resulttext.eacheach{ |a| a.removeremove }
Learn BDD Playing Dice! - Your behavior is have fun
40
# The wardice object is initializingwardice == WarDiceWarDice.newnew( @numberreddice.texttext.to_ito_i, @numberyellowdice.texttext.to_ito_i )@reddice == wardice.reddicereddice@yellowdice == wardice.yellowdiceyellowdice@result == wardice.resultresult
# Every dice is drawn in random [email protected]{ |item| drawdraw( @a[randrand(@a.lengthlength)], @c[randrand(@c.lengthlength)], item.to_sto_s.to_ito_i, 1, truetrue ) }@yellowdice.eacheach{ |item| drawdraw( @b[randrand(@b.lengthlength)], @c[randrand(@c.lengthlength)], item.to_sto_s.to_ito_i, 2, truetrue )}
endend
button "Verify", ::widthwidth => 80 dodo
# Clear the [email protected]{ |d| d.removeremove }@resulttext.eacheach{ |a| a.removeremove }
# Initial position of diceleftyellow == 250leftred == 150topred == topyellow == 100
# Every dice are drawn in a defined [email protected] dodo |item|
drawdraw( leftred, topred, item.to_sto_s.to_ito_i, 1, falsefalse )topred +=+= 100
endend
@yellowdice.eacheach dodo |item|drawdraw( leftyellow, topyellow, item.to_sto_s.to_ito_i, 2, falsefalse )topyellow +=+= 100
endend
Chapter 3: Playing dice with shoes
41
# Initial position of resultleftresult == 300topresult == 80
# The results are drawn in defined [email protected]_with_indexeach_with_index dodo |item, index|
@resulttext[index] == para item.to_sto_s, ::strokestroke => snow, ::toptop => topresult, ::leftleft => leftresulttopresult +=+= 100
endend
endend
# Method draw was based in the Pretty Dice project written by Ed Heil@dice == []
defdef drawdraw( left, top, number, color, rotate )
imagewidth == 60imageheight == 60
i == imageimage( imagewidth, imageheight,::toptop => top -- imagewidth // 2,::leftleft => left -- imagewidth // 2,::shadowshadow => 10, ::centercenter => truetrue ) dodo
ifif color ==== 1strokecolor == redfillrectanglecolor == tomatofilldotscolor == darkred
elseelsestrokecolor == yellowfillrectanglecolor == lightgoldenrodyellow
Learn BDD Playing Dice! - Your behavior is have fun
42
filldotscolor == chocolateendend
sw == 1strokewidth swstroke strokecolor
fill fillrectanglecolor
inset == 2inset2 == 8
rectrect( inset, inset, imagewidth--inset--sw, imageheight--inset--sw, 10 )
fill filldotscolor
ovalradius == 10low == inset2high == imagewidth -- inset2 -- ovalradiusmid == ( imagewidth -- ovalradius ) // 2
ovaloval( mid, mid, ovalradius ) ifif number %% 2 ==== 1
ifif number >> 1ovaloval( low, low, ovalradius )ovaloval( high, high, ovalradius )
endend
ifif number >> 3ovaloval( low, high, ovalradius )ovaloval( high, low, ovalradius )
endend
ifif number >> 5
Chapter 3: Playing dice with shoes
43
ovaloval( mid, low, ovalradius )ovaloval( mid, high, ovalradius )
endend
endend # end of image block
i.rotaterotate( randrand( 359 ) ) ifif rotate
@dice <<<< i
endend
endend
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 inrespective list box. Then click in Attack button to play dice. Now just have fun!
Learn BDD Playing Dice! - Your behavior is have fun
44
Now click in verify button to know who wins!
Chapter 3: Playing dice with shoes
45
Learn BDD Playing Dice! - Your behavior is have fun
46