I think we’re ready to draw something. How can we make the drawing more testable? I have a p-baked idea, for small p.

You’ve heard of half-baked ideas? This one is about p=0.15 baked, but I have high hopes. We’ll see how it goes.

First let’s do a bit of “design”, figuring out our initial display ideas. My screen is 1366x1024. I figure we should have a square display, spanning some number of coordinates centered on the robot. Let’s do a little display spike in a new project.

I think I’ll go old school on the display, make it green on black like an old CRT tube. Some fiddling and I get this picture:

grid of green lines on black. R in middle square

The draw code is this:

function draw()
    local size = 40
    local x = 0
    local y = 0
    background(40, 40, 40)
    strokeWidth(2)
    stroke(0, 255, 0, 96)
    noFill()
    --fill(128,150,128)
    rectMode(CENTER)
    translate(WIDTH/2,HEIGHT/2)
    for x = -10,10 do
        for y = -10,10 do
            rect(x*size,y*size,size,size)
        end
    end
    textMode(CENTER)
    fill(0,256,0)
    fontSize(30)
    text("R",0,0)
end

The stroke is drawn 2 wide, but only 96/256 opacity. One wide wasn’t visible enough. Note that I translated the screen so that 0,0 is at WIDTH/2,HEIGHT/2. I’ve found that using Codea’s scale feature and scaling up from single pixel logic like our scan being at 1-5, doesn’t work well, so I’m planning to scale manually.

Anyway, now we have the basic numbers we need for drawing in the game. This was a spike, and I am under great pressure from fanatics to delete the code. OK, I succumb to the pressure. We’ll just pop back into the robot program.

How Can We Test This?

In the Dungeon program, 300+ articles and counting, I didn’t figure out much if any testing for the display. I’d like to do better here, even though I expect the display code to be rather simple.

My basic plan for the initial game display is to display the grid, centered around the player’s robot, and to display the various things we’ve scanned in cells relative to the player. We can do that pretty readily, because the facts are all recorded in the player’s robot’s Knowledge. We can use a Lens focused on the robot to give us relative coordinates.

I’m thinking that what I’d like to do is display obstacles as solid blocks, and pits as black. Let’s go back to the spike and see if that will look OK.

I add this:

    fill(0)
    rect(5*size,5*size,size,size)
    fill(0,256,0, 128)
    rect(-3*size,2*size,size,size)

And get this:

grid showing R in middle, a black square upper right, green square upper left

I tried full green and it was too bright, so I made it half transparent. Looks OK to me for starters.

OK, where were we?

It seems to me that we might be able to do the drawing this way: In one pass over x from -10 to 10, y from -10 to 10, fetch the factAt(x,y) and convert it to a little object, a display packet, containing a fill color, and an optional letter.

Let’s imagine a little object, GridDisplay that does the conversion. And let’s TDD it.

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

This should drive out the object:

1: GridDisplay -- TestGridDisplay:16: attempt to call a nil value (global 'GridDisplay')

And:

GridDisplay = class()

And now we fail on display:

1: GridDisplay -- TestGridDisplay:17: attempt to call a nil value (method 'display')

And we code:

function GridDisplay:display(content)
    
end

Expectation should fail with a nil. Oops not quite:

1: GridDisplay -- TestGridDisplay:17: attempt to index a nil value

Return an empty table. I’m going inch by inch just for fun.

function GridDisplay:display(content)
    return {}
end

Expectation should fail:

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

Fill in the table:

function GridDisplay:display(content)
    return {color=color(0,256,0,128)}
end

Expect success. Yes. I think we can draw the game now if we care to:

Let’s try it. The base drawing code looks like this:

function draw()
    background(40)
    if CodeaUnit and CodeaUnit_Lock then 
        _.showTests()
    else
        background(128)
        stroke(0)
        fill(0)
        text("Your App Here", WIDTH/2,HEIGHT/2)
    end
end

We’ll call a plain function for now:

function draw()
    background(40)
    if CodeaUnit and CodeaUnit_Lock then 
        _.showTests()
    else
        drawGame()
    end
end

And we’ll need to fill it in:

function drawGame()
    setUpDrawing()
    drawScreen()
end

If I touch the screen after running the tests, this will crash, as the two functions there are not written. So …

function setUpDrawing()
    background(40,40,40)
    rectMode(CENTER)
    textMode(CENTER)
    translate(WIDTH/2, HEIGHT/2)
    stroke(0,256,0,96)
    strokeWidth(2)
end

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

This just draws the grid ad hoc, and it looks right on the screen. Now we want to draw the real thing.

Let’s try this:

function drawScreen()
    local size = 40
    noFill()
    for x = -10,10 do
        for y = -10,10 do
            local fact = CurrentRobot:factAt(x,y)
            local gd = GridDisplay:display(fact)
            if gd.color then
                fill(color)
            else
                noFill()
            end
        end
    end
end

Houston, we have a problem. We have no game set up, no world, no robot. I’ve just finessed this here by saying CurrentRobot.

We need to set up the initial situation and do it just once. Let’s modify touched:

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

Now all should be fine until we touch the screen, at which point we should crash for want of “setUpGame”:

Main:22: attempt to call a nil value (method 'setUpGame')
stack traceback:
	Main:22: in function 'touched'

Oddly, that happens twice. I’ll ignore that, probably something about multiple touches. We need the method:

