What shall we do today, Brain?

Same as we do every day, Pinkie: look at the situation and decide what to do next.

Looking at our update method, we see this:

function BVehicle1:update()
    local wheels = vec2(0.5, 0.5)
    wheels = wheels + vec2(math.random()*4 - 2, math.random()*4 -2)
    local lr = vec2(self:seekFood())
    wheels = wheels + lr
    self.angle = self.angle + math.deg(self:calcAngle(wheels))
    local step = vec2((wheels.x+wheels.y)/2,0)
    local move = step:rotate(math.rad(self.angle))
    self.position = self.position + move
    if self.position.x > 1000 then self.x = 100; self.y = 100 end
end

There’s a lot going on here. Let me begin by putting some comments and space in.

function BVehicle1:update()
     default to move 0.5
    local wheels = vec2(0.5, 0.5)
    
     randomly jitter a bit, between -2 and 2
    wheels = wheels + vec2(math.random()*4 - 2, math.random()*4 -2)
    
     move toward food
    local lr = vec2(self:seekFood())
    wheels = wheels + lr
    
     determine new angle and position
    self.angle = self.angle + math.deg(self:calcAngle(wheels))
    local step = vec2((wheels.x+wheels.y)/2,0)
    local move = step:rotate(math.rad(self.angle))
    
     reposition self
    self.position = self.position + move
    
     useless check for going off screen
    if self.position.x > 1000 then self.x = 100; self.y = 100 end
end

Well, now that this code is documented, it must be perfect. No, I don’t think so. I was taught that when a method does multiple things with inline code, it is asking to be refactored. A method should either do one thing, or only call methods (each of which does one thing). I try to live by that guidance.

Now the first few of these comments describe vehicle “behaviors”: a default motion, a tendency to wander, a tendency to move toward food. Then there’s the final calculation of the vehicle’s position in the world, and a very weak and useless check for going off screen (in one direction). We can imagine vehicles that don’t default to move, who are so stable that they don’t wander, and that don’t eat the current kind of food. I anticipate giving our vehicles some kind of pluggable behavior, to create different kinds of creatures. So those things need to be extracted in some way that we’ll need to devise.

The off-world check should be removed, although we probably want something to happen if a vehicle falls off the world. I recall that in Braitenberg’s book, at one point, falling off the world is part of how vehicles evolve. So for now, I think I’ll remove the feature entirely but something will go in its place someday.1

The actual updating seems to me to be a single thing, both angle and position, so I think I’ll extract that.

function BVehicle1:update()
     default to move 0.5
    local wheels = vec2(0.5, 0.5)
    
     randomly jitter a bit, between -2 and 2
    wheels = wheels + vec2(math.random()*4 - 2, math.random()*4 -2)
    
     move toward food
    local lr = vec2(self:seekFood())
    wheels = wheels + lr
    
    self:determinePosition(wheels)
    
     useless check for going off screen
    if self.position.x > 1000 then self.x = 100; self.y = 100 end
end

function BVehicle1:determinePosition(wheels)
    self.angle = self.angle + math.deg(self:calcAngle(wheels))
    local step = vec2((wheels.x+wheels.y)/2,0)
    local move = step:rotate(math.rad(self.angle))
    self.position = self.position + move
end

This seems to me to be a bit better, but I’m wondering now about the wheels variable,which represents the motion of each of the two wheels. Part of me wants to make it a member variable, because it seems that wheels are part of the vehicle. But part of me thinks it’s just a convention for calculating motion. (And I’m still a bit troubled by the fact that wheels isn’t really a proper vector, though it does sort of represent a distance along x and y.) For now, I think it’s OK as a local and parameter. This will come up again when we do something about behavior.

The screen check still looks good, so let’s commit this and move on.

Behavior

I’m feeling the need to do something of substance, having been refactoring for a while now over the past couple of sessions. So how about a first cut at behavior? Here’s my plan:

A vehicle will have a collection of behaviors, like a default velocity, a tendency to seek food, whatever. On each update, we’ll run all of its behaviors, which will mostly result in motion, and then we’ll update position and be ready to draw. Each behavior will need a reference to the vehicle, to get distances from food or whatever info it may need. And, at least for now, let’s say that each behavior returns a vector of values to be added to the wheels.

So behaviors, it seems to me, are functions taking a vehicle as a parameter. Methods in the vehicle are functions taking the vehicle as a parameter. For now, I think I’ll make them methods, and give them identifying names. I suspect we may change this going forward.

So first step, I’ll make one of these existing behaviors into a method. First I’ll just call it from within update, but then I’ll change setup to build the behaviors collection, put that method name into it, and put a loop calling all the behaviors into update. Then I’ll move the others one by one.

Here goes:

function BVehicle1:behaveDefaultMotion()
    return vec2(0.5, 0.5)
end


