As I look at the current code, after over a month away from it, I’m wondering whether the behaviors should be, as they are now, methods on the vehicle class, BVehicle1, or whether they should be more free-standing functions. If they were, where would we keep them? Perhaps in another class called Behavior? Perhaps just in a table indexed by the behavior name?

I lean toward the former, but that could be my long OO background talking. And maybe there’s some approach that I haven’t even considered.

With some experimentation, I learned a few things. First, some better names for the behavior sets, and for the vehicles list (rename objects to vehicles, the code got a little clearer.

However, there’s this anomaly, for example

    b1 = {}
    table.insert(b1, BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1))
    table.insert(b1, BVehicle1.behaveJitter)
    table.insert(b1, BVehicle1.behaveAvoidWall)

Note that we init the behaveDefaultMotion behavior with partial, and not the other two. Furthermore, trying to use partial with those causes errors that look as if the function, such as behaveJitter, no longer has the right value for self. That tells me that I don’t really understand my partial binding trick code as well as I thought.

So we’ll revisit that for next time. Meanwhile, I’ve reverted the code, making today’s experiment vanish as if it had never been.

Thursday

Well, I’ve done the tax prep work I needed to do this morning, so let’s take a look at the partial function stuff.

The basic idea, as I recall it from over a month ago, is that each vehicle has a list of various “behaviors” that control what it does. So ideas like “seek food” or “avoid walls” are behaviors. A behavior might in principle do anything but in practice what they do now is adjust the speed of rotation of one or both of the wheels of the vehicle. When we update a vehicle before drawing it, we use this code:


function BVehicle1:update()
    self:locateEyes()
    local wheels = vec2(0,0)
    for i,b in pairs(self.behaviors) do
        wheels = wheels + b(self)
    end
        
    self:determinePosition(wheels)
end

The update method runs each behavior, which is expected to return a vec2 that is added into wheels, which is added to the current position to figure out the new position.

So each behavior is expected to return a vec2. Here’s a typical one:

function BVehicle1:behaveSeekFood()
    return vec2(self:seekFood())
end

function BVehicle1:seekFood()
    local lw = 0
    local rw = 0
    local food = vec2(food.x, food.y)
    local distL = food:dist(self.eyeL)
    local distR = food:dist(self.eyeR)
    local adj
    if distR > distL then 
        rw = 1
    else
        lw = 1
    end
    return lw, rw
end

You might ask why that’s divided up that way, and the answer is history. It probably shouldn’t be, and that can be on our list of things to fix. Be that as it may, all the behaveSeekFood does is add one to the wheel that will turn the vehicle toward sensed food. You can see in the videos that the vehicle does get there. It jitters around, however, because there’s also a behaveJitter function. It sets a random addendum into the wheels:

function BVehicle1:behaveJitter()
    self.jitterCount = self.jitterCount + 1
    if math.fmod(self.jitterCount, 10) == 0 then
        return vec2(math.random()*10 - 5, math.random()*10 - 5)
    else
        return vec2(0,0)
    end
end

So that’s all good. But there’s more. I wanted to give each vehicle a default movement amount, and I wanted that to be different for each wheel. That’s one way of getting a vehicle to go around in a circle. So I wanted the behavior for default motion to take two parameters, and set those into the wheels:

function BVehicle1:behaveDefaultMotion(m1,m2)
    return vec2(m1, m2)
end

The question was, given that behaveDefaultMotion is a method, how could I set it up so that I could provide the parameters at vehicle definition time, and have them remembered and used at run time? My setup looks like this:

     define behaviors
    
    bAvoidWalls = {}
    table.insert(bAvoidWalls, BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1))
    table.insert(bAvoidWalls, BVehicle1.behaveJitter)
    table.insert(bAvoidWalls, BVehicle1.behaveAvoidWall)
    
    bSeekFood = {}
    table.insert(bSeekFood, BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1.1))
    table.insert(bSeekFood, BVehicle1.behaveJitter)
    table.insert(bSeekFood, BVehicle1.behaveSeekFood)

Here I’m defining two behavior tables, containing pointers to methods (functions) that the Vehicle will understand. Note that the last two lines there say BVehicle1 DOT methodName, not COLON. So BVehicle.behaveJitter is the function pointer itself, not a call. In the update method shown above we say b(self), which calls that function/method.

The tables are used in creating vehicles, like this:

    table.insert(objects, BVehicle1(bAvoidWalls, 400,200,0))
    table.insert(objects, BVehicle1(bSeekFood, 400,400, 0))

Vehicle creation expects a behavior table, x and y coordinates, and a rotation:

function BVehicle1:init(behaviors, x,y, angle)
    self.behaviors = behaviors
    self.position = vec2(x,y)
    self.angle = angle or 0
    self.width = 10
    self.jitterCount = 0
end

