One that I liked up front (Chapter 1) is Conway’s Game of Life. The rules are explained here along with some cool information. This is basically a “cellular automaton”.. sounds ultra nerdy doesn’t it? Don’t worry sounds as greek to me as it does to you. In simple terms, it is a simulation of a colony of cells evolving. There are some rules governing the birth and mortality of cells. See the wikipedia page for details
Anyways, the writeup is also present in the uploaded zip -
http://tech.groups.yahoo.com/group/testdrivendevelopment/files/
"TDD_Conways_GameOfLife_Ruby.zip"
Let’s begin.
First I need to figure out how to write a test in Ruby. Pick up the pickaxe… What the…. now it can’t be that easy… can it?
Let’s create a file called GameOfLife.rb. Important items in bold below
require "test/unit"
class Test_GameOfLife < Test::Unit::TestCase
def test_GetOne
assert_equal(1, GameOfLife.new.getOne);
end
end
class GameOfLife
end
L:\Gishu\Ruby\GameOfLife>ruby GameOfLife.rb
Loaded suite GameOfLife
Started
E
Finished in 0.016 seconds.
1) Error:
test_GetOne(Test_GameOfLife):
NoMethodError: undefined method `getOne' for #<GameOfLife:0x2ba14d8>
GameOfLife.rb:5:in `test_GetOne'
1 tests, 0 assertions, 0 failures, 1 errors
Ok we have a code red !! Let’s get it green… quick to the test mobile..
require "test/unit"
class Test_GameOfLife < Test::Unit::TestCase
def test_GetOne
assert_equal(1, GameOfLife.new.getOne);
end
end
class GameOfLife
end
L:\Gishu\Ruby\GameOfLife>ruby GameOfLife.rb
Loaded suite GameOfLife
Started
E
Finished in 0.016 seconds.
1) Error:
test_GetOne(Test_GameOfLife):
NoMethodError: undefined method `getOne' for #<GameOfLife:0x2ba14d8>
GameOfLife.rb:5:in `test_GetOne'
1 tests, 0 assertions, 0 failures, 1 errors
require "test/unit"
class Test_GameOfLife < Test::Unit::TestCase
def test_GetOne
assert_equal(1, GameOfLife.new.getOne);
end
end
class GameOfLife
def getOne
return 1
end
end
L:\Gishu\Ruby\GameOfLife>ruby GameOfLife.rb
Loaded suite GameOfLife
Started
.
Finished in 0.0 seconds.
1 tests, 1 assertions, 0 failures, 0 errors
Woo hoo !!! I have my first green light in Ruby. I feel the confidence running through my veins now… I had heard about Ruby’s principle of least surprise.. I’m fascinated. Beam me up, Scotty !!
The advent of colonization
Let’s make the input to the program a file. This file would contain the current snapshot of a “colony” of cells. The first line in the file shall tell us the max rows and columns in the colony
Colony1.txt
3, 4
So we need a colony class that can tell us the bounds of a colony in an input file.
Colony.ReadFromFile - ReadBounds
Naming conventions : Test case classes are saved in files prefixed with tc_
tc_Colony.rb
require 'test/unit'
require 'Colony'
class Test_Colony < Test::Unit::TestCase
def test_ReadFromFile
c = Colony.readFromFile("Colony1.txt");
assert_equal(3, c.maxRows);
assert_equal(4, c.maxCols);
end
end
Colony.rb
class Colony
def Colony.readFromFile( sColonyFile )
end
end
Green
Initialize is a method that would be called whenever a Colony.new is issued. We take in 2 parameters for maxRows and maxCols to know the bounds.
Colony.readFromFile: We also have our first taste of File I/O. We read the first line and use regular expressions to break the line into 2 parts. We convert them to integers via to_i() and use them to create a Colony instance.
class Colony
attr_reader :maxRows, :maxCols
def initialize( iRows, iCols )
@maxRows=iRows
@maxCols=iCols
end
def Colony.readFromFile( sColonyFile )
sColonyRows = sColonyCols = ""
File.open(sColonyFile) { |obFile|
sDimensions = obFile.gets
sColonyRows, sColonyCols = sDimensions.scan(/\d+/)
}
return Colony.new(sColonyRows.to_i, sColonyCols.to_i)
end
end
Refactor
Inline the variable sDimensions.
def Colony.readFromFile( sColonyFile )
sColonyRows = sColonyCols = ""
File.open(sColonyFile) { |obFile|
sColonyRows, sColonyCols = obFile.gets.scan(/\d+/)
}
return Colony.new(sColonyRows.to_i, sColonyCols.to_i)
end
Colony.IsCellAlive
Since I am new to this.. I wrote the “read from file” test in 2 parts. Part 2 – now we read the actual colony. Update the Colony1.txt file as follows
Colony1.txt
3, 4
0 0 0 0
1 1 0 0
0 0 0 1
Red
tc_Colony.rb
def test_IsCellAlive
c = Colony.readFromFile("Colony1.txt");
expected = [
false, false, false, false,
true, true, false, false,
false, false, false, true ]
iExpectedLooper = 0;
(0...c.maxRows).each{ |iRow|
(0...c.maxCols).each{ |iCol|
assert_equal( expected[iExpectedLooper], c.isCellAlive( iRow, iCol ) )
iExpectedLooper = iExpectedLooper.succ
}
}
end
Colony.rb
def isCellAlive( iRow, iCol )
return false;
end
Now to show that I’m not making all this up…
L:\Gishu\Ruby\GameOfLife>ruby tc_Colony.rb
Loaded suite tc_Colony
Started
F.
Finished in 0.094 seconds.
1) Failure:
test_IsCellAlive(Test_Colony)
[tc_Colony.rb:23:in `test_IsCellAlive'
tc_Colony.rb:21:in `each'
tc_Colony.rb:21:in `test_IsCellAlive'
tc_Colony.rb:20:in `each'
tc_Colony.rb:20:in `test_IsCellAlive']:
<true> expected but was
<false>.
2 tests, 7 assertions, 1 failures, 0 errors
Green
String.scan can parse a line and return an array. So this should work for us…
sRow.scan(/\d/) - > [“0”, “0”, “0”, 0”]
So I build up a 2D Array like this and stash it away in the Colony object.
[ [“0”, “0”, “0”, 0”], [“1”, “1”, “0”, “0”], [“0”, “0”, ”0”, “1” ] ]
class Colony
attr_reader :maxRows, :maxCols
def initialize( iRows, iCols, obColony2DArray )
@maxRows=iRows
@maxCols=iCols
@colonyGrid=obColony2DArray
end
def Colony.readFromFile( sColonyFile )
sColonyRows = sColonyCols = ""
ob2DArray ||= []
File.open(sColonyFile) { |obFile|
sColonyRows, sColonyCols = obFile.gets.scan(/\d+/)
while(sRow = obFile.gets)
ob2DArray.insert(-1, sRow.scan(/\d/) )
end
}
return Colony.new(sColonyRows.to_i, sColonyCols.to_i, ob2DArray)
end
def isCellAlive( iRow, iCol )
return ( @colonyGrid[iRow][iCol] == "1" ? true : false )
end
end
Refactor
For the ones who came in late, I can factor out common code into a setup method, which is guaranteed to be executed before every test. So off with duplication !!
Add a member variable @obColony and initialize it inside setup()
tc_Colony.rb
def setup
@obColony = Colony.readFromFile("Colony1.txt");
end
to_s() – ToString() for the .Net ters
Red
def test_ToString
assert_equal( ". . . . \n@ @ . . \n. . . @ \n",
@obColony.to_s )
end
1) Failure:
test_ToString(Test_Colony) [tc_Colony.rb:34]:
<". . . . \n@ @ . . \n. . . @ \n"> expected
but was
<"#<Colony:0x2b9c9a8>">.
Green
def to_s
sGridListing = ""
(0…maxRows).each{ |iRow|
(0...maxCols).each{ |iCol|
sGridListing += ( isCellAlive( iRow, iCol) ? "@ " : ". " )
}
sGridListing += "\n"
}
return sGridListing
end
On to Part 2
No comments:
Post a Comment