function BVehicle1:update()
    local wheels = vec2(0,0)
    wheels = wheels + self:behaveDefaultMotion()
    
     randomly jitter a bit, between -2 and 2
    wheels = wheels + vec2(math.random()*4 - 2, math.random()*4 -2)
    
     move toward food
    local lr = vec2(self:seekFood())
    wheels = wheels + lr
    
    self:determinePosition(wheels)
    
     useless check for going off screen
    if self.position.x > 1000 then self.x = 100; self.y = 100 end
end

That was just a simple Extract Method, except that the local, wheels, was declared in that initial usage, so I created it separately and added it in. I foresee that we’re going to see a lot of wheels = wheels + before this is over, but that’s Lua for you. Anyway, I’ve committed that change.

Now if I were just going for cleaning up the update method, I’d extract the others. But we have our eye on a bigger prize, building behavior. That will get done, for now, in setup:

function BVehicle1:init(x,y, angle)
    self.position = vec2(x,y)
    self.angle = angle or 0
    self.width = 10
    self.behaviors = {}
    table.insert(self.behaviors, BVehicle1.behaveDefaultMotion)
end


function BVehicle1:update()
    local wheels = vec2(0,0)
    for i,b in pairs(self.behaviors) do
        wheels = wheels + b(self)
    end
    
     randomly jitter a bit, between -2 and 2
    wheels = wheels + vec2(math.random()*4 - 2, math.random()*4 -2)
    
     move toward food
    local lr = vec2(self:seekFood())
    wheels = wheels + lr
    
    self:determinePosition(wheels)
    
     useless check for going off screen
    if self.position.x > 1000 then self.x = 100; self.y = 100 end
end

In setup, we build our behaviors collection and put our one behavior into it. In update, we loop over all the behaviors, all one of them, and apply their result to our local wheels variable. You’ll note that we said b(self), not self:b(). That’s because the colon syntax is syntactic sugar in Lua, looking up ‘b’ in the method table, rather than dereferencing the variable named ‘b’. That took me a while to remember.

Anyway, now we have one behavior working, and it’s time to commit and take a break.

One more thing: tests!

You’ll have noticed that I don’t have any of those TDD tests I’m always talking about. That’s a problem in a number of ways:

First, every time I change something, my only recourse is to run the program and look at the screen. Usually, if there’s a problem, it’s pretty visible, like bugs not moving or the program exploding on an error, usually a nil reference. But it’s also the case, since there’s so much randomness in here, that I might miss seeing a problem as the little guys run around the screen.

Second, and this is always the case when writing about a program whose sole purpose is to drive interesting stuff on the screen, it’s hard to communicate here. If you enjoy reading code and seeing how it changes, well, it might be OK, but you miss out on the little kick I get when I run it and it works.

Right now, I don’t have a solution for either one of these, and you’ll see that I don’t come up with one in the next few articles at least. The good news is that the changes I make are small, so my visual test works OK for me, and for you, I’ll try to include a new movie or pictures every time something interesting goes in.

I honestly don’t know a good way to TDD on a graphics program, and it always bothers me. It feels like I’m stepping back a couple of decades and relying on what I knew then rather than what I’ve learned since.

When I get these first articles up, about 8 of them, I think, remind me to look at plugging in some tests. Maybe we can think of something that makes sense. I hope so.

Friday

Friday rolled around rather quickly. Today I think I’ll create the other behaviors that are implicit in our current update method, and then maybe we’ll make some new behaviors to see if we can make this thing interesting.

function BVehicle1:update()
    local wheels = vec2(0,0)
    for i,b in pairs(self.behaviors) do
        wheels = wheels + b(self)
    end
    
     randomly jitter a bit, between -2 and 2
    wheels = wheels + vec2(math.random()*4 - 2, math.random()*4 -2)
    
     move toward food
    local lr = vec2(self:seekFood())
    wheels = wheels + lr
    
    self:determinePosition(wheels)
    
     useless check for going off screen
    if self.position.x > 1000 then self.x = 100; self.y = 100 end
end