For this to work, the behaveDefaultMotion in the table can’t be a raw call to that function, because we have to bind in the parameters, 1 and 1, or 1 and 1.1, in our two cases. So we want, not a function pointer to behaveDefaultMotion, but instead a pointer to a function that will call behaveDefaultMotion with the two parameters, as needed. This is called, variously, a “partial application” of the function, or a “closure”. We take a multi-parameter function and fix the values of some of its parameters, and return that as a new function. We’re not fixing all the parameters, because each vehicle method expects self as its first parameter. This is automatically provided when you use object:method(blah), and we provide self’ explicitly in our update method, when we call with b(self)`.

To deal with this case, as shown in the setup above, I use the method partial, which looks like this:

function BVehicle1:partial(f, arg1, arg2)
    return function(...)
        return f(self, arg1, arg2,...)
    end
end

So partial accepts two arguments arg1 and arg2, and returns a new function with arbitrary parameters, which calls the provided function (behaveDefaultMotion in our case), providing as its parameters, first the two arguments provided, and then all the rest.

The miraculous thing is that this works. If you don’t quite understand it, don’t feel bad: neither do I. In fact, I think it is wrong. Note that we return a function that will call the provided one with self, arg1, arg2, .... What’s self here? It’s whatever it was when we called partial`! But we wrote this:

BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1))

So at that moment, self is surely the class BVehicle1, isn’t it? So as it happens, our default motion method doesn’t reference self, but if it did, I think it wouldn’t be our specific vehicle at all.

I’m going to test that. I do it by printing BVehicle1 at the beginning, and self plus the two parameters inside behaveDefaultMotion. Sure enough, behaveDefaultMotion1 prints the same table name every time, the class table BVehicle1`. I try to fix that this way:

function BVehicle1:partial(f, arg1, arg2)
    return function(s, ...)
        return f(s, arg1, arg2,...)
    end
end

That works. We know that when we call the function in update, we’ll call it with the correct self, which will bind to s in the inner function and be used as self in the actual call. So that’s better.

Are you confused? I am, though a bit less so, and I’m right here and I wrote this. And we aren’t even to the concern I really had, which was “Why don’t we always use partial, instead of just when the behavior method expects parameters.

Now one more thing comes to mind: in the partial above, I’m assuming that there are additional parameters that are going to be passed to the newly created function. But there aren’t: by design we only pass in the appropriate self object. So let’s see if we can remove the ... and make it a bit simpler.

function BVehicle1:partial(f, arg1, arg2)
    return function(s)
        return f(s, arg1, arg2)
    end
end

That continues to work and makes a bit more sense to me. Now I wonder if we can’t use … to allow partial to accept any number of arg, not just two.

After some tries and some googling, I found that this works:

function BVehicle1:partial(f, ...)
    local a = {...}
    return function(s)
        return f(s,unpack(a))
    end
end

Putting the ... directly where unpack(a) is now wouldn’t compile, with no useful error message. Saving the parameters as a table and then unpacking the table back into the call to f works, and our little guys are still getting the right parameters. I was briefly concerned that they might all get the same a, but they don’t. The reason, of course, is that on every call to partial we get a new a` and bind it into the new function that we create.

So this is an improvement, though I have no use at present for allowing other than two arguments, and that was a violation of YAGNI. Still, I’ve learned something and improved the code a bit in, I hope, both clarity and function. That will do for now.

Friday

Since my new partial seems to work correctly and to be more likely to actually be right, I tried using it in all the setup operations:

function setup()
    print(Braitenberg Vehicle Experiment)
    objects = {}
    
     define behaviors
    
    bAvoidWalls = {}
    table.insert(bAvoidWalls, BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1))
    table.insert(bAvoidWalls, BVehicle1:partial(BVehicle1.behaveJitter))
    table.insert(bAvoidWalls, BVehicle1:partial(BVehicle1.behaveAvoidWall))
    
    bSeekFood = {}
    table.insert(bSeekFood, BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1.1))
    table.insert(bSeekFood, BVehicle1:partial(BVehicle1.behaveJitter))
    table.insert(bSeekFood, BVehicle1:partial(BVehicle1.behaveSeekFood))
    
     define vehicles
    
    table.insert(objects, BVehicle1(bAvoidWalls, 600,400,0))
    table.insert(objects, BVehicle1(bAvoidWalls, 400,600,0))
    table.insert(objects, BVehicle1(bAvoidWalls, 800,400,0))
    table.insert(objects, BVehicle1(bAvoidWalls, 400,800,0))
    table.insert(objects, BVehicle1(bAvoidWalls, 100,500,0))
    table.insert(objects, BVehicle1(bAvoidWalls, 200,700,0))
    table.insert(objects, BVehicle1(bAvoidWalls, 300,300,0))
    table.insert(objects, BVehicle1(bAvoidWalls, 400,200,0))
    
    table.insert(objects, BVehicle1(bSeekFood, 400,400, 0))
    
    food = Food(300,900)
end

This does seem to work correctly throughout. Now, we have some very nice duplication to remove, left for next time.

Tune in then!