Last night I spend about 4 hours writing some Ruby code to solve a particular Coding Problem. That time includes reading the problem description, creating the design, writing specs and code, and looking up API calls. It was mad fun.The coding problem dealt with a couple of Mars rovers. The problem description was very detailed; I wish most clients had as detailed requirements.
Design Time
After reading the problem description a couple of times to get a feel for what I’m trying to solve I opened a new document in TextMate (which rocks and every Mac developer should own a copy) and started putting down my thoughts in a semi-freeform manner. I could see I would have a Rover, of course. However I also seen that the Rover would have a Motor and a Navigational System; the Navigational System would also have a GPS Unit. I then wrote down notes on movement and direction; I also defined movement in a particular direction in terms of the x,y coordinates. Coding Time!
BDD Time
I like using Rspec and wanted to use it for my coding solutions. It provides a nice narrative over my code base; what I needed since I would not be creating a ‘Main’ script. So having just wrote down some design notes I thought to myself, “What is the driving context? What’s going to manage my Rovers?” I came up with the idea of a Mission - back to my design notes.
Mission Design
What is this Mission object and what does it do? Well, it defines how many Rovers are going to be used. It also defines how large the movement area will be. It’s also responsible for issuing the commands to the Rovers and handling any errors and output. Cool - now back to coding…Coding:
I created a Mission Spec but then switched gears. I thought I should probably define my Rover first to make sure it knows how to handle all the input I’m (the Mission) is going to throw at it. Super! I create a Rover Spec. Then in an iterative fashion I create my tests and the simplest implementation that works.
Iteration 1
First my Rover has a default location and heading and knows how to report this information separately and combined. Pretty simple - all hard coded values because the spec just expects values.
it "should be pointing North" do
@rover.heading.should == 'N'
end
it "should be located on 0 0" do
@rover.location.should == '0 0'
end
it "should be positioned at 0 0 N" do
@rover.position.should == '0 0 N'
end
Iteration 2
My Rover learns to spin to the left or the right. This is a little trickier since it changes the heading of my rover. I add the Navigational System because it knows about headings, or compass directions.
it "should be pointing West" do
@rover.input('L'
@rover.position.should == '0 0 W'
end
it "should be pointing East" do
@rover.input('R'
@rover.position.should == '0 0 E'
end
it "should be pointing South, spinning Left" do
@rover.input('L').input('L'
@rover.position.should == '0 0 S'
end
it "should be pointing South, spinning Right" do
@rover.input('R').input('R'
@rover.position.should == '0 0 S'
end
it "should be pointing East, spinning Left" do
@rover.input('L').input('L').input('L'
@rover.position.should == '0 0 E'
end
it "should be pointing West, spinning Right" do
@rover.input('R').input('R').input('R'
@rover.position.should == '0 0 W'
end
it "should be pointing North, spinning Left then Right" do
@rover.input('L').input('R'
@rover.position.should == '0 0 N'
end
it "should be pointing North, completing a full spin" do
4.times { @rover.input('L'
}
@rover.position.should == '0 0 N'
end
Iteration 3
The Rover learns to move. Now I have to add the GPS Unit since it knows where the rover is located. It was at this time that I realized, for this exercise the Motor class adds no value to the solution. I leave it in anyways. I had to tweak the Navigational system so that it knew the size of the movement area. I did this because I didn’t want my Rover to travel beyond the boundaries of the movement area.
it "should not move pass border" do
@rover.position.should == '0 0 N'
@rover.input('M'
@rover.position.should == '0 0 N'
end
it "should move one unit" do
@rover.set_grid_system(1, 1)
@rover.position.should == '0 0 N'
@rover.input('M'
@rover.position.should == '0 1 N'
@rover.input('M'
@rover.position.should == '0 1 N'
end
Iteration 4
I felt at this point I had enough behavior to start implementing the Mission spec. I started creating the spec and the corresponding Mission object. I quickly discovered I didn’t have a way to set the size of the movement area; Rover, Navigational System updated. I then discovered I didn’t have a way to set the initial position of a Rover; back to the Rover Spec, implemented. Lastly I wanted to process the command of movements as entered: a single line of characters. This was added without a corresponding spec test (bad Mel).
it "should have an initial position of 3 3 E" do
@rover.position.should == '0 0 N'
@rover.initial_position('3 3 E'
@rover.position.should == '3 3 E'
end
Iteration 5
Thank goodness I had tests! My movement wasn’t working as expected. My initial set of tests didn’t cover a particular case. However, when implementing the Mission Spec and not getting the expected results I was able to discover a typo if my movement logic.
before(:each) do
@mission = Mission.new
@mission.define_plateau(5,5)
end
it "should command Rover 1 from [1 2 N] to [1 3 N]" do
rover_1 = @mission.rover_1
rover_1.initial_position('1 2 N'
rover_1.issue_command('LMLMLMLMM'
rover_1.position.should == '1 3 N'
end
it "should command Rover 2 from [3 3 E] to [5 1 E]" do
rover_2 = @mission.rover_2
rover_2.initial_position('3 3 E'
rover_2.issue_command('MMRMMRMRRM'
rover_2.position.should == '5 1 E'
end
So all in all I had a great time last night (I’m sure I’m missing some details - I’m sleepy for goodness sake). I’ll try to add bits of my code to this blog if I can figure out how to do code highlighting.
Cheers!


