My lil Subversion Jumpstart tutorial

I'll keep it short.

  1. Get Subversion from http://subversion.tigris.org Get the binary packages for your OS for ease of installation. approx 3MB
  2. Get Mike Clark's book .

I'll just note down some of the things that I do when I set up a SVN box on my WinXP machine.

  • Run the install or unzip the zip to the [SubversionFolder] e.g. d:/subversion
  • Modify the PATH environment variable so that it includes [SubversionFolder]/bin. You can verify this via the command prompt
>svn --version
svn, version 1.4.5 (r25188)
compiled Aug 22 2007, 20:49:04
  • Create the repository. To create a repository in D:\CoderZone\svn-repos - assuming that d:\CoderZone exists
>svnadmin create /CoderZone/svn-repos
  • Now you can import a dump file e.g. if you are migrating between machines / subversion versions. e.g. if I had created a dump file from the source repository and load it into the new repository as shown below. (If you get an error like svnadmin: Expected format '3' of repository; found format '5' make sure that you dont have the old version of svnserve running. Close the process and create the repository again)
>svnadmin dump D:\CoderZone2\svn-repos > ReposDump.dmp
>svnadmin load D:\CoderZone\svn-repos < ReposDump.dmp
  • You start the subversion daemon like this
>start svnserve --daemon --root d:\CoderZone\svn-repos
  • Next we import a folder like this. e.g. if we want to check in contents of c:\work
c:\work
>svn import -m "First Check-in" svn://[SubversionRunningMachine]/[ProjectName]/trunk
  • That's it we can now checkout the contents of our project to a folder name Work2 like this
>svn co svn://[SubversionRunningMachine]/[ProjectName]/trunk Work2
  • I can add a new file to version control and check in modifications to already versioned files with
>svn add Data\templateData.xml
>svn commit -m "First Change!"

Getting FIT with Fitnesse: Getting Started

Useful links:
  1. Cory Foy does it! Fitnesse with Dotnet to suck em' into showing how to do it with Ruby as well.
  2. Ron Jeffries XP Mag article on Fitnesse with Ruby
  3. Gojko's PDF on getting Fitnesse up and running with DotNet
  4. Fitnesse Mailing List

On our way to Fitnesse
First we need to install Fitness, which will get FIT along with it. Go to http://fitnesse.org/ Click on Download. Get the full distribution zip. Next unzip it to a folder say c:\fitnesse.
Now just to be safe in case you have something running on the default port 80. Edit the run.bat batch file in the c:\fitnesse folder. Modify it as shown below to use a different port.

java -cp fitnesse.jar fitnesse.FitNesse %1 %2 %3 %4 %5 -p 2345
pause
That's it. Double click / Launch run.bat.

L:\fitnesse>java -cp fitnesse.jar fitnesse.FitNesse -p 2345
FitNesse (20070619) Started...
port: 2345
root page: fitnesse.wiki.FileSystemPage at ./FitNesseRoot
logger: none
authenticator: fitnesse.authentication.PromiscuousAuthenticator
html page factory: fitnesse.html.HtmlPageFactory
page version expiration set to 14 days.
PromiscuousAuthenticator... hmm wonder why its named like that.. but let us go on.

Fire up a browser to http://localhost:2345/
You should see the FrontPage with the Fitnesse Gauge Pointing to Green. Good work!

Does it work?

By default, Fitnesse is set to work with Java. So mustering the little bits of java that I know, lets forge ahead. Hmm.. lil reading up the wiki documentation.. Lets try the 2 Min Example
Next I spent a good 30 minutes, wondering how do I start typing that table. How do I create a blank page? I'll cut right to the chase. Just go to the address bar, think of a good name that is a WikiWord.. I chose 'GrandEntraceToMyAcceptanceTests'.. type in
http://localhost:2345/GrandEntraceToMyAcceptanceTests
and... (Right click and open the image in a new window/tab to make it readable)

Quick Batman.. click on the 'create this page' link. Its simple when you know how. And soon you should be on the 'Edit Page' screen, with a large textbox. Lets master page creation.. we don't want all our tests on one page. Type in ColumnFixtureSample and hit the Save button at the bottom of the page. You're back to the Entrance page but this time it has a set of buttons on the left and the content area shows ColumnFixtureSample?

Click on the ? link to edit the ColumnFixtureSample page.. welcome to the land of wikis.

