So many good ideas. But we need to ship it. We’ll talk about that—and get ready to do it! UPDATE: Walking skeleton works!

I’ve been thinking about all kinds of things that will—or might—be useful or nice for the Robot World. Rotations of Knowledge Lenses, graphical effects. DisplayPackets, on and on. At least some of these are probably good enough ideas to actually do them. However … we need to ship it. Let’s talk about why.

The following comes from a bit of a discussion we had last night (Tuesday evening) at the weekly meeting of the Friday Night Coding group. I’m sure it was GeePaw Hill who first brought it into focus. He said that while he seemed to have Many More Much Smaller Steps as his lead topic, he felt that the most important single thing in software is to ship the product immediately and often.

I have long believed this. Since according to Kent Beck, all software methods are based on fear, my own focus is on always having a shippable version of the software ready to go, because my bad experiences in business—and they have been many—arguably boil down to “We were doing really well, but stakeholder patience ran out before we could ship it”.

I believe that the best way to avoid the running out of patience trap is to always have a visible version to show, containing all the work to date. It changes the conversation from “Aren’t you _ers done yet” to “If we just had X, customer Y could use this”. I don’t think having a shippable version solves all our problems and life is instantly good, but it can allow us to change the conversation from one side attacking the other to everyone collaborating to make an existing thing better.

Therefore, we need to ship this product, so that our stakeholders can see that we have some good things working.

The Target “Product”

My rough plan for the first release is:

  1. Start the program, show the robot on a blank plain;
  2. Scan, show results of the scan;
  3. Move, show the remembered scan info has adjusted;
  4. Scan again, show results of two scans.

Because the display is hard to see on my Mac’s screen, I plan to adjust the colors as well, which won’t take long and will improve the demo.

We’ll want to make clear to our stakeholders what is complete and what is in rudimentary form. We are working toward common understanding, not toward impressing them by letting them imagine that things are more complete than they are. That could lead to impossible expectations and exactly the disaster we’re trying to avoid by showing our work.

I happen to know that my stakeholders will have a solid appreciation of the system’s shortcomings, but yours will need a bit more help than mine do, since mine are me.

I think what I’d like to do today is rig up a simple keyboard interface to the game. I have learned that a Robot can move more than one square at a time, so I am planning to use the number keys to move 1, 2, and so on. Numbers larger than ten will not be supported. And let’s use S for scan.

If I’m not mistaken, the Codea keyboard function receives typed keys. I think we have to do showKeyboard() to enable it. Let’s find out.

function keyboard(key)
    print("Key: "..key)
end

function touched()
    if CodeaUnit_Lock then
        CurrentRobot = Robot:setUpGame()
        showKeyboard()
        CodeaUnit_Lock = false
    end
end

Sure enough, when I type things, I get the echo in the console:

screen shows some echoed characters as keyed

I think we should just send the keys to the robot, at least for now:

function keyboard(key)
    print("Key: "..key)
    CurrentRobot:keyboard(key)
end

I think it’ll be useful to see the echos for a while. Is it “echos” or “echoes”? Apparently either. We should really TDD this, shouldn’t we? OK, let’s.

        _:test("Robot does scan on s key", function()
            local world = World()
            world:addFactAt("O", 3,0)
            local robot = Robot("Louie", world)
            _:expect(robot:factAt(3,0)).is(nil)
            robot:keyboard("s")
            _:expect(robot:factAt(3,0)).is("O")
        end)

Test should fail for lack of keyboard method, but there’s another failure as well, which I half expected:

4: Robot does scan on s key  -- 
Actual: O, 
Expected: nil
4: Robot does scan on s key -- TestRobot:53: attempt to call a nil value (method 'keyboard')

Recall that yesterday we did a scan automatically when we create the robot:

function Robot:init(name,aWorld)
    self._world = aWorld
    self._x, self._y = aWorld:launchRobot(name, self)
    self._name = name
    self:scan()
end

That was a stopgap to get my initial display to work. Now we should remove that, which will make our test for scanning fail, remove the first error above, and leave the second.

3: Set Up Game -- TestRobot:74: attempt to index a nil value (field 'knowledge')
4: Robot does scan on s key -- TestRobot:74: attempt to index a nil value (field 'knowledge')