function Robot:setUpGame()
    local world = World:setUpGame()
    return Robot("Louie", world)
end

I should state clearly that at this point we are just building a rope bridge across the various chasms. We’ll figure out a solid bridge soon. Now we want to set up a game world:

function World:setUpGame()
    local world = World(20,20)
    world:createObstacle(5,-3,-5,-3)
    world:createPit(2,3,-2,4)
    return world
end

I’ve put a big obstacle on the left and a pit on the right. I am not sure what to expect when I run this, most likely some random error. This is very experimental.

TestRobot:59: attempt to index a nil value (field 'knowledge')
stack traceback:
	TestRobot:59: in method 'factAt'
	Main:46: in function 'drawScreen'
	Main:29: in function 'drawGame'
	Main:16: in function 'draw'

Ah. The robot needs to scan or at least start with an empty knowledge. Let’s try scan:

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

Try it.

Main:49: bad argument #1 to 'fill' (number expected, got function)
stack traceback:
	[C]: in function 'fill'
	Main:49: in function 'drawScreen'
	Main:29: in function 'drawGame'
	Main:16: in function 'draw'

Hm. We got rather far along. What’s this?

function drawScreen()
    local size = 40
    noFill()
    for x = -10,10 do
        for y = -10,10 do
            local fact = CurrentRobot:factAt(x,y)
            local gd = GridDisplay:display(fact)
            if gd.color then
                fill(color)
            else
                noFill()
            end
        end
    end
end

I forgot to fetch the color:

                fill(gd.color)

Again. I think I’ll get a grid. I don’t, but it’s because I didn’t display anything yet. … not entirely clever …

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

Try again. I get a very green grid.

very green grid

We need to fix the test and code in GridDisplay. We want the default color to be absent (noFill). Let’s add some lines to the test:

        _: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))
        end)

This will fail the first and third expect.

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

OK, we’ll write this out longhand for now:

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

I think this passes the test. It does. When I touch the screen, I expect good news.

I do not get good news. I get a blank grid. I wonder where that’s happening. Am I getting no facts? Seems it’s the only possibility.

Let’s write a test for that:

Test setUpGame

In writing the test, I find that I didn’t set up the game as I thought I had. I was putting in the coordinates incorrectly. Let me recast that:

function World:setUpGame()
    local world = World(20,20)
    -- left top right bottom
    world:createObstacle(-5,3,-5,-3)
    world:createPit(3,2,3,-2)
    return world
end

I think that’s a long obstacle at x = -5 and a long pit at x = 3.

Now back to the test, but I can’t resist looking at the picture.

obstacle and pit shown

Woot! The picture is good. And we realize we didn’t display the R …

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)
        end
    end
end

And let me extend display:

        _: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)

The new rule is that robot returns no color, but a text. This will be easy to implement but shows a problem in our display scheme.

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

Tests should run.

Now to fix the drawing:

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

This should display correctly:

grid showing green obstacle on left black pit on right, R in middle

Perfect! But I was here to test setUpGame, wasn’t I? Let’s get back to that:

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

Test passes. Let’s commit: Initial display includes obstacle and pit. player robot display faked.

Let’s reflect … and possibly sum up, as we have hit somewhat of a milestone in our walking skeleton.

Reflection

I think I like the idea of the little display control table that tells the display loop what to draw at each location. By converting the display information to data rather than just asking each thing to draw itself, we open the door to testing what will be displayed rather than checking by eye.

We’ll still have to check to see that it looks right, but if we do most of the changing in the data table, we’ll keep the display code very simple and be able to test drive more of the code.

I think this is a promising idea, and I’m promoting it to about 0.75-baked.

You might be questioning the wisdom of representing map “facts” with nothing more than a letter, and you would be wise to do so. However, so far there has been no behavior associated directly with things in the scan. Perhaps now they might have the ability to return their own display control table. That could be reason enough for an object to be born.

One issue here is that while in our current implementation, we can readily share objects between World and Robot, such as Knowledge, in the “real” game, the only things passing back and forth will be text, intended to be JSON descriptions of some rather simple tables. So while we might build an object into the Robot’s knowledge, we’d base it on a rather simple text message coming back from our scan of the world.

I’m imagining that all that will be hidden inside the perhaps someday to be written communications objects. We’ll see.

With respect to our walking skeleton, we’re nearly there. I’ve proposed that we’ll allow the robot to “move”, showing that it remembers objects that it has scanned. I think we’ll move it with keyboard commands, though I’m not certain of that. Maybe tomorrow. Certainly this week.

Syntax

Oh. I’m reminded that I have decided that I don’t like the idea of naming member variables with a leading underbar. The reason is that to type “self._foo”, my fourth finger has to type the “l”, then down to the “.”, then immediately up to the “_”, and I find that awkward, irritating, and error-prone. So I’ll be migrating away from that over time. I’m left with the concern that an accessor method can’t have the same name as the member variable, and I don’t like getX kinds of accessors.

I suppose we just shouldn’t have accessors.

Summary

I think we’re good for the day. We have a starting picture for our stakeholders to look at, and it has a certain retro kind of look like they (I) asked for.

And we can actually do some testing of the display logic. That’s a first.

Stakeholder:
I see on my Mac screen that the black square for the pit doesn’t show up very well. Possibly we’ll need to improve our coloring.
Developer:
It works on my iPad!

See you next time!