Today we work further toward accommodating the ‘spec’. I think we’ll focus on the robot state. I think I may rediscover why something is a good idea.

I think it is a fundamental design notion of the original Robot Worlds spec that the World maintains the Robot’s “true” status. The World knows where it is, which way it is facing, whether or not it has fallen into a pit, and so on. While it is explicitly allowed for the Robot to keep track of any information we may wish to track, the World’s information is “the truth”. Requests always return robot state, which is a dictionary kind of thing like this:

{
  ...
  state={
    position=[x,y],
    direction="N",
    shields="number of hits that can be absorbed",
    shots="number of shots left",
    status="operational status"
  }
}

We’ll encounter the details about these as we go forward. The state object is itself embedded in the “Response Message”, which also includes result, and data. We’ve seen a bit of data and will see more as we progress.

This morning, I think I’d like to change my Robot so that it retains, and uses, the information in the state when it needs to know things about itself. To begin, I would like for state to be kept, not in a raw dictionary thing, but in an object with the same shape. Let’s TDD RobotState. Shouldn’t need much.

RobotState

I create a new tab “TestRobotState”, and paste in and edit my standard test starter. It runs green. Now to test. The RobotState object might as well be created with a dictionary, because that’s what we’ll be getting back from the World server.

        _:test("RobotState", function()
            local stateDict = {}
            local state = RobotState(stateDict)
        end)

This is more than enough to drive out the class:

1: RobotState -- TestRobotState:17: attempt to call a nil value (global 'RobotState')

And …

RobotState = class()

Nota Bene: I know that I’ll benefit from smaller steps. So even the test above is more than I needed to drive out the class. The second line alone would have done it. I’ll try to go in tiny steps, but I’ll try not to get silly about it.

I want my RobotState to just contain the dictionary, and to have accessors to fetch information that the Robot needs. Should I provide for all of position, direction, shields, shots, and status now? I have no need of them. Still, I want to get started. Let’s do position. It’s speculative, but not very.

        _:test("RobotState", function()
            local stateDict = {
                position={1,2}
            }
            local state = RobotState(stateDict)
            local x,y = state:position()
            _:expect(x).is(1)
            _:expect(y).is(2)
        end)

That should suffice to push out some code. Test will fail looking for position.

1: RobotState -- TestRobotState:20: attempt to call a nil value (method 'position')

OK, in service of taking the smallest steps:

function RobotState:position()
end

Failure will be nils for x and y.

1: RobotState  -- 
Actual: nil, 
Expected: 1
1: RobotState  -- 
Actual: nil, 
Expected: 2

I don’t see anything for it but two do this much:

function RobotState:init(aDictionary)
    self._dict = aDictionary
end

function RobotState:position()
    return self._dict.position[1],self._dict.position[2]
end

I suppose I could have returned vanilla 1,2 in position, then plugged in the dictionary … I think this was tiny enough.

There is the question of whether we should check to see that everything is legitimate. There certainly are places where things could go wrong, between the World creating an incorrect dictionary, to something going wrong in the JSON, and so on. Because it comes to us from the outside, I do think we should validate it. That’ll be somewhat interesting. Let’s do it.

We already have a conveniently invalid input dictionary, the one in our test. I’d rather work with another test. We’ll perhaps want to ignore this test. We’ll see. A new test:

        _:test("Invalid States", function()
            local stateDict
            t = function() RobotState(stateDict) end
            _:expect(t).throws("no dictionary")
        end)

We have to create the function t because we want the call to create the RobotState to occur when throws asks for it. An oddity of CodeaUnit and Lua.

Test:

1: Invalid States  -- 
Actual: nothing thrown, 
Expected: no dictionary

No surprise.

function RobotState:init(aDictionary)
    self._dict = aDictionary
    self:validate()
end

function RobotState:validate()
    assert(self._dict, "no dictionary")
end

I expect a pass. Yes. Now make the test harder:

        _:test("Invalid States", function()
            local stateDict
            t = function() RobotState(stateDict) end
            _:expect(t).throws("no dictionary")
            stateDict = {}
            _:expect(t).throws("no position")
        end)

