Well, I don’t know. A bit more on motion. Maybe a start on scan/look? Oh and a better look for the robot?

The robot can now turn left and right, in the code, and left on the screen, using the “r” key to mean either rotate or right, I’m not sure which. But we use “l” to mean look, so that w and s and be forward and back, because WASD is a common set of keystrokes in games for left, forward, back, right.

Presently, scanning works, but screen rotation does not. I had planned to rotate the screen so that the robot always moved “upward” no matter how it was aimed. Just now, it will move side to side, and the screen orientation does not change:

moving robot, north-oriented screen

In the movie above, we see the robot wandering, and we see the effect of our current scan limit being 5, but there’s no way to know which way the robot is facing. We either need to rotate the screen so that you’re always facing the top of the screen, or to give some indication of which way you’ll move.

Let’s make an oriented icon represent the robot.

Robot Display Icon

I don’t even remember how drawing works. Let’s have a look. Ah. It all comes down to this, drawing at world coordinates x and y, 10 on either side of wherever the robot is.

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 have this little gimmick as well:

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

This just returns a little structure that tells the drawing whether to draw just a fill (for obstacles or pits) or a letter (for other facts, of which we have none really, or the robot.

Let’s try something. In Codea’s assets, there are a lot of sprites and such. Let’s enhance the R structure to include an asset.

    elseif content == "R" then
        return {asset=asset.builtin.UI.Yellow_Slider_Up, color = color(0,256,0,48)}

Now in the draw code, we’ll check for that option:

    if gd.asset then
        pushMatrix()
        scale(0.8, 0.8)
        sprite(gd.asset,x,y)
        popMatrix()
    end

The 0.8 was cut in by looking to see how big the arrow appears. It looks like this:

robot represented by yellow arrow

I think I like that. The change broke one of GridDisplay’s tests. And we want to make it rotate. Let’s do that first. I propose some grotesque hackery here for a bit. Please consider holding your nose.

    if x == 0 and y == 0 then
        dir = CurrentRobot._direction
        fact = "R"..dir
    end

I’m going to pass in “RN” for robot facing north, etc. This may not survive long term but I’m trying to get a rope cast across this ravine so that we can get to the other side.

Now in the display:

function GridDisplay:display(content)
    if content == "OBSTACLE" then
        return {color=color(0,256,0,128)}
    elseif content == "PIT" then
        return { color=color(0)}
    elseif content and content:sub(1,1) == "R" then
        local a = self:assetInDirection(content)
        return {asset=a, color = color(0,256,0,48)}
    else
        return {color=color(0,256,0,48)}
    end
end

So if we get any of RN, RE, RS, RW, we’ll do this:

local assets = {
    RN=asset.builtin.UI.Yellow_Slider_Up,
    RE=asset.builtin.UI.Yellow_Slider_Right,
    RS=asset.builtin.UI.Yellow_Slider_Down,
    RW=asset.builtin.UI.Yellow_Slider_Left,
}

function GridDisplay:assetInDirection(Rdir)
    return assets[Rdir]
end

And the result is quite nice:

yellow arrow robot moving on screen

I rather like that. We have a broken test:

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

Now I could have worked on this test first, and driven out the result, but I needed to see it on the screen. So we’ll update the test now:

        _:test("GridDisplay", function()
            local gd = GridDisplay()
            _:expect(gd:display(nil).color).is(color(0,256,0,48))
            _:expect(gd:display("OBSTACLE").color).is(color(0,256,0,128))
            _:expect(gd:display("PIT").color).is(color(0))
            _:expect(gd:display("RN").color).is(color(0,256,0,48))
            _:expect(gd:display("RE").color).is(color(0,256,0,48))
            _:expect(gd:display("RS").color).is(color(0,256,0,48))
            _:expect(gd:display("RW").color).is(color(0,256,0,48))
            _:expect(gd:display("RN").asset).is(
                    asset.builtin.UI.Yellow_Slider_Up)
            _:expect(gd:display("RE").asset).is(
                    asset.builtin.UI.Yellow_Slider_Right)
            _:expect(gd:display("RS").asset).is(
                    asset.builtin.UI.Yellow_Slider_Down)
            _:expect(gd:display("RW").asset).is(
                    asset.builtin.UI.Yellow_Slider_Left)
        end)

Kind of boring but it’s the right thing to have done.

Now as to the code itself … we have no checking of any kind. If the display code asks for a fact “RQ”, we’re not going to display anything, or we might even explode … though I’m rather certain that a nil asset just doesn’t display.

So this is not ready for prime time, but we have a lot of display work ahead of us, so I think it’ll do for now.

You may hate me for this, but I think the stakeholders will love it, so commit: Robot now displays as yellow arrow pointing in its direction of motion.

WASD

Now that we’re here, let’s change the motion to use WASD throughout. I’m not sure what we’ll do longer term, especially for moves beyond one square at a time. But for now:

        _:test("robot wasd", function()
            local world = WorldProxy()
            local robot = Robot("Wilbur", world)
            _:expect(robot:x()).is(0)
            _:expect(robot:y()).is(0)
            robot:keyboard("w")
            _:expect(robot:y()).is(1)
            robot:keyboard("s")
            _:expect(robot:y()).is(0)
            robot:keyboard("d")
            _:expect(robot:direction()).is("E")
            robot:keyboard("a")
            robot:keyboard("a")
            robot:keyboard("a")
            _:expect(robot:direction()).is("S")

        end)

The w and s should work but the d and a do not.

10: robot wasd  -- 
Actual: N, 
Expected: E
10: robot wasd  -- 
Actual: N, 
Expected: S

As expected. Change the keyboard code. I’m going to remove the old keys, “1” for forward and “r” for right. A couple of tests may break.

function Robot:keyboard(key)
    if key == "l" then
        self:scan() -- look
    elseif key == "w" then
        self:forward(1)
    elseif key == "s" then
        self:back(1)
    elseif key == "d" then
        self:turn("right")
    elseif key == "a" then
        self:turn("left")
    end
end

Yes …

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

I decide that that test is worth keeping, since it is checking the facts, not just the coordinates, so I revise it to use “w”:

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

Should be green now. We are. Commit: robot motion converted to WASD. A and D only rotate.

Nice progress. Let’s sum up.

Summary

It’s Sunday, so this is enough “work”. An opoportunity of the right size appeared, improving the display, and a random idea of looking for an oriented icon in the Codea assets found a very nice arrow icon. The GridDisplay, really just a method returning a struct, is an object waiting to be born. As we come up with more obstacles, and ways to display them, I expect that GridDisplay will grow up. For now, it’s good enough.

Then, another feature that has been waiting was to convert to WASD, and that, too was quite simple. We can imagine improving keyboard handling, if the game is going to be driven from the keyboard, but again, for now, it’s good enough to demo the game.

And, given the new icon and the ability to move forward, backward, and to turn in either direction, even our walking skeleton demo is much improved. I’m sure our new Robot Overlords, whom I for one welcome, will approve.

Me, I’m going to drink my chai latte, eat a banana, read some trasy novel, and wait for breakfast.

Hoping you are the same, I remain, yours etc etc …