The next one is jitter, so let’s get on it. I’m tempted to do it all in one step, but I don’t have a beer for you to hold. So first, I’ll extract a jitter method and call it, and then I’ll move it into the behaviors collection. And as I think about this, I suspect we’re going to want different jittering (and probably different default speeds, which makes me suspect we’ve got some interesting partially-applied function thing, or the like, coming up. We’ll see. One thing at a time.

function BVehicle1:update()
    local wheels = vec2(0,0)
    for i,b in pairs(self.behaviors) do
        wheels = wheels + b(self)
    end
    
    wheels = wheels + self:behaveJitter()
    
     move toward food
    local lr = vec2(self:seekFood())
    wheels = wheels + lr
    
    self:determinePosition(wheels)
    
     useless check for going off screen
    if self.position.x > 1000 then self.x = 100; self.y = 100 end
end


function BVehicle1:behaveJitter()
    return vec2(math.random()*4 - 2, math.random()*4 -2)
end

That works a treat. Commit the code! Now to move it into behavior and take it out of the update:

function BVehicle1:init(x,y, angle)
    self.position = vec2(x,y)
    self.angle = angle or 0
    self.width = 10
    self.behaviors = {}
    table.insert(self.behaviors, BVehicle1.behaveDefaultMotion)
    table.insert(self.behaviors, BVehicle1.behaveJitter)
end

Still works! Commit!

Now, what’s left? Move toward food. This time I’m feeling brave, having just done one, so I’ll go in one step. Watch this!

Fortunately, for my waning confidence and reputation, this worked first try:

function BVehicle1:init(x,y, angle)
    self.position = vec2(x,y)
    self.angle = angle or 0
    self.width = 10
    self.behaviors = {}
    table.insert(self.behaviors, BVehicle1.behaveDefaultMotion)
    table.insert(self.behaviors, BVehicle1.behaveJitter)
    table.insert(self.behaviors, BVehicle1.behaveSeekFood)
end


function BVehicle1:update()
    local wheels = vec2(0,0)
    for i,b in pairs(self.behaviors) do
        wheels = wheels + b(self)
    end
        
    self:determinePosition(wheels)
    
     useless check for going off screen
    if self.position.x > 1000 then self.x = 100; self.y = 100 end
end

Commit again!

Frequent commits

Let’s talk about all these commits I’m doing. I’m basically committing the code to git every time the system runs correctly. And, the other day, when things were going wrong, I reverted to the previous version quickly without even trying to debug. I’m doing that inspired by something Kent Beck has been writing and videoing about, called T&C|R, meaning Test&Commit|Revert (in short-circuited boolean expression style). Beck’s little practice is to write a test, write the code to make it run, and run it. If it runs green, commit the code. If not, unconditionally revert it. He has a script of some kind running that does that.

I’d like to try that sometime: it seems interesting and educational to me. You’d sure learn to go in tiny little steps, wouldn’t you? And if you did, it would be pretty harmless, since each revert would be just a line or two.

With this visually oriented little game, I haven’t figured out how to have automated tests, so I can’t really do the practice. But I’m finding it very comforting to commit the code every time it’s a little bit better, because sooner or later I’ll mess it up, and if there’s a commit from a few minutes ago, reverting will be easy.

I’ve only been committing this frequently for a while, not least because Codea only got this active relationship with WorkingCopy recently, and I’ve not been coding much on the iPad anyway. The combination of a new iPad and a new Codea has me back doing this.

Anyway, I’m liking these insanely frequent commits, and I commend the idea to your attention.

One giant leap for vehicle-kind

I spoke above about the need for a behavior to take parameters.

And as I think about this, I suspect we’re going to want different jittering (and probably different default speeds, which makes me suspect we’ve got some interesting partially-applied function thing, or the like, coming up. We’ll see. One thing at a time.

Well, while waiting for my Apple Pencil to charge, I did a little research on partial application of functions in Lua, or, as it is called, “currying”. Then, with a few missteps to small to mention, I built what follows. What I’ve done is allow the behavior setup for behaveDefaultMotion to set up two parameters, one for each wheel. To do that, I built a new vehicle method, partial, that returns a method that calls its first parameter with the two arguments provided to partial. If this is confusing, you’re not alone. Let’s look at the code:

function BVehicle1:init(x,y, angle)
    self.position = vec2(x,y)
    self.angle = angle or 0
    self.width = 10
    self.behaviors = {}
    table.insert(self.behaviors, self:partial(BVehicle1.behaveDefaultMotion, 1, 1.1))
    table.insert(self.behaviors, BVehicle1.behaveJitter)
    table.insert(self.behaviors, BVehicle1.behaveSeekFood)
end

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

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

We see here that we’ve extended behaveVehicleMotion to accept two parameters. It formerly accepted just one, making just for straight motion. Now it accepts the two wheels separately, allowing for a vehicle that runs in a circle, for what that’s worth.

Now in this case, rather than an executing behavior, we could just return a constant, but in general our behaviors are more active than that, so we make it dynamic up above.

In setup, we write this:

    table.insert(self.behaviors, self:partial(BVehicle1.behaveDefaultMotion, 1, 1.1))

This means to add to behaviors, the method behaveDefaultMotion, with parameters 1 and 1.1. We can’t just call it now, because it has to be dynamic. That’s the job of the partial method. Let’s look right at that:

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

When we call self:partial, it returns a function (remember that our usual behavior calls just return the method (i.e. function) as well. In this case we’re creating a new function on each use of partial. That function applies the input function, behaveDefaultMotion, in this case, passing self, to make it a method call, and the two arguments provided. So we wind up calling what amounts to:

self:behaveDefaultMotion(1, 1.1)

That’s just what we want. Weird but good, and now the little guy tries to circle, although the food search overrides that behavior successfully. Just what we wanted. Nothing like a little internet research. Still, I do find it hard to encompass this in my mind. Fortunately all we have to do day to day is use it, and that’s pretty straightforward.

See you next time!


  1. Keep an eye out for this. I think maybe the old man forgets to remove this check. What should he do about that? Maybe write down things to do on cards or something?