Now lets masquerade as an excel-happy customer. He writes the following in the spreadsheet
Starting from an initial credit of 500, anytime the person spends money, the balance should be deducted by the same amount.


Copy this range and switch back to the wiki page in edit mode.
Enter the name of the java Fixture class that should be called as the first line. Press Enter. Paste the contents of the excel range too. You should see
com.gishu.trial.TestFixture
moneySpent balance?
100 400
100 300
Press the nifty 'Spreadsheet to Fitnesse' button at the bottom and ...
!|com.gishu.trial.TestFixture|
|moneySpent|balance?|

|100|400|

|100|300|
We have the fitnesse table ready. Hit Save.
Now to make this page testable, we need to set the 'Test' page property. Click on 'Properties' on the left panel. Check the 'Test' checkbox in the Actions group and click 'Save Properties'. You should now see that the page has a Test button at the top of the left panel.
Lets try it.. and we see a dirty yellow telling us we had an Exception - "Could not find fixture: com.gishu.trial.TestFixture."
But that was expected.
Let me code the java fixture up in L:\Gishu\Java\com\gishu\trial\TestFixture.java
package com.gishu.trial;

import fit.ColumnFixture;

public class TestFixture extends ColumnFixture
{
public int moneySpent;
public int balance()
{
UnderCoverDomainClass obDomainClass = new UnderCoverDomainClass();
obDomainClass.spend(moneySpent);
return obDomainClass.getBalance();
}
}

// class from the application source
class UnderCoverDomainClass
{
private int m_iBalance;
public void spend(int iMoneySpent)
{
m_iBalance -= iMoneySpent;
}
public int getBalance()
{
return m_iBalance;
}
}
Code is pretty simple. The test fixture has public data members for each column in the wiki fixture table, whose value is set to cell value. Every column name that ends in a ? signifies a method call, the value in the cell is the expected return value of that method. So the first row, would set moneySpent to 100 and then verify if the balance() method returns 400.
The fixture methods are supposed to call the application code to be verified. Here represented by the UnderCoverDomainClass.

Compile with...
L:\Gishu\Java\com\gishu\trial>javac -classpath l:\fitnesse\fitnesse.jar TestFixture.java
Now we need to tell fitnesse where to find our class files. Looks like this page has what we need.
So we edit our ColumnFixtureSample page to

!path L:\Gishu\Java

!|com.gishu.trial.TestFixture|
|moneySpent|balance?|
|100|400|
|100|300|
Save and click on Test again and we see that the tests have failed, with expected and actual values. The set classpath is being shown.

We seem to be getting a value of -100 as the balance always. Well.. we have not given the default credit of 500. Initialize the m_iBalance variable in UnderCoverDomainClass to 500. Save and Recompile. Lets run our tests again. Click Test again. This time, the first test passes. Second test returned 400 instead of 300.
Something is amiss.. Let me re-read the page on FixtureCode Aha! Test isolation. Read the section 'What that call to StaticGame is for'
So we need a singleton instance of UnderCoverDomainClass to be operated on by each row. So add another class

class SingletonDelivery
{
private static UnderCoverDomainClass m_obInstance = null;
public static UnderCoverDomainClass getInstance()
{
if (m_obInstance == null)
{
m_obInstance = new UnderCoverDomainClass();
}
return m_obInstance;
}
}
Update the balance method in the fixture to use SingletonDelivery.getInstance() instead of a new UnderCoverDomainClass(). Save and Recompile. Run Tests. Woo hoo!!!

And there we have it.
Next I'm gonna try writing some acceptance tests for a small Ruby on Rails project that I am gonna work on. But thats the next post...

Agile Estimating and Planning - Mike Cohn

Book

Key take-aways from this book..

How to use something that sounds as childish as 'story points' and make it work better than the traditional GANTT chart sequential task based model ? Separate size from duration.
How to prioritize stories or themes ?
How do you know and explain Story#1 is 'more value to the customer'?
How to separate stories into must-haves / linear / exciters ?
How to plan releases and iterations ? How to track em ?
How to make an estimate of initial velocity and how to get better at it with iterations under your belt?

In short this was my missing piece of the agile jigsaw. Gonna read it again soon when I plan something new.

Recommended Read. Recommended Buy.

Compare (and merge?) excel files with Beyond Compare

Another note to myself.