I did position first just because I knew it was necessary. Should probably really do them in alpha order or something.

Test will fail to throw.

1: Invalid States  -- 
Actual: nothing thrown, 
Expected: no position

Fix:

function RobotState:validate()
    assert(self._dict, "no dictionary")
    assert(self._dict.position, "no position")
end

Tests green. Extend:

            stateDict.position = 3
            _:expect(t).throws("position must be two entry array")

Test will fail, nothing thrown:

1: Invalid States  -- 
Actual: nothing thrown, 
Expected: position must be two entry array

Implement:

Grr. I ran into some misunderstanding. The returns from the throws calls aren’t always clear, because if you throw with the wrong message you see something like this:

3: must be array  -- 
Actual: function: 0x2840d82a0, 
Expected: position must be two element array

That threw me off track, and I decided to write the tests out one at a time.

So we’re here:

        _:test("dictionary", function()
            local stateDict
            local t = function() RobotState(stateDict) end
            _:expect(t).throws("no dictionary")
        end)
        
        _:test("position", function()
            local stateDict
            local t = function() RobotState(stateDict) end
            stateDict = {}
            _:expect(t).throws("no position")
        end)
        
        _:test("must be array", function()
            local stateDict
            local t = function() RobotState(stateDict) end
            stateDict = {}
            stateDict.position = 3
            _:expect(t).throws("position must be two element array")
        end)
        
        _:test("two elements", function()
            local stateDict
            local t = function() RobotState(stateDict) end
            stateDict = {}
            stateDict.position = {1}
            _:expect(t).throws("position must be two element array")
        end)
        
        _:test("elements", function()
            local stateDict
            local t = function() RobotState(stateDict) end
            stateDict = {}
            stateDict.position = {1,2,3}
            _:expect(t).throws("position must be two element array")
        end)

And validate is implemented like this, so far:

function RobotState:validate()
    assert(self._dict, "no dictionary")
    local p = self._dict.position
    assert(p, "no position")
    local t = type(p)
    assert(t=="table", "position must be two element array")
    assert(#t==2, "position must be two element array")
end

This is tedious. And my correct test is throwing the array error

I finally spot my bug in the last line, checking t, the type, instead of p, the position.

function RobotState:validate()
    assert(self._dict, "no dictionary")
    local p = self._dict.position
    assert(p, "no position")
    local t = type(p)
    assert(t=="table", "position must be two element array")
    assert(#p==2, "position must be two element array")
end

That’s what I get for rushing with short names. Tests green. Rename:

function RobotState:validate()
    assert(self._dict, "no dictionary")
    local position = self._dict.position
    assert(position, "no position")
    local pType = type(position)
    assert(pType=="table", "position must be two element array")
    assert(#position==2, "position must be two element array")
end

I’d never have typed

assert(#type==2, ...)

Not in a million years. This is still tedious, however. Is it necessary? Well, we can guarantee that in our own code we’ll probably return an invalid state at some point, I’m just not that clever. So let’s see if we can see how to make this less tedious, or at least more clear.

Let’s try extracting …

function RobotState:validate()
    assert(self._dict, "no dictionary")
    self:validatePosition()
end

function RobotState:validatePosition()
    local position = self._dict.position
    assert(position, "no position")
    local pType = type(position)
    assert(pType=="table", "position must be two element array")
    assert(#position==2, "position must be two element array")
end

As soon as I do this, I see one nice thing about breaking the validations out: I can implement empty ones and fill them in later, providing the right structure while leaving the details for later.

And I’d like to have the errors all produced by the main validate function. Like this:

function RobotState:validate()
    assert(self._dict, "no dictionary")
    assert(self:validatePosition(), "position must be array [x,y]")
end

function RobotState:validatePosition()
    local position = self._dict.position
    return position and type(position)=="table" and #position==2
end

Wow. I did not see that coming. Good news. Once I started just returning true/false, that nice implementation dropped out. So I changed all the expectations to the better message, and we’re green again.

I should commit: Initial RobotStatus checks position in input dictionary.

This has taken me rather longer than I had anticipated. I think I started just before ten and it is almost 1130 now and this is my first commit. I could have done more, but this is arguably the first reasonable time to commit.

Relearning is still learning.

Well, it takes as long as it takes, and I was troubled by the #t that should have been #p for longer than I care to admit. I do love how I learn so quickly not to take shortcuts, over and over again. It’s weird how we know what we should do and then we do something else, isn’t it? Where did I put those potato chips?

I’d really like to plug this status into Robot today. Let’s at least take a look at how that would go.

Robot with Status

Robot gets created like this:

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

If launchRobot were kind enough to return a status dictionary, we could plug it in right there where we do x and y. We’ll have to worry about x and y of course.

I’m trying to channel what Hill would do, how he would find a way to do this one line at a time. Let’s look at our proxy. We might fake it there and then proceed.

function WorldProxy:launchRobot(...)
    return self.world:launchRobot(...)
end

Now we know that we’re just getting back x and y, and if we didn’t know that we could look, so we can do this:

function WorldProxy:launchRobot(...)
    local x,y = self.world:launchRobot(...)
    return x,y
end

Tests should be green. They are. Now I am really sure that I have to change more than one line. Let’s make a dictionary in the proxy launch:

function WorldProxy:launchRobot(...)
    local x,y = self.world:launchRobot(...)
    local status = {
        position = {x,y}
    }
    return status
end

And in Robot:

function Robot:init(name,aWorld)
    assert(aWorld:is_a(WorldProxy), "expected WorldProxy")
    self._world = aWorld
    self._status = aWorld:launchRobot(name, self)
    self._x, self._y = self._status.position[1], self._status.position[2]
    self._name = name
    self.knowledge = Knowledge()    
end

Green. Could commit. Will, just to prove it: Proxy launch returns status dictionary, only position so far.

Now we have a position method in RobotStatus, we started with that, so let’s refactor to this:

Wow. Another really odd mistake. I named the new class RobotState. And, it turns out, we already have a nascent class, RobotStatus, in WorldProxy. I used that and of course it didn’t work. First make it work, using the new object.

function Robot:init(name,aWorld)
    assert(aWorld:is_a(WorldProxy), "expected WorldProxy")
    self._world = aWorld
    self._status = RobotState(aWorld:launchRobot(name, self))
    self._x, self._y = self._status:position()
    self._name = name
    self.knowledge = Knowledge()    
end

We are green. Commit again: Using RobotState class in Robot. Needs convergence with RobotStatus!!!

OK, what’s up with RobotStatus? Did someone already start working on this idea?

Oh yes, the World has one:

function World:launchRobot(name, kind,shieldStrength, shots)
    local robot = RobotStatus(self, name, kind, shieldStrength, shots)
    self._robots[name]=robot
    return robot._x, robot._y, robot._direction
end

How should we converge these two ideas? Perhaps we need not do so at all. Arguably World should have its own view of the robot’s status and so should the Robot. In a real situation we wouldn’t be building both sides all in one project. That’s an artifact of the Codea situation.

The two things will be mirrors of each other, since they share a common spec. For now, I’m going to make RobotStatus local to World.

Now on the other side, the Robot side, let’s rename RobotState simply to Status. It is, after all, the only status we care about.

Global replace doesn’t kill me. Still green. Commit: Robot Status object is Status. World has local class RobotStatus.

Where Were We … Oh, Yes …

We were working toward getting the Robot’s status from that of the World. Presently we’re creating it in the Proxy:

function WorldProxy:launchRobot(...)
    local x,y = self.world:launchRobot(...)
    local status = {
        position = {x,y}
    }
    return status
end

Let’s review the launch in World:

function World:launchRobot(name, kind,shieldStrength, shots)
    local robot = RobotStatus(self, name, kind, shieldStrength, shots)
    self._robots[name]=robot
    return robot._x, robot._y, robot._direction
end

Well, if we were to return a properly formatted status here, that would be sweet. What does the spec say1?

Well, it says a lot. The general response packet has result, data, and state. There’s a lot of data. Let’s first code enough in line to have the right format.

function World:launchRobot(name, kind,shieldStrength, shots)
    local robot = RobotStatus(self, name, kind, shieldStrength, shots)
    self._robots[name]=robot
    local response = {
        result="OK",
        data={},
        state={
            position = {robot._x, robot._y},
            direction = "N",
            status = "NORMAL",
            shots = robot._shots,
            shields = robot._strength
        }
    }
    return response
end

I kind of went over the top here. Hill would be screaming. On the Proxy side:

function WorldProxy:launchRobot(...)
    local response = self.world:launchRobot(...)
    return response.state
end

We are green. Commit: Enough response returned from World:launch to keep skeleton walking.

I notice that my notion of state vs status is fuzzy, and that the spec has an opinion. The Robot’s “state” is the larger object with position and all that, and its “status” is NORMAL, DEAD, and so on. Let’s rename RobotStatus and Status to RobotState and State.

Green. Commit: Rename RobotStatus to RobotState, and Status to State.

I think we could draw a line under the day right here, but I want first to look at the places where we did the JSON stuff. There are a few, of the nature of experimental first steps:

function World:jsonLookResult(packetArray)
    local outcome = {}
    outcome.result="OK"
    local objects = {}
    for i,p in ipairs(packetArray) do
        table.insert(objects,p:asObject())
    end
    outcome.data = { objects=objects }
    local jsonString = json.encode(outcome, {indent=true})
    return jsonString
end

function WorldProxy:forward(name,steps)
    local result
    local rq = {
        robot=name,
        command="forward",
        arguments={steps}
    }
    local jsonRq = json.encode(rq)
    local decoded = json.decode(jsonRq)
    local dName = decoded.robot
    local dCommand = decoded.command
    if dCommand == "forward" then
        local dSteps = decoded.arguments[1]
        return self.world:forward(dName,dSteps)
    end
    assert(false,"impossible command "..dCommand)
end

function WorldProxy:scan(...)
    local jsonString = self.world:scan(...)
    local outcome = json.decode(jsonString)
    local packets = outcome.data.objects
    --print(#packets, " packets")
    local result = {}
    for i,p in ipairs(packets) do
        local lp = LookPacket:fromObject(p)
        --print(lp)
        table.insert(result, lp)
    end
    return result
end

Those are the most significant ones. I have a sort of design, sketched in fake pencil, for how this thing should ultimately work:

pencil drawing of boxes and arrows

That scrawl says “Maybe RobotStatus is more prominent”.

I envision that the robot thinks of things it wants to say to the world and says them to the WorldProxy. The Proxy converts each saying to a Request and passes it to an object that converts it to JSON and sends it to the World. That object will receive the response, in JSON, from the World, convert it to a dictionary, and return it to the Proxy, which converts it to a Result and returns that to the Robot, which will unpack whatever data it cares about, store the state part, and so on, all to be figured out one day soon.

On the world side it is similar, there is a receiver that sees some JSON and decodes it into a dictionary, which it sends to an object that turns it into a Request, and sends it to the World, which deals with the Request, creates a Response, returns it to the JSON thingie, which converts it to JSON and sends it on its way.

In the middle, there’s a socket connection.

Je Ne Regrette Rien

I don’t regret starting with direct calls: I think they are a better structure for my main objects. But I am certainly aware that the current structure of the overall program needs some conversion layers put in, and if I were a person who did regret, I’d probably be wishing that I had had more of an understanding of the spec than I had when I started, or that I were smarter, or that someone had asked me about a Command Pattern sooner or something … you begin to see why I try not to do regret: I’d run out of time to do anything else.

We have some work to do, we’ll leave it at that. I think we’d do well to continue on the path of getting these request and response objects / dictionaries in place for the commands we have now.

We’ll continue with that tomorrow, working closer and closer to round trip with these command / response dictionaries … which we’ll make be true, albeit not very smart objects, as we’ve begun with State and RobotState.

See you then!



  1. The fox says “Wa pa pa pa pow”. As for the spec …