Ah. We do need to provide an empty Knowledge in init. (And we also have to merge the result of a scan into the existing knowledge: we don’t do that yet.)

function Robot:init(name,aWorld)
    self._world = aWorld
    self._x, self._y = aWorld:launchRobot(name, self)
    self._name = name
    self.knowledge = Knowledge()
end

Now we should get the errors I expect:

3: Set Up Game  -- 
Actual: nil, 
Expected: O
3: Set Up Game  -- 
Actual: nil, 
Expected: P
4: Robot does scan on s key -- TestRobot:53: attempt to call a nil value (method 'keyboard')

Right. Add a scan to the first test:

        _:test("Set Up Game", function()
            local robot = Robot:setUpGame()
            _:expect(robot:factAt(-5,0)).is("O")
            _:expect(robot:factAt(3,0)).is("P")
        end)

Let’s check to be sure the scan does not happen and then that it does:

        _:test("Set Up Game", function()
            local robot = Robot:setUpGame()
            _:expect(robot:factAt(-5,0)).is(nil)
            _:expect(robot:factAt(3,0)).is(nil)
            robot:scan()
            _:expect(robot:factAt(-5,0)).is("O")
            _:expect(robot:factAt(3,0)).is("P")
        end)

I expect 3 to run now. Right. We’re left with:

4: Robot does scan on s key -- TestRobot:56: attempt to call a nil value (method 'keyboard')

And we implement:

function Robot:keyboard(key)
    self:scan()
end

I decide not to do anything about key dispatch. For now all keys scan.

Tests run.

I think that now, when I run the game, it’ll start with a blank grid and add the obstacle and pit when i type something.

That works, and I am so proud of it that I’ll make a movie of it for all to admire and marvel.

short movie showing only R on grid, then obstacle and pit appear

I think we should implement motion. Let’s just accept the “1” key for now. Add a test:

        _:test("Robot moves forward on 1 key", function()
            local world = World()
            world:addFactAt("O",3,1)
            local robot = Robot("Louie", world)
            _:expect(robot._y).is(0)
            robot:scan()
            _:expect(robot:factAt(3,0)).is(nil)
            robot:keyboard("1")
            _:expect(robot._y).is(1)
            robot:scan()
            _:expect(robot:factAt(3,0)).is("O")
        end)

Test expecting the fails on y = 1 and nil for O.

5: Robot moves forward on 1 key  -- 
Actual: 0, 
Expected: 1
5: Robot moves forward on 1 key  -- 
Actual: nil, 
Expected: O

Now we can do the keyboard stuff:

function Robot:keyboard(key)
    if key == "s" then
        self:scan()
    elseif key == "1" then
        self:move(0,1)
    end
end

This is only good enough for north motion, we’re not supporting turning yet. I do expect the tests to run, and they do. I go ahead and test in the game. Works as expected:

movie shows blank, scan results. then scan results move down.

Note that after a second scan, the things we’ve moved past disappear. We do not as yet merge knowledge.

We do, however, have our intended demo, modulo better colors. Commit: Game understands “s” and “1” key to scan and move north.

I’m going to go back to my little spike to select better coloring. I think I just need to make the screen background a bit brighter. But I think that’s going to require some improvements to the little display packets.

Improving Display

I made these cryptic notes:

  • bg = g 48
  • txtfill = g
  • obsfill = g
  • pitfill = 0
  • stroke = g 128

In the above I mean g to be color (0,256,0) and the additional numbers are transparency. Now let’s see what the game display code currently does. I expect we’ll want to refactor it a bit.

function drawScreen()
    local size = 40
    noFill()
    for x = -10,10 do
        for y = -10,10 do
            local fact = CurrentRobot:factAt(x,y)
            if x == 0 and y == 0 then
                fact = "R"
            end
            local gd = GridDisplay:display(fact)
            if gd.color then
                fill(gd.color)
            else
                noFill()
            end
            rect(x*size,y*size,size,size)
            if gd.text then
                fill(0,256,0)
                text(gd.text, x*size,y*size)
            end
        end
    end
end

Yes, let’s extract a bit here:

function drawScreen()
    local size = 40
    noFill()
    for x = -10,10 do
        for y = -10,10 do
            drawCell(x,y, size)
        end
    end
end