My main app - Beyond Compare, a file diff-merge utility from Scooter Software(and a really good one at that) has some spiffy rules that let you 'do the diff' on versions of an excel document.
You can compare (and it seems now you have merge as well) excel as well as word files. I haven't really compared word docs yet but xls diffs work ... well!

All you gotta do is download the rules (as a zip).
  • Unzip it to your Beyond Compare install Directory.. MSExcel folder for the xls diff rule.
  • Run Beyond Compare.
  • Main Menu > Tools > Import Rules. Select the rule file under the MSExcel folder.
  • Select the two xls files in Windows Explorer, right click and choose Compare! voila!
There are a lot of other goodies available here

Zipping up a set of files listed in a .txt file

Problem:
I have a selected set of files spanning across folders and subfolders that I would like to package into one zip e.g. list of modified files for a source control check-in operation.
I have the list of file paths in a .txt file.

I had an old version of PowerArchiver command line utility on my machine. After some tinkering with the console help... ta da!

>"PACOMP.EXE" -p -a [MyBatchzip.zip] @ListOfFiles.txt

-p : To store relative paths of zipped files.
-P : To store absolute paths of zipped files
ListOfFiles.txt : A text file with all the files you wanted to zip up
MyBatch.zip : Name of zip file to be created

The resulting zip had just those files that I wanted and the zip can be opened by any extraction utility like WinZip, etc.
Each filepath should be on a new line in the text file.

Noting down for my reference.

Update:
  • If you have some spaces or tabs at the beginning of the filepath, i think that file gets skipped.
  • Make sure of case uniformity in the List of files. It messes up with some files zipped with absolute path. To be safe, check relative paths by opening up the zip once.
    • d:\myfolder\..
    • D:\MyFolder\.. [No No.. use one of these throughout the list]

Uninstalling Vista

While helping a friend uninstall Vista so that he could go back to his friendly XP Sp2 that knew his hardware... came across this link which saved me a ton of work

