Braitenberg Vehicles - 6
Thursday
After somewhat redeeming my delusions of adequacy yesterday, I have an ambitious goal for today (and tomorrow if necessary). I’d like to get wall avoidance finished, then change feeding behavior so that the guy only looks for food when he’s hungry. Other possibilities include making some of them fear each other and some aggressive toward each other. Somewhere in there, ideally today, otherwise tomorrow, I’d like to make a little video to see if anyone is remotely interested in reading about this.
First thing, finish wall avoidance. Right now we have this:
function BVehicle1:avoidWall()
local minDist = 50
local wheels = vec2(0,0)
local distLeftEye, distRightEye
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(1000, self.position.y))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(0, self.position.y))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
return wheels.x, wheels.y
end
I think the 50 is too large and will reduce it, but of course the main thing is that we need to do the top and bottom walls as well as right and left. That should be straightforward:
function BVehicle1:avoidWall()
local minDist = 50
local wheels = vec2(0,0)
local distLeftEye, distRightEye
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(1000, self.position.y))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(0, self.position.y))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(self.position.x, 0))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(self.position.x, 1000))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
return wheels.x, wheels.y
end
Here I just put in the other two walls. This did not work as I expected. After a bit of wandering, the little dude gets on the diagonal, and just keeps following it, allowing the world wrap-around to bring him back on the screen. This is boring. First, I’ll reduce the minDist value to 10.
This works better for a while, but sooner or later he gets headed into a corner and can’t get enough difference on his wheels to turn him around. I was thinking about having the wall be unnoticed beyond some distance, maybe 100, and will try that.
function BVehicle1:eyeDistancesFrom(aPoint)
return self:eyeDistanceLimited(self.eyeL, aPoint, 100), self:eyeDistanceLimited(self.eyeR, aPoint, 100)
end
function BVehicle1:eyeDistanceLimited(eye, aPoint, limit)
local d = eye:dist(aPoint)
if d > limit then return 10000 end
return d
end
That seems to work better. I’ll give my wall avoider some jitter to make him a bit more random.
function setup()
print("Braitenberg Vehicle Experiment")
objects = {}
b1 = {}
table.insert(b1, BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1))
table.insert(b1, BVehicle1.behaveJitter)
table.insert(b1, BVehicle1.behaveAvoidWall)
table.insert(objects, BVehicle1(b1, 800,200, 45))
b2 = {}
table.insert(b2, BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1.1))
table.insert(b2, BVehicle1.behaveJitter)
table.insert(b2, BVehicle1.behaveSeekFood)
table.insert(objects, BVehicle1(b2, 400,400,0))
food = Food(300,900)
end
This is pretty decent. One issue is that jitter is so strong that sometimes his nervousness overrides his fear of the wall. Also they jitter quite frequently. I’m inclined to play with jitter in one or more of these ways: trigger it less often; make it more cumulative; parameterize the function to allow more playing. Let’s try the trigger first. How about if we set jitter just once every ten times through or something like that?
A little fiddling and we’ve got our video. Hold on while I post it and commit the code. Now then, here’s the whole program for the record:
— Br1
function setup()
print("Braitenberg Vehicle Experiment")
objects = {}
b1 = {}
table.insert(b1, BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1))
table.insert(b1, BVehicle1.behaveJitter)
table.insert(b1, BVehicle1.behaveAvoidWall)
table.insert(objects, BVehicle1(b1, 600,400,0))
table.insert(objects, BVehicle1(b1, 400,600,0))
table.insert(objects, BVehicle1(b1, 800,400,0))
table.insert(objects, BVehicle1(b1, 400,800,0))
table.insert(objects, BVehicle1(b1, 100,500,0))
table.insert(objects, BVehicle1(b1, 200,700,0))
table.insert(objects, BVehicle1(b1, 300,300,0))
table.insert(objects, BVehicle1(b1, 400,200,0))
b2 = {}
table.insert(b2, BVehicle1:partial(BVehicle1.behaveDefaultMotion, 1, 1.1))
table.insert(b2, BVehicle1.behaveJitter)
table.insert(b2, BVehicle1.behaveSeekFood)
table.insert(objects, BVehicle1(b2, 400,400, 0))
food = Food(300,900)
end
— This function gets called once every frame
function draw()
food:update()
for _,v in pairs(objects) do
v:update()
end
— This sets a dark background color
background(40, 40, 50)
— Draw the playground
strokeWidth(5)
stroke(255,0,0)
fill(0,0,0,0)
rect(0,0, 1091, 1024)
stroke(255,255,255)
fill(50,50,50)
rect(0,0,1000,1000)
— draw the world
stroke(255,0,0)
fill(255,0,0)
ellipse(400,400, 10)
food:draw()
for _,v in pairs(objects) do
v:draw()
end
end
BVehicle1 = class()
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
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
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
function BVehicle1:behaveSeekFood()
return vec2(self:seekFood())
end
function BVehicle1:behaveAvoidWall()
return vec2(self:avoidWall())
end
function BVehicle1:calcAngle(wheels)
return math.atan(wheels.y-wheels.x, self.width)
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
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
function computeWheelIncrement(value, left, right)
if right < left then
return vec2(0, value)
else
return vec2(value, 0)
end
end
function BVehicle1:avoidWall()
local minDist = 10
local wheels = vec2(0,0)
local distLeftEye, distRightEye
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(1000, self.position.y))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(0, self.position.y))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(self.position.x, 0))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
distLeftEye, distRightEye = self:eyeDistancesFrom(vec2(self.position.x, 1000))
wheels = wheels + computeWheelIncrement(minDist/distRightEye, distLeftEye, distRightEye)
return wheels.x, wheels.y
end
function BVehicle1:eyeDistancesFrom(aPoint)
return self:eyeDistanceLimited(self.eyeL, aPoint, 100), self:eyeDistanceLimited(self.eyeR, aPoint, 100)
end
function BVehicle1:eyeDistanceLimited(eye, aPoint, limit)
local d = eye:dist(aPoint)
if d > limit then return 10000 end
return d
end
function BVehicle1:keepOnPage()
self.position.x = math.fmod(self.position.x + 1000, 1000)
self.position.y = math.fmod(self.position.y + 1000, 1000)
end
function BVehicle1:locateEyes()
local eyeR = vec2(20,0)
local eyeL = vec2(20,10)
self.eyeR = eyeR:rotate(math.rad(self.angle)) + self.position
self.eyeL = eyeL:rotate(math.rad(self.angle)) + self.position
end
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)
— useless check for going off screen
if self.position.x > 1000 then self.x = 100; self.y = 100 end
end
function BVehicle1:draw()
pushStyle()
pushMatrix()
rectMode(CENTER)
stroke(255,255,0)
fill(255,255,0)
self:keepOnPage()
translate(self.position:unpack())
rotate(self.angle)
rect(0, 0, 20, 10)
local eyeSize = 8
local eyeR = vec2(10,-5) — changed
local eyeL = vec2(10,5) — changed
stroke(255,0,0)
fill(255,0,0)
ellipse(eyeR.x, eyeR.y, eyeSize)
ellipse(eyeL.x, eyeL.y, eyeSize)
popMatrix()
popStyle(d)
end
Food = class()
function Food:init(x,y)
— you can accept and set parameters here
self.x = x
self.y = y
end
function Food:update()
end
function Food:draw()
pushStyle()
pushMatrix()
stroke(0,0,255)
fill(0,0,255)
translate(self.x, self.y)
ellipse(0,0,20,20)
popMatrix()
popStyle()
end
Looking forward, I see lots of magic numbers in here, and other things needing improvement. Most of the magic is at least encapsulated in small methods, which is sort of half way to reasonable. The little guys are starting to behave in a seemingly purposeful fashion, which is kind of the point. Simple behavior leading to apparently complex and purposeful action, where we impute meaning to what are really simple wiring kinds of things.
There’s an issue here, which is that as I program these behaviors, I’m not doing them randomly, but with intention to direct the bug to food or away from the walls. Braitenberg seems to be making the point that very simple wiring internals can lead to behavior that from the outside seems more purposeful than the wiring dictates. The fact that I’m choosing the behaviors belies that theory: I am the god-scientist creating these little creatures. And Braitenberg’s story is a bit the same, as clearly he has chosen his wirings to tell his story. Still, it’s fascinating what a little bit of running toward or away can accomplish.
I’m having fun, if nothing else.
Enough for today. See you next time!