function drawCell(x,y, size)
    local fact = CurrentRobot:factAt(x,y)
    if x == 0 and y == 0 then
        fact = "R"
    end
    local gd = GridDisplay:display(fact)
    if gd.color then
        fill(gd.color)
    else
        noFill()
    end
    rect(x*size,y*size,size,size)
    if gd.text then
        fill(0,256,0)
        text(gd.text, x*size,y*size)
    end
end

We’ll let that ride. I think we’re pretty close on the values. Here’s the display function as it stands:

function GridDisplay:display(content)
    if content == "O" then
        return {color=color(0,256,0,128)}
    elseif content == "P" then
        return { color=color(0)}
    elseif content == "R" then
        return {text="R"}
    else
        return {}
    end
end

We have a test for that:

        _:test("GridDisplay", function()
            local gd = GridDisplay()
            _:expect(gd:display(nil).color).is(nil)
            _:expect(gd:display("O").color).is(color(0,256,0,128))
            _:expect(gd:display("P").color).is(color(0))
            _:expect(gd:display("R").color).is(nil)
            _:expect(gd:display("R").text).is("R")
        end)

I think we just want for nil to display a color:

            _:expect(gd:display(nil).color).is(color(0,256,0,48))

Test to fix the issue:

1: GridDisplay  -- 
Actual: nil, 
Expected: (0, 256, 0, 48)

And:

function GridDisplay:display(content)
    if content == "O" then
        return {color=color(0,256,0,128)}
    elseif content == "P" then
        return { color=color(0)}
    elseif content == "R" then
        return {text="R"}
    else
        return {color=color(0,256,0,48)}
    end
end

These do need to be objects, but baby steps. Test, and check the game view.

pic shows proper colors but black background on R

I think we need a background for the R. Change the test and the table:

        _:test("GridDisplay", function()
            local gd = GridDisplay()
            _:expect(gd:display(nil).color).is(color(0,256,0,48))
            _:expect(gd:display("O").color).is(color(0,256,0,128))
            _:expect(gd:display("P").color).is(color(0))
            _:expect(gd:display("R").color).is(color(0,256,0,48))
            _:expect(gd:display("R").text).is("R")
        end)
...
    elseif content == "R" then
        return {text="R", color = color(0,256,0,48)}
...

And in game:

view looks good

I didn’t change the obstacle to be pure green, but I think it looks good as it is, so belay that order. Commit: adjust colors.

Here’s another demo with the new colors:

third movie with better contrast

I think we’re good for today. Let’s sum up.

Summary

It’s only Wednesday, and we have a decent demo for the end of Friday. We can display the radar screen, scan to show objects, and then move. If we then scan again, memory goes away, but we aren’t promising that for this week, and we’ll surely have it in a day or two anyway.

We can now sit down with our stakeholders, and instead of showing them a burndown or a powerpoint presentation, we can show them the product, show them what it looks like and what it can do. We’ll show them what it can’t do as well. Then they’ll give us feedback and odds are it’ll be about what they just saw.

In a situation with stakeholders who are very untrusting, it may well take some time to get them focused on Here’s the product, what do you suggest, but I think most any stakeholders will come around quickly. If they do not, one may need to think about moving on.

Looking at what we have inside the program, much of it is pretty simplistic and ad-hoc. But we can see things starting to take shape. We have a little table defining how to display each kind of cell. That table may want to evolve into a small class, and it probably wants to have its own display method rather than having it coupled to the one that’s currently in Main.

We aren’t handling our screen memory at all, nor are we allowing the robot to turn. I think those will be important capabilities. (Master of understatement here.)

There are significant issues in the message-passing area. There are specifications for the form of all our messages to the World and its replies to us. The real spec, of course, includes translation to JSON and back to our internal format, and transmission of the JSON string across a socket connection. I frankly doubt that we’ll do either of those, instead calling them “left to the reader”, meaning “I don’t want to do this”.

My purpose in this little series isn’t to create a game, but to show how I’d address the initial stages of the game, in counterpoint to how Hill has gone about it. I suppose there is a slim chance that I’ll think of a way to turn this into a real game, but I frankly doubt that we’ll go that way, even though I do need something fun to work on.

Overall, here we are on Robot 8, probably three working days in, and we have a demonstrable walking skeleton that we can use to guide the conversation with our stakeholders.

This is the way.

See you next time!