For the uninitiated, Vista needs around 6 Gigs of space to sit down.. yeah you read that right!
Installs quick. Machine starts and shuts down quicker. (But then we didn't have any applications installed) - It didn't recognize the sound card. This lead to same problem with Linux in that you need hardware that plays along with your OS instead of the other way around.
Nothing great, can't hear music, videos needed codecs and didn't play, the default theme looked lik KDE revamped.

Soon enough, uninstalling meant formatting a 10Gig partition and installing XP all over again. I was worried that it may not allow XP again stating something like 'you have a newer version of the OS installed already'

Did a smart thing.. asked Google. Sure enough Google saves the day. http://www.istartedsomething.com/20060622/installing-and-uninstall-vista-beta-2-for-dual-boot-with-xp/
Seems MS has made this easy ( a thumbs up for MS !)

Note: We had a dual boot scenario (XP n Vista)- I don't know if this works for an upgrade.
Ans: Type in “e:\boot\bootsect.exe /nt52 ALL /force” (without quotes, and replacing e: with the drive letter of your Vista DVD).

What this does is rollback your boot loader to its previous state. After that, restart and you should see the familiar XP startup screen. Format the Vista partition and you have your space back. Hurrah !!!

"Required Resource is not available" Oh the agony !!!

I recently got my hands dunked into VC++ code. With my limited brain matter and absolutely clean C++ hands, i got flummoxed with a doozy !

Problem:
I need to package my C++ UI (PropertySheet / Page Wizard like UI) into a regular DLL. Then call it from the main application. So here's my sample code...

CPropertySheet obSheet(_T("MySheet"));
CMyPage obPage;
obSheet.AddPage( &obPage );
obSheet.SetWizardMode();
obSheet.DoModal();

This would work except when called from the main App (EXE). When called from the mainApp, it pops an error message "Required Resource is not available" and no UI. The offending line is the DoModal call..

After some help and lots of motivation to find the resolution of this irritating lil thing, here is what I found. When called from the exe, it attempts to read the resources from the Exe file instead of ones in the Dll. (Don't ask me why ?) That explains the resource missing error message.

Resolution:
We need to tell the powers that be .. to do this switch when we do the UI magic.

Set Res Handle to current DLL's path :

HINSTANCE m_hOldResHandle = AfxGetResourceHandle();

TCHAR szPathName[_MAX_PATH];
for Extension DLL
::GetModuleFileName(AfxGetAppModuleState()->m_hCurrentInstanceHandle,
szPathName,
_MAX_PATH)
for reg DLLs
::GetModuleFileName(AfxGetStaticModuleState()->m_hCurrentInstanceHandle,
szPathName,
_MAX_PATH)

AfxSetResourceHandle(::GetModuleHandle(szPathName));


Reset when you are done :
AfxSetResourceHandle( m_hOldResHandle );

So what I did in the end was
wrap up this code into an exported [ _declspec(dllexport) ] static gateway method ShowDialog()
void CGateWayClass::ShowDialog()
{
//set res handle

CPropertySheet obSheet(_T("MySheet"));

CMyPage obPage;
obSheet.AddPage( &obPage );
obSheet.SetWizardMode();
obSheet.DoModal();

//reset res handle

}

"Holy Cow Batman! I'm glad that's over!!"

Test driving Conway's Game of Life in Ruby : Part 2 of 2

Contd... Part 2
Do the evolution (with due apologies to Pearl Jam)
Let’s model our simulation with a file aptly titled ‘GameOfLife.rb’

Rule#1 : Any live cell with fewer than two live neighbours dies, as if by loneliness.
Red
Create new test case class

ts_GameOfLife.rb
require 'test/unit'
require 'Colony'
require 'GameOfLife'

class Test_GameOfLife < Test::Unit::TestCase

def test_Evolve_Loneliness
@obColony = Colony.readFromFile("Colony1.txt");
@obColony = GameOfLife.evolve(@obColony)
assert( !@obColony.isCellAlive(1, 0) )
assert( @obColony.isCellAlive(1, 1) )
assert( !@obColony.isCellAlive(2, 2) )
end
end


Since we want to run all the tests from multiple files now, we can create a “test suite” It is as easy as creating a new file named ts_GameOfLife.rb.
Naming conventions : Prefix ts_ for test suites..
ts_GameOfLife.rb
require 'test/unit'
require 'tc_Colony'
require 'tc_GameOfLife'


We can then run the test suite with
ruby ts_GameOfLife.rb

Green
We traverse each cell in the colony and kill the cell if it is alive
class GameOfLife

def evolve ( obColony )
(0..maxRows).each{ |iRow|
(0..maxCols).each{ |iCol|
killCellIfLonely(obColony, iRow, iCol)
}
}
return obColony;
end

private
def killCellIfLonely(obColony, iRow, iCol)
if (obColony.isCellAlive(iRow, iCol) && ( obColony.getLiveNeighbourCount( iRow, iCol ) < 2 ) )
obColony.markCell( iRow, iCol, false )
end
end

end


Detour
Hmm but we don’t have getLiveNeighbourCount() and markCell(). Let’s test drive
Small red

tc_Colony.rb
def test_GetLiveNeighbourCount()
assert_equal( 2, @obColony.getLiveNeighbourCount(0,0) )
assert_equal( 0, @obColony.getLiveNeighbourCount(0,3) )
assert_equal( 1, @obColony.getLiveNeighbourCount(1,3) )
assert_equal( 0, @obColony.getLiveNeighbourCount(2,3) )
end


Small green
Colony.rb
def getLiveNeighbourCount( iRow, iCol )
iCountOfLiveNeighbours = 0

obCoordinatesToExamine = [
[iRow-1, iCol-1], [iRow-1, iCol], [iRow-1, iCol+1],
[iRow, iCol-1], [iRow, iCol+1],
[iRow+1, iCol-1], [iRow+1, iCol ], [iRow+1, iCol+1] ]

obCoordinatesToExamine.each{ |curRow, curCol|
iCountOfLiveNeighbours = iCountOfLiveNeighbours+1 if (isCellInBounds(curRow, curCol) && isCellAlive(curRow, curCol))
}
return iCountOfLiveNeighbours
end

def isCellInBounds( iRow, iCol)
return ((0...@maxRows) === iRow) &amp;& ((0…@maxCols) === iCol)
end


Small Red
def test_MarkCell()
assert( !@obColony.isCellAlive(0,0) )

@obColony.markCell( 0, 0, true )
assert( @obColony.isCellAlive(0,0), "Should have born now!" )

@obColony.markCell( 0, 0, false )
assert( !@obColony.isCellAlive(0,0), "Should have died now!" )
end


Small Green
def markCell( iRow, iCol, bIsCellAlive )
@colonyGrid[iRow][iCol] = ( bIsCellAlive ? "1" : "0" )
return
end


Refactor
I see some duplication with “1” and “0” – but I’ll refactor with Strike 3.

Hmmm still Red is my evolve test case. Had to use a couple of traces to find the bugger .. I left out a dot in the bold section below. 2 dots – inclusive range, 3 dots – max bound is not part of the range.

def evolve ( obColony )
obNewColony = obColony.clone()

(0...obColony.maxRows).each{ |iRow|
(0...obColony.maxCols).each{ |iCol|
#print "Count = " + obColonySnapshot.getLiveNeighboursCount( iRow, iCol ).to_s
if ( obColony.getLiveNeighboursCount( iRow, iCol ) < 2 )
obNewColony.markCell( iRow, iCol, false )
end
}
}

return obNewColony;
end

Colony.rb
def clone()
#return Colony.new( @maxRows, @maxCols, Array.new(@colonyGrid) )
return Colony.new( @maxRows, @maxCols, @colonyGrid.map{ |elem| elem.clone } )
end


Refactor

Well I got bit with the ranges. I used 0..obColony.maxRows instead of 0…obColony.maxRows and it blew up my test. I think traversing the Colony should be handled / provided by the colony itself. I don’t want anyone making the same mistake.

Let’s move that into a ruby iterator style Colony.each() method. So here it is
Colony.rb
def each
(0...@maxRows).each{ |iRow|
(0...@maxCols).each{ |iCol|
yield iRow, iCol
}
}
end


so now I can use this in GameOfLife#evolve, Colony#to_s and Test_Colony#test_IsCellAlive. Code is even smaller!!

def evolve ( obColony )
obNewColony = obColony.clone()

obColony.each{ |iRow, iCol|
if ( obColony.getLiveNeighboursCount( iRow, iCol ) < 2 )
obNewColony.markCell( iRow, iCol, false )
end
}

return obNewColony;
end

Colony.rb
def to_s
sGridListing = ""
each{ |iRow, iCol|
sGridListing += ( isCellAlive( iRow, iCol) ? "@ " : ". " )
sGridListing += "\n" if iCol.succ == @maxCols
}
return sGridListing
end


Next we need a test for Colony#clone that we sneakily added in to fix this test. We need to check if the cloned colony is similar to the original ..
tc_Colony.rb
def test_Clone
clonedColony = @obColony.clone()
assert_not_equal( clonedColony.object_id, @obColony.object_id, "Cloned object is the same as original" )

@obColony.each{ |iRow, iCol|
assert_equal( @obColony.isCellAlive(iRow, iCol),
clonedColony.isCellAlive(iRow, iCol),
"Contents differ at " + iRow.to_s + ", " + iCol.to_s )
}
end


Ok let’s take a look at our next rule.
Rule#2 - Any live cell with more than three live neighbours dies, as if by overcrowding.
Let’s create a test colony for this rule.

Colony_Overcrowding.txt
3, 4
0 0 1 0
1 1 1 1
0 0 1 1


Red
tc_GameOfLife.rb
def test_Evolve_Overcrowding
arrExpected = [false, false, true, false,
false, false, false, false,
false, false, false, true]

@obColony = Colony.readFromFile("Colony_Overcrowding.txt");
@obColony = GameOfLife.new.evolve(@obColony)

iLooper = 0
@obColony.each{ |iRow, iCol|
assert_equal( arrExpected[iLooper], @obColony.isCellAlive(iRow, iCol),
"Mismatch at " + iRow.to_s + ", " + iCol.to_s )
iLooper += 1
}
end


Green
Ok so we kill em off even if there are too many of them in an area ! Just another OR clause …
def evolve ( obColony )
obNewColony = obColony.clone()

obColony.each{ |iRow, iCol|
if obColony.isCellAlive(iRow, iCol) and
( (obColony.getLiveNeighboursCount(iRow,iCol) < 2) or (obColony.getLiveNeighboursCount(iRow,iCol) > 3) )
obNewColony.markCell( iRow, iCol, false )
end
}
return obNewColony;
end



Rule#3 - Any dead cell with exactly three live neighbours comes to life.
Red

Colony_Birth.txt
5, 5
0 0 0 0 1
1 1 0 0 1
0 0 1 0 0
0 0 1 0 0
0 0 1 0 0

tc_GameOfLife.rb
def test_Evolve_Birth
arrExpected = [false, false, false, false, false,
false, true, false, true, false,
false, false, true, true, false,
false, true, true, true, false,
false, false,false, false, false
]

@obColony = Colony.readFromFile("Colony_Birth.txt");
@obColony = GameOfLife.new.evolve(@obColony)

iLooper = 0
@obColony.each{ |iRow, iCol|
assert_equal( arrExpected[iLooper], @obColony.isCellAlive(iRow, iCol),
"Mismatch at " + iRow.to_s + ", " + iCol.to_s )
iLooper += 1
}
end


Green
GameOfLife.rb
def evolve ( obColony )
obNewColony = obColony.clone()

obColony.each{ |iRow, iCol|
if obColony.isCellAlive(iRow, iCol)
if ( (obColony.getLiveNeighboursCount(iRow,iCol) < 2) or (obColony.getLiveNeighboursCount(iRow,iCol) > 3) ) then
obNewColony.markCell( iRow, iCol, false )
end
else
obNewColony.markCell( iRow, iCol, true ) if ( obColony.getLiveNeighboursCount(iRow,iCol) == 3 )
end
}
return obNewColony;
end


Hmm… that broke my overcrowding test. Oh yeah, as per the new rule, there is a newborn in that colony. Let me just update the expected results as

arrExpected = [false, false, true, true,
false, false, false, false,
false, false, false, true]

Refactor
The tests are duplicating code to traverse and check the Colony with the expected results/array. Extract method
private
def assert_colonies_are_similar( arrExpectedValues, obColony )
iLooper = 0
obColony.each{ |iRow, iCol|
assert_equal( arrExpectedValues[iLooper], obColony.isCellAlive(iRow, iCol),
"Mismatch at " + iRow.to_s + ", " + iCol.to_s )
iLooper += 1
}
end


refactored test looks like :
def test_Evolve_Overcrowding
arrExpected = [false, false, true, true,
false, false, false, false,
false, false, false, true]

obColony = Colony.readFromFile("Colony_Overcrowding.txt");

assert_colonies_are_similar( arrExpected, GameOfLife.new.evolve(obColony) )
end


And that’s a wrap !!
Finally I quickly write up what I learn is called a “Driver”
ARGV is an array that contains the command line parameters. I’ll take two.. thank you!
One for the input colony text file and the second for the number of generations.
We then just evolve and keep printing the colony.

if (ARGV.length != 2) then
puts "Usage : ruby GameOfLife.rb [ColonyTextFile] [No of generations]"
exit
end
if (!File.exist?(ARGV[0])) then
puts "Specified Colony File does not exist"
exit
end


obColony = Colony.readFromFile(ARGV[0]);

0.upto(ARGV[1].to_i) { |iGenNo|
puts obColony.to_s + "Gen:" + iGenNo.to_s

obColony = GameOfLife.new.evolve(obColony)
}

Run it as
L:\Gishu\Ruby\GameOfLife>ruby GameOfLife.rb TestColony.txt 100

You can also redirect output to a file
L:\Gishu\Ruby\GameOfLife>ruby GameOfLife.rb TestColony.txt 100 > a.txt


Open it up in Notepad for example… resize the height such that you can see one line below the Gen : X line. Now use the PgUp or PgDown keys to see evolution in action!!

By the way the TestColony.txt file contains the colony called Gosper’s glider gun. Watch the output and read the wiki to know why ! 

Hey this program is slow for 100+ generations.. I am at chapter 2 and find that there is another better way to implement this… a different perspective on this problem that cuts down the grid traversal for better performance.

Till next time….
Gishu
Feb 18, 2006

[Update : ]
A special Thanks to http://blog.wolfman.com/articles/2006/05/26/howto-format-ruby-code-for-blogs
for a good tip and an even cooler ruby script on pretty-formatting ruby code for html display.

Also the ruby-185-21 windows package seems to be broken. the gem install fails with a
"getaddrinfo: no address associated with hostname". I went back to 1.8.2 to get this done today !

Test driving Conway's Game of Life in Ruby : Part I

Well I had Ruby on my to-learn list for some time now. The pickaxe (don't leave your ruby-home without it) was getting rusty in a corner. I also had another book, Data Structures and Program Design in C, something I had picked up for the interview of my first job. So I decided why not translate the examples in the latter in Ruby – two birds with one stone ??

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