Last active
December 11, 2015 04:08
-
-
Save JMV38/4542577 to your computer and use it in GitHub Desktop.
Ant simulation v2
Update v2.1 added the behavior so ants are not stuck on obstacles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--# Main | |
-- 0 fourmi | |
-- copyright JMV38 2013 - all rights reserved | |
displayMode(FULLSCREEN) | |
supportedOrientations(LANDSCAPE_ANY) | |
function setup() | |
-- codea settings | |
physics.gravity(0,0) | |
smooth() | |
-- game menu | |
buttons = Buttons() | |
-- initial game settings | |
pheromonVisible = false | |
antRunningMode = true -- those modes are linked: | |
antDesignMode = false | |
antDesignStep = 8 | |
-- world settings | |
PHERO_SCALE = 20 -- constant: the size reduction factor for pheromon images | |
world = World() | |
-- finger is a global object of world | |
-- create a 1rst ant family: | |
local brown = color(120, 65, 30, 255) | |
local v0 = vec2(WIDTH/20,HEIGHT/15) | |
local size = 0.7 | |
local familly = 1 | |
redAnts = Ants(50, brown ,size ,v0, familly) | |
-- create a 2nd ant family: | |
local black = color(0, 0, 0, 255) | |
v0 = vec2(WIDTH/15*4,HEIGHT/13*4) | |
size = 1.1 | |
-- blackAnts = Ants(100, black,size ,v0) | |
if FPS then | |
local dummyFps = FPS() -- corrects for the drawing bug | |
fps=FPS() | |
end | |
end | |
function draw() | |
background(255, 255, 255, 255) | |
-- normal mode | |
if antRunningMode then | |
if pheromonVisible then background(0, 0, 0, 255) end | |
world:draw() | |
if redAnts then redAnts:draw() end | |
if blackAnts then blackAnts:draw() end | |
if fps then fps:draw() end | |
end | |
-- alternative drawing for ant design | |
if antDesignMode then redAnts:drawDesignBench() end | |
-- in any case show buttons on top | |
buttons:draw() | |
end | |
function touched(touch) | |
buttons:touched(touch) | |
world:touched(touch) | |
end | |
local pi = math.pi | |
-- object types | |
local TYP_EDGE = 0 | |
local ANT = 1 | |
local HOME = 2 | |
local FOOD = 3 | |
local FINGER = 4 | |
-- index for ant texture parts | |
local ANT_TEXTURE_MOVE_MIN = 1 | |
local ANT_TEXTURE_MOVE_MAX = 6 | |
local ANT_TEXTURE_STOP = 7 | |
local ANT_TEXTURE_DEAD = 8 | |
-- physical action | |
local ANT_ACTION_DEAD = 1 | |
local ANT_ACTION_STOP = 2 | |
local ANT_ACTION_RUN = 3 | |
-- main objective of the ant | |
local GOAL_FIND_FOOD = 1 | |
local GOAL_FIND_HOME = 2 | |
local GOAL_ATTACK = 3 | |
local GOAL_LEAVE_HOME = 4 | |
local GOAL_ENTER_HOME = 5 | |
local GOAL_WAIT_IN_HOME = 6 | |
local GOAL_DEAD = 7 | |
function collide(contact) | |
local objA,objB = contact.bodyA.info,contact.bodyB.info | |
if objA then if objA.typ==ANT then objA:collide(objB,contact) end end | |
if objB then if objB.typ==ANT then objB:collide(objA,contact) end end | |
end | |
-- this function can be wrapped into a coroutine that will | |
-- 1/ apply a unismooth to img | |
-- 2/ autoadjust itself to 40 FPS | |
imgSmooth2 = function(img) | |
local budget = 0.001 | |
local targetDeltaTime = 1/40 | |
local r0,g0,b0,a0 | |
local r,g,b,a | |
local att = 2 | |
local imgGet,imgSet = img.get,img.set | |
local c,count =0,0 | |
local t0 = 0 | |
local clock = os.clock | |
local buffer1, buffer2= {},{} | |
while true do | |
for y = 2, img.height-1 do buffer1[y] = color(imgGet(img,1,y)) end -- init | |
-- copy values to edges | |
for y = 1, img.height do imgSet(img,1,y, color(imgGet(img,2,y)) ) end | |
for y = 1, img.height do imgSet(img,img.width,y, color(imgGet(img,img.width-1,y)) ) end | |
for x = 1, img.width do imgSet(img,x,1, color(imgGet(img,x,2)) ) end | |
for x = 1, img.width do imgSet(img,x,img.height, color(imgGet(img,x,img.height-2)) ) end | |
for x = 1, img.width do imgSet(img,x,img.height-1, color(imgGet(img,x,img.height-2)) ) end | |
-- average other values | |
for x = 2, img.width-1 do -- for each column of the image, | |
for y = 2, img.height-1 do -- compute the 3x3 average ... | |
r0,g0,b0,a0 = 0,0,0,0 | |
for p=1,3 do for q=1,3 do | |
r,g,b,a = imgGet(img,x+p-2,y+q-2) | |
r0,g0,b0,a0 = r0+r ,g0+g ,b0+b ,a0+a | |
end end | |
r,g,b,a = imgGet(img,x,y) | |
r0,g0,b0,a0 = r0/9, g0/9, b0/9, 255 | |
if r0<r then r0 = r else r0 = (r0-r) + r end | |
if g0<g then g0 = g else g0 = (g0-g) + g end | |
if b0<b then b0 = b else b0 = (b0-b) + b end | |
buffer2[y] = color(r0,g0,b0,a0) -- ... and save it in a buffer | |
-- (if the results are directly written, then next column cannot be computed) | |
end | |
for y = 2, img.height-1 do -- instead, | |
imgSet(img,x-1,y,buffer1[y]) -- write the results of PREVIOUS column | |
buffer1[y] = buffer2[y] -- and save current results for next col | |
end | |
if t0 < clock() then -- this is to stop if there no time left | |
--[[ if DeltaTime > targetDeltaTime -- autoadjustment to target FPS | |
then budget = budget*0.99 | |
else budget = budget*1.01 | |
end | |
--]] | |
count = count + 1 -- just for debug: proof the coroutine in runnning | |
coroutine.yield(false) -- leave the coroutine ... | |
t0 = clock()+1/60 * budget -- ... and when back compute new time limit | |
end | |
end | |
coroutine.yield(true) -- leave the coroutine with info it is finished | |
end | |
end | |
-- CAUTION: create a new wrap for each new img! | |
imgDecrease = function(img) | |
local budget = 0.001 | |
local targetDeltaTime = 1/40 | |
local r0,g0,b0,a0 | |
local r,g,b,a | |
local db,bmax = 0,0 | |
local imgGet,imgSet = img.get,img.set | |
local c,count =0,0 | |
local t0 = 0 | |
local dr = 1 | |
local clock = os.clock | |
while true do | |
for x = 1, img.width do -- for each column of the image, | |
for y = 1, img.height do | |
r0,g0,b0,a0 = imgGet(img,x,y) | |
r0,g0,b0,a0 = r0-dr,g0-1,b0-1,255 | |
if r0<0 then r0=0 end | |
if g0<0 then g0=0 end | |
if b0<0 then b0=0 end | |
-- if b0>250 then bmax = 250 end | |
-- if b0>0 then b0=b0-db end | |
imgSet(img,x,y, color(r0,g0,b0,a0) ) | |
end | |
if t0 < clock() then -- this is to stop if there no time left | |
--[[ if DeltaTime > targetDeltaTime -- autoadjustment to target FPS | |
then budget = budget*0.99 | |
else budget = budget*1.01 | |
end | |
--]] | |
count = count + 1 -- just for debug: proof the coroutine in runnning | |
coroutine.yield(false) -- leave the coroutine ... | |
t0 = clock()+1/60 * budget -- ... and when back compute new time limit | |
end | |
end | |
-- if bmax ==250 then db=1 else db=0 end | |
-- bmax=0 | |
coroutine.yield(true) | |
end | |
end | |
--# Ant | |
Ant = class() | |
-- copyright JMV38 2013 - all rights reserved | |
-- an Ant is just a data structure with no method, | |
-- to avoid memory overload by duplication of methods | |
-- utilities | |
local rnd = math.random | |
local deg = math.deg | |
local rad = math.rad | |
local cos = math.cos | |
local sin = math.sin | |
local floor = math.floor | |
-- constants | |
-- object types | |
local TYP_EDGE = 0 | |
local ANT = 1 | |
local HOME = 2 | |
local FOOD = 3 | |
local FINGER = 4 | |
-- index for ant texture parts | |
local ANT_TEXTURE_MOVE_MIN = 1 | |
local ANT_TEXTURE_MOVE_MAX = 6 | |
local ANT_TEXTURE_STOP = 7 | |
local ANT_TEXTURE_DEAD = 8 | |
-- physical action | |
local ANT_ACTION_DEAD = 1 | |
local ANT_ACTION_STOP = 2 | |
local ANT_ACTION_RUN = 3 | |
-- main objective of the ant | |
local GOAL_FIND_FOOD = 1 | |
local GOAL_FIND_HOME = 2 | |
local GOAL_ATTACK = 3 | |
local GOAL_LEAVE_HOME = 4 | |
local GOAL_ENTER_HOME = 5 | |
local GOAL_WAIT_IN_HOME = 6 | |
local GOAL_DEAD = 7 | |
function Ant:init(caller) | |
self.caller = caller | |
self.speed = 100 * caller.sizeFactor | |
self.changeDirPeriod = 0.5 | |
self.body = physics.body(CIRCLE,1*caller.sizeFactor/0.7) | |
local body = self.body | |
body.info = self | |
body.type = DYNAMIC | |
body.fixedRotation = true | |
body.position = caller.v0 | |
local angle = rad(rnd(360)) | |
body.angle = angle | |
self.linearVelocity = vec2(cos(angle),sin(angle))*self.speed | |
body.linearVelocity = self.linearVelocity | |
self.action = ANT_ACTION_RUN | |
-- init mesh information | |
local w,h = caller:getOneImageWH() | |
self.w = w *caller.sizeFactor | |
self.h = h *caller.sizeFactor | |
self.imgState = 0 | |
self.imgStateDelta = 1 | |
self.imgState0 = ANT_TEXTURE_MOVE_MIN | |
self.imgStateMax = ANT_TEXTURE_MOVE_MAX - ANT_TEXTURE_MOVE_MIN | |
self.coords = caller.coords | |
self.ms = caller.antsMesh | |
local v = caller.v0 | |
self.rect = self.ms:addRect(v.x,v.y,self.w,self.h,body.angle) | |
self.ms:setRectColor(self.rect,caller.color) | |
self.ms:setRectTex(self.rect,unpack(self.coords[self.imgState0])) | |
-- ant brain data | |
self.typ = ANT | |
self.familly = familly | |
self.goal = GOAL_LEAVE_HOME | |
self.decisionT0 = ElapsedTime + 1.5 | |
self.dead = false | |
-- self.iAmCloseFromHome = 255 | |
-- self.iAmCloseFromFood = 0 | |
-- self.lastHomeSmell = 0 | |
self.lastPheroPos = {-1,-1} | |
self.lastTouchT0 = 0 | |
self.closeFromHome0 = 254 | |
self.closeFromHome = self.closeFromHome0 | |
self.closeFromFood0 = 254 | |
self.closeFromFood = 0 | |
self.energy0 = 7000 | |
self.energy = self.energy0 | |
self.collide = caller.collide | |
end | |
function Ant:touched(touch) | |
end | |
--# Ants | |
Ants = class() | |
-- copyright JMV38 2013 - all rights reserved | |
-- shortcuts to functions (accelerate) | |
local rnd = math.random | |
local deg = math.deg | |
local rad = math.rad | |
local cos = math.cos | |
local sin = math.sin | |
local floor = math.floor | |
-- constants | |
-- object types | |
local TYP_EDGE = 0 | |
local ANT = 1 | |
local HOME = 2 | |
local FOOD = 3 | |
local FINGER = 4 | |
local TYP_ANT = 1 | |
local TYP_HOME = 2 | |
local TYP_FOOD = 3 | |
local TYP_FINGER = 4 | |
local OBJ_OBSTACLE = 5 | |
-- index for ant texture parts | |
local ANT_TEXTURE_MOVE_MIN = 1 | |
local ANT_TEXTURE_MOVE_MAX = 6 | |
local ANT_TEXTURE_STOP = 7 | |
local ANT_TEXTURE_DEAD = 8 | |
-- physical action | |
local ANT_ACTION_DEAD = 1 | |
local ANT_ACTION_STOP = 2 | |
local ANT_ACTION_RUN = 3 | |
-- main objective of the ant | |
local GOAL_FIND_FOOD = 1 | |
local GOAL_FIND_HOME = 2 | |
local GOAL_ATTACK = 3 | |
local GOAL_LEAVE_HOME = 4 | |
local GOAL_ENTER_HOME = 5 | |
local GOAL_WAIT_IN_HOME = 6 | |
local GOAL_DEAD = 7 | |
function Ants:init(n,c,size,v0,familly) | |
self.antsMesh = mesh() -- the mesh to draw this family | |
self.miscMesh = mesh() -- another mesh for home base, etc... | |
self.color = c -- color of these ants | |
self.sizeFactor = size -- small size adjustment | |
self.count = 0 -- internal clock | |
self.v0 = v0 -- vec2 starting position | |
self.body = {["position"]=v0} -- a non-clean trick to use ant function | |
self.familly = familly | |
self:antMeshInit() | |
self:miscMeshInit() | |
self.antList = {} | |
self:homeBaseInit() | |
-- the ant house data | |
self.foodStock = 100 | |
self.antEggs = n | |
self.antCreateTime = 0 | |
self.antCreateDelta = 0.5 | |
-- pheromons | |
local w = floor(WIDTH/PHERO_SCALE)+3 | |
local h = floor(HEIGHT/PHERO_SCALE) +3 | |
self.pheromon = image(w,h) | |
setContext(self.pheromon) | |
background(0, 0, 0, 255) | |
-- fill(0,0,120,255) | |
-- rect(1,1,w-3,h-3) | |
setContext() | |
pheromon = self.pheromon | |
self.doPheroSmooth = true | |
self.pheroSmooth = coroutine.wrap( imgSmooth2 ) | |
self.pheroDecreaseT0 = 10 | |
self.pheroDecrease = coroutine.wrap( imgDecrease ) | |
end | |
function Ants:draw() | |
self.count = self.count + 1 | |
self:antsMeshUpdate() | |
self.miscMesh:draw() | |
self.antsMesh:draw() | |
self:antCreate() | |
self:antsUpdate() | |
self:pheroForce() | |
end | |
function Ants:pheroXY(ant) | |
local pos = ant.body.position | |
local x,y | |
x = floor(pos.x/PHERO_SCALE)+2 | |
y = floor(pos.y/PHERO_SCALE)+2 | |
return x,y | |
end | |
function Ants:pheroAdd(ant) | |
local x0,y0 = unpack(ant.lastPheroPos) | |
local x,y = self:pheroXY(ant) | |
if x~=x0 or y~=y0 then | |
local r,g,b,a = self.pheromon:get(x,y) | |
if ant.closeFromHome > 0 then | |
ant.closeFromHome = ant.closeFromHome - 2 | |
if b<ant.closeFromHome | |
then b = ant.closeFromHome | |
else ant.closeFromHome = b | |
end | |
-- local db = 10 + ant.closeFromHome/25 | |
-- if b<ant.closeFromHome then b = b + db end | |
-- if b>255 then b=255 end | |
end | |
if ant.closeFromFood > 0 then | |
-- local dr = ant.closeFromFood/5*2 | |
ant.closeFromFood = ant.closeFromFood - 2 | |
if r<ant.closeFromFood | |
then r = ant.closeFromFood | |
else ant.closeFromFood = r | |
end | |
-- if r<ant.closeFromFood then r = r + dr end | |
-- if r>255 then r=255 end | |
end | |
self.pheromon:set(x,y,r,g,b,255) | |
ant.lastPheroPos = {x,y} | |
end | |
end | |
function Ants:pheroForce() | |
local x,y = self:pheroXY(self) | |
local r,g,b,a = self.pheromon:get(x,y) | |
self.pheromon:set(x,y,r,g,255,255) | |
end | |
function Ants:antCreate() | |
if self.antCreateTime < ElapsedTime then | |
self.antCreateTime = ElapsedTime + self.antCreateDelta | |
if self.antEggs > 0 then | |
self.antEggs = self.antEggs -1 | |
table.insert(self.antList,Ant(self)) | |
end | |
end | |
end | |
function Ants:antsMeshUpdate() | |
-- actions to do every frame for all ants | |
local ms = self.antsMesh | |
local coords = self.coords | |
local body | |
for _,ant in pairs(self.antList) do | |
body = ant.body | |
body.linearVelocity = ant.linearVelocity --(avoids sliding) | |
-- upddate visual | |
ant.imgState = (ant.imgState + ant.imgStateDelta) % ant.imgStateMax | |
ms:setRect(ant.rect,body.position.x,body.position.y,ant.w,ant.h,body.angle) | |
ms:setRectTex(ant.rect,unpack(coords[ant.imgState + ant.imgState0])) | |
if ant.energy < 1000 then ms:setRectColor(ant.rect,color(255,0,0,255)) end | |
end | |
end | |
function Ants:antsUpdate() | |
local nextDecision = self.makeDecision | |
for _,ant in pairs(self.antList) do | |
-- decrease energy | |
ant.energy = ant.energy - 1 | |
-- if time's up, make new decision | |
if ant.decisionT0 < ElapsedTime then nextDecision(self,ant) end | |
-- put pheromon | |
-- if ant.lastTouchT0 < ElapsedTime then self:pheroAdd(ant) end | |
self:pheroAdd(ant) | |
end | |
if self.pheroDecreaseT0< ElapsedTime and not self.doPheroDecrease | |
then self.doPheroSmooth = true end | |
local stepFinished = false | |
if self.doPheroSmooth then stepFinished = self.pheroSmooth(self.pheromon) end | |
-- if self.doPheroSmooth then stepFinished = true end | |
if stepFinished then | |
self.doPheroSmooth = false | |
self.doPheroDecrease = true | |
end | |
stepFinished = false | |
if self.doPheroDecrease then stepFinished = self.pheroDecrease(self.pheromon) end | |
if stepFinished then | |
self.doPheroDecrease = false | |
self.pheroDecreaseT0 = ElapsedTime + 1 | |
end | |
end | |
function Ants:makeDecision(ant) | |
local goal = ant.goal | |
local t0 | |
if goal == GOAL_FIND_FOOD then self:walkAround(ant) | |
elseif goal == GOAL_LEAVE_HOME then ant.goal = GOAL_FIND_FOOD | |
elseif goal == GOAL_FIND_HOME then self:walkAround(ant) | |
elseif goal == GOAL_DEAD then self:kill(ant) | |
elseif goal == GOAL_LEAVE_HOME then | |
elseif goal == GOAL_LEAVE_HOME then | |
elseif goal == GOAL_LEAVE_HOME then | |
end | |
if ant.energy <= 5000 then ant.goal = GOAL_FIND_HOME end | |
if ant.energy <= 0 then ant.goal = GOAL_DEAD end | |
end | |
-- GOAL_DEAD = 0 | |
-- GOAL_FIND_FOOD = 1 | |
-- GOAL_FIND_HOME = 2 | |
-- GOAL_ATTACK = 3 | |
-- GOAL_LEAVE_HOME = 4 | |
-- GOAL_ENTER_HOME = 5 | |
-- GOAL_WAIT_IN_HOME = 6 | |
function Ants:walkAround(ant) | |
local n = 5 | |
if rnd(n) == 1 then self:shortPause(ant) else | |
if ant.goal == GOAL_FIND_HOME then self:toHome(ant) end | |
if ant.goal == GOAL_FIND_FOOD then self:toFood(ant) end | |
end | |
end | |
function Ants:changeDir(ant,duration) | |
ant.action = ANT_ACTION_RUN | |
self:setActionTexture(ant) | |
local speed,angle | |
local body = ant.body | |
speed = ant.speed *(0.8+rnd()*0.4) | |
angle = self:toHomeAngle(ant) | |
if angle then angle = angle + rad(rnd()*180 + 90) | |
else angle = body.angle + rad(rnd()*180 - 90) end | |
body.angle = angle | |
ant.linearVelocity = vec2(cos(angle),sin(angle)) * speed | |
body.linearVelocity = ant.linearVelocity | |
local waitingTime = duration or 1 | |
ant.decisionT0 = ElapsedTime + waitingTime*(1+rnd()) | |
end | |
function Ants:toHome(ant,duration) | |
ant.action = ANT_ACTION_RUN | |
self:setActionTexture(ant) | |
local speed,angle | |
local body = ant.body | |
speed = ant.speed *(0.8+rnd()*0.4) | |
angle = self:toHomeAngle(ant) | |
if angle then angle = angle + rad(rnd()*60 - 40) | |
-- if angle then | |
else angle = body.angle + rad(rnd()*180 - 100) end | |
body.angle = angle | |
ant.linearVelocity = vec2(cos(angle),sin(angle)) * speed | |
body.linearVelocity = ant.linearVelocity | |
local waitingTime = duration or 1 | |
ant.decisionT0 = ElapsedTime + waitingTime*(0.5+rnd()) | |
end | |
function Ants:toFood(ant,duration) | |
ant.action = ANT_ACTION_RUN | |
self:setActionTexture(ant) | |
local speed,angle | |
local body = ant.body | |
speed = ant.speed *(0.8+rnd()*0.4) | |
angle = self:toFoodAngle(ant) | |
-- if angle then | |
if angle then angle = angle + rad(rnd()*60 - 40) | |
else | |
angle = self:toHomeAngle(ant) | |
if angle then angle = angle + rad(rnd()*60 - 40 +180) | |
else angle = body.angle + rad(rnd()*180 - 100) end | |
end | |
body.angle = angle | |
ant.linearVelocity = vec2(cos(angle),sin(angle)) * speed | |
body.linearVelocity = ant.linearVelocity | |
local waitingTime = duration or 1 | |
ant.decisionT0 = ElapsedTime + waitingTime*(0.5+rnd()) | |
end | |
function Ants:pause(ant,duration) | |
ant.action = ANT_ACTION_STOP | |
self:setActionTexture(ant) | |
ant.linearVelocity = vec2(0,0) | |
ant.body.linearVelocity = ant.linearVelocity | |
local waitingTime = duration or 0.5 | |
ant.decisionT0 = ElapsedTime + waitingTime*(1+rnd()) | |
end | |
function Ants:kill(ant,duration) | |
ant.action = ANT_ACTION_DEAD | |
self:setActionTexture(ant) | |
ant.linearVelocity = vec2(0,0) | |
ant.body.linearVelocity = ant.linearVelocity | |
local waitingTime = duration or 60 | |
ant.decisionT0 = ElapsedTime + waitingTime*(1+rnd()) | |
end | |
function Ants:shortPause(ant) | |
self:pause(ant,0.2) | |
end | |
local xdir = vec2(1,0) | |
local srqt2 = math.sqrt(2) | |
local dir000 = xdir | |
local dir045 = xdir:rotate(45)/srqt2 | |
local dir090 = xdir:rotate(90) | |
local dir135 = xdir:rotate(135)/srqt2 | |
local dir180 = xdir:rotate(180) | |
local dir225 = xdir:rotate(225)/srqt2 | |
local dir270 = xdir:rotate(270) | |
local dir315 = xdir:rotate(315)/srqt2 | |
function Ants:toFoodAngle(ant) | |
local map = self.pheromon | |
local x,y = self:pheroXY(ant) | |
local ref,c,_,angle,v | |
ref,_,_ = map:get(x,y) | |
c,_,_ = map:get(x+1,y) v = dir000*(c-ref) | |
c,_,_ = map:get(x+1,y+1) v = dir045*(c-ref) + v | |
c,_,_ = map:get(x,y+1) v = dir090*(c-ref) + v | |
c,_,_ = map:get(x-1,y+1) v = dir135*(c-ref) + v | |
c,_,_ = map:get(x-1,y) v = dir180*(c-ref) + v | |
c,_,_ = map:get(x-1,y-1) v = dir225*(c-ref) + v | |
c,_,_ = map:get(x,y-1) v = dir270*(c-ref) + v | |
c,_,_ = map:get(x+1,y-1) v = dir315*(c-ref) + v | |
if v:lenSqr()>0 then angle = xdir:angleBetween(v) end | |
return angle | |
end | |
function Ants:toHomeAngle(ant) | |
local map = self.pheromon | |
local x,y = self:pheroXY(ant) | |
local ref,c,_,angle,v | |
_,_,ref = map:get(x,y) | |
_,_,c = map:get(x+1,y) v = dir000*(c-ref) | |
_,_,c = map:get(x+1,y+1) v = dir045*(c-ref) + v | |
_,_,c = map:get(x,y+1) v = dir090*(c-ref) + v | |
_,_,c = map:get(x-1,y+1) v = dir135*(c-ref) + v | |
_,_,c = map:get(x-1,y) v = dir180*(c-ref) + v | |
_,_,c = map:get(x-1,y-1) v = dir225*(c-ref) + v | |
_,_,c = map:get(x,y-1) v = dir270*(c-ref) + v | |
_,_,c = map:get(x+1,y-1) v = dir315*(c-ref) + v | |
if v:lenSqr()>0 then angle = xdir:angleBetween(v) end | |
return angle | |
end | |
function Ants:forceVelocity(ant,v) | |
local body = ant.body | |
angle = xdir:angleBetween(v) | |
body.angle = angle | |
ant.linearVelocity = v | |
body.linearVelocity = v | |
end | |
function Ants.collide(ant,obj,contact) | |
local state = contact.state | |
local typ = obj.typ | |
if not typ then return end | |
if typ == TYP_HOME and (state==BEGAN or state==ENDED) then | |
ant.goal = GOAL_FIND_FOOD | |
ant.closeFromHome = ant.closeFromHome0 | |
ant.closeFromFood = 0 | |
ant.energy = ant.energy0 | |
end | |
if typ == TYP_FOOD and (state==BEGAN or state==ENDED) then | |
ant.goal = GOAL_FIND_HOME | |
ant.closeFromHome = 0 | |
ant.closeFromFood = ant.closeFromFood0 | |
ant.energy = ant.energy0 | |
end | |
if (typ == TYP_EDGE )and (state==BEGAN) then | |
local v = ant.linearVelocity | |
if obj.side=="left" or obj.side=="right" then v.x = -v.x end | |
if obj.side=="bottom" or obj.side=="top" then v.y = -v.y end | |
local body = ant.body | |
local angle = xdir:angleBetween(v) | |
body.angle = angle | |
ant.linearVelocity = v | |
body.linearVelocity = v | |
end | |
if ( typ == OBJ_OBSTACLE )and (state==BEGAN) then | |
local v = ant.linearVelocity | |
local pos0 = obj.body.position | |
local body = ant.body | |
local pos1 = body.position | |
local normal = (pos1-pos0):normalize() | |
local a = normal:dot(v) | |
v = v - 2*a*normal | |
body.angle = xdir:angleBetween(v) | |
ant.linearVelocity = v | |
body.linearVelocity = v | |
end | |
end | |
function Ants:setActionTexture(ant) | |
local function setAction(ant,delta,state0,state1) | |
local stateMax | |
if state1 then stateMax = state1 - state0 + 1 else stateMax = 1 end | |
if ant.imgState0 ~= state0 then | |
ant.imgState = 0 | |
ant.imgState0 = state0 | |
ant.imgStateMax = stateMax | |
ant.imgStateDelta = delta | |
end | |
end | |
local action = ant.action | |
if action == ANT_ACTION_DEAD then setAction(ant,0,ANT_TEXTURE_DEAD) | |
elseif action == ANT_ACTION_STOP then setAction(ant,0,ANT_TEXTURE_STOP) | |
elseif action == ANT_ACTION_RUN then | |
setAction(ant,1,ANT_TEXTURE_MOVE_MIN,ANT_TEXTURE_MOVE_MAX) | |
elseif action == ANT_ACTION_BACKRUN then | |
setAction(ant,-1,ANT_TEXTURE_MOVE_MIN,ANT_TEXTURE_MOVE_MAX) | |
end | |
end | |
function Ants:homeBaseInit() | |
-- the home base | |
self.baseRadius = 35 | |
self.base = physics.body(CIRCLE,self.baseRadius) | |
self.base.position = self.v0 | |
self.base.sensor = true | |
self.base.info = {["typ"]=HOME,["familly"]= self.familly } | |
end | |
-- ######### functions for initial drawings of misc ####################### | |
function Ants:miscMeshInit() | |
local function setRectZlevel(ms,i,z) | |
local v | |
local k0 = (i-1)*6 | |
for k = k0+1,k0+6 do | |
v = ms:vertex(k) | |
v.z = z | |
ms:vertex(k,v) | |
end | |
end | |
img = image(100,100) | |
setContext(img) | |
pushStyle() | |
fill(255, 255, 255, 255) | |
ellipseMode(CORNER) | |
ellipse(0,0,100,100) | |
popStyle() | |
setContext() | |
local ms = self.miscMesh | |
local p = self.v0 | |
ms.texture = img | |
local i = ms:addRect(p.x,p.y,70,70) | |
ms:setRectColor(i, color(178, 150, 116, 255)) | |
setRectZlevel(ms,i,-5) | |
i = ms:addRect(p.x,p.y,40,40) | |
ms:setRectColor(i, color(0, 0, 0, 200)) | |
setRectZlevel(ms,i,-4) | |
--[[ | |
i = ms:addRect(p.x,p.y,50,50) | |
ms:setRectColor(i, color(0, 0, 0, 128)) | |
setRectZlevel(ms,i,5) | |
--]] | |
i = ms:addRect(p.x,p.y,30,30) | |
ms:setRectColor(i, color(0, 0, 0, 255)) | |
setRectZlevel(ms,i,5) | |
end | |
-- ######### functions for initial drawings of ants ####################### | |
function Ants:drawDesignBench() | |
-- special to built images | |
local ant = self.antList[1] | |
local s = 10 | |
self.antsMesh:setRect(ant.rect, WIDTH/2, HEIGHT/2, ant.w*s, ant.h*s, 0) | |
ant.body.linearVelocity = vec2(0) | |
self.antsMesh:setRectTex(ant.rect, unpack(self.coords[antDesignStep])) | |
noSmooth() | |
self.antsMesh:draw() | |
smooth() | |
end | |
function Ants:antMeshInit() | |
local img = {} | |
local coords = {} | |
-- get the images of various ant positions | |
for i=ANT_TEXTURE_MOVE_MIN,ANT_TEXTURE_MOVE_MAX | |
do img[i] = self:loadImg(i) end | |
img[ANT_TEXTURE_STOP] = self:stopImg() | |
img[ANT_TEXTURE_DEAD] = self:deadImg() | |
-- definition of 1 image | |
local w0 = img[1].width | |
local h0 = img[1].height | |
-- stick then im a single texture image | |
local w = w0 * #img | |
local h = h0 | |
local tex = image(w,h) | |
setContext(tex) | |
background(255, 255, 255, 0) | |
setContext() | |
for i =1,#img do | |
local im = img[i] | |
local x1 = (w0)*(i-1) | |
coords[i] = {(x1)/w, 0,w0/w, 1} | |
for x=1,im.width do for y=1,im.height do | |
tex:set(x+x1,y,color(im:get(x,y))) | |
end end | |
end | |
self.coords = coords | |
self.antsMesh.texture = tex | |
end | |
function Ants:getOneImageWH() | |
return 31,31 | |
end | |
function Ants:loadImg(phase) | |
local w,h = self:getOneImageWH() | |
self.img = image(w,h) | |
local img = self.img | |
setContext(img) | |
pushMatrix() pushStyle() | |
background(0, 0, 0, 0) | |
fill(255, 255, 255, 255) | |
stroke(255, 255, 255, 255) | |
self:bodyImg() | |
self:frontLeg(phase,"left") | |
self:frontLeg(phase+3,"right") | |
self:midLeg(phase+1,"left") | |
self:midLeg(phase+4,"right") | |
self:backLeg(phase+2,"left") | |
self:backLeg(phase+5,"right") | |
popMatrix() popStyle() | |
setContext() | |
return img | |
end | |
function Ants:stopImg() | |
local w,h = self:getOneImageWH() | |
self.img = image(w,h) | |
local img = self.img | |
setContext(img) | |
pushMatrix() pushStyle() | |
background(0, 0, 0, 0) | |
fill(255, 255, 255, 255) | |
stroke(255, 255, 255, 255) | |
self:bodyImg() | |
self:frontLeg(1,"left") | |
self:frontLeg(1,"right") | |
self:midLeg(3,"left") | |
self:midLeg(3,"right") | |
self:backLeg(5.99,"left") | |
self:backLeg(5.99,"right") | |
popMatrix() popStyle() | |
setContext() | |
return img | |
end | |
function Ants:deadImg() | |
local w,h = self:getOneImageWH() | |
self.img = image(w,h) | |
local img = self.img | |
setContext(img) | |
pushMatrix() pushStyle() | |
background(0, 0, 0, 0) | |
fill(255, 255, 255, 255) | |
stroke(255, 255, 255, 255) | |
self:bodyImg("dead") | |
self:deadLegs() | |
popMatrix() popStyle() | |
setContext() | |
return img | |
end | |
function Ants:moveLeg(phase,side,ref,front,back) | |
-- phase: 0 to 5.99, 0 = front and 5.99 = back | |
local q = (phase - math.floor(phase/6)*6)/6 | |
local p = 1-q | |
local sgn | |
if side =="left" then sgn=1 else sgn=-1 end | |
local x0,y0 = unpack(ref) | |
local x1a,y1a,x2a,y2a = unpack(front) -- front position | |
local x1b,y1b,x2b,y2b = unpack(back ) -- back position | |
local x1,y1 = x1a*p + x1b*q , y1a*p + y1b*q | |
local x2,y2 = x2a*p + x2b*q , y2a*p + y2b*q | |
self:leg(x0,y0,x1,y1*sgn,x2,y2*sgn) | |
end | |
function Ants:frontLeg(phase,side) | |
local x0,y0 = 17,(self.img.height + 0)/2 | |
self:moveLeg(phase,side,{x0,y0},{0,0,5,7},{0,0,3,7}) | |
self:moveLeg(phase,side,{x0,y0},{4,6,11,12},{3,6,0,12}) | |
end | |
function Ants:midLeg(phase,side) | |
local x0,y0 = 14,(self.img.height + 0)/2 | |
self:moveLeg(phase,side,{x0,y0},{0,0,3,6},{0,0,1,6}) | |
self:moveLeg(phase,side,{x0,y0},{3,5,3,9},{1,5,-1,9}) | |
self:moveLeg(phase,side,{x0,y0},{3,8,3,14},{-1,8,-3,12}) | |
end | |
function Ants:backLeg(phase,side) | |
local x0,y0 = 13,(self.img.height + 0)/2 | |
self:moveLeg(phase,side,{x0,y0},{0,0,0,6},{0,0,-3,6}) | |
self:moveLeg(phase,side,{x0,y0},{1,6,-4,6},{-2,6,-9,6}) | |
self:moveLeg(phase,side,{x0,y0},{-3,6,-4,13},{-7,6,-13,9}) | |
end | |
function Ants:deadLegs() | |
local x0,y0 = self.img.width/2,(self.img.height + 0)/2 | |
translate(x0,y0) | |
self:leg(0,0,0,0,2,5) | |
self:leg(0,0,2,5,4,-4) | |
self:leg(0,0,4,-3,-2,-7) | |
self:leg(0,0,0,0,0,4) | |
self:leg(0,0,0,4,2,-4) | |
self:leg(0,0,2,-4,-1,-10) | |
translate(2,-1) | |
rotate(10) | |
self:leg(0,0,0,0,2,5) | |
self:leg(0,0,2,5,4,-4) | |
self:leg(0,0,4,-3,-2,-7) | |
self:leg(0,0,0,0,0,4) | |
self:leg(0,0,0,4,2,-4) | |
self:leg(0,0,2,-4,-1,-10) | |
translate(-3,0) | |
rotate(-10) | |
self:leg(0,0,0,0,-2,5) | |
self:leg(0,0,-2,5,-4,-4) | |
self:leg(0,0,-4,-3,2,-7) | |
self:leg(0,0,0,0,0,4) | |
self:leg(0,0,0,4,-2,-4) | |
self:leg(0,0,-2,-4,1,-10) | |
end | |
function Ants:bodyImg(spec) | |
local y0 = (self.img.height + 1)/2 | |
local x0 = (self.img.width + 1)/2 | |
if spec==nil then | |
strokeWidth(0) | |
ellipse(6,y0,10,8) -- abdomen | |
ellipse(14,y0,12,4) -- centre | |
ellipse(21,y0,6,6) -- tete | |
self:legs(22,y0,0,0,3,5) -- antenne | |
self:legs(22,y0,3,5,9,4) | |
elseif spec == "dead" then | |
pushMatrix() | |
translate(x0,y0) | |
strokeWidth(0) | |
ellipse(0,0,12,4) -- centre | |
rotate(20) | |
ellipse(-8,0,10,8) -- abdomen | |
rotate(-40) | |
ellipse(6,0,6,6) -- tete | |
self:legs(7,0,0,0,3,5) -- antenne | |
self:legs(7,0,3,5,9,4) | |
popMatrix() | |
end | |
end | |
function Ants:leg(x0,y0,x1,y1,x2,y2) | |
strokeWidth(3) | |
lineCapMode(SQUARE) | |
line(x0+x1,y0+y1,x0+x2,y0+y2) | |
end | |
function Ants:legs(x0,y0,x1,y1,x2,y2) | |
self:leg(x0,y0,x1,y1,x2,y2) | |
self:leg(x0,y0,x1,-y1,x2,-y2) | |
end | |
function Ants:touched(touch) | |
-- Codea does not automatically call this method | |
end | |
--# World | |
World = class() | |
-- copyright JMV38 2013 - all rights reserved | |
-- object types | |
local OBJ_EDGE = 0 | |
local OBJ_ANT = 1 | |
local OBJ_HOME = 2 | |
local OBJ_FOOD = 3 | |
local OBJ_FINGER = 4 | |
local OBJ_OBSTACLE = 5 | |
local rnd = math.random | |
function World:init() | |
-- visibility | |
local ms = mesh() | |
local img = image(200,200) | |
setContext(img) | |
pushStyle() | |
background(0, 0, 0, 0) | |
fill(255, 255, 255, 255) | |
ellipse(100,100,200,200) | |
popStyle() | |
setContext() | |
ms.texture = img | |
local i = ms:addRect(75,HEIGHT-15,150,30) | |
-- rect for fps | |
local c = color(127, 127, 127, 255) | |
ms:setRectColor(i,c) | |
ms:setRectTex(i,0.5,0.5,0.1,0.1) | |
self.ms = ms | |
-- edges (not too close to real edges, due to image read) | |
edge = {} | |
local d = PHERO_SCALE | |
edge[1] = physics.body(EDGE,vec2(d,d),vec2(WIDTH-d,d)) | |
edge[1].info = {typ=OBJ_EDGE, side="bottom"} | |
edge[2] = physics.body(EDGE,vec2(d,d),vec2(d,HEIGHT-d)) | |
edge[2].info = {typ=OBJ_EDGE, side="left"} | |
edge[3] = physics.body(EDGE,vec2(WIDTH-d,d),vec2(WIDTH-d,HEIGHT-d)) | |
edge[3].info = {typ=OBJ_EDGE, side="right"} | |
edge[4] = physics.body(EDGE,vec2(d,HEIGHT-d),vec2(WIDTH-d,HEIGHT-d)) | |
edge[4].info = {typ=OBJ_EDGE, side="top"} | |
local obstacle = {} | |
local body,r | |
r = 40 | |
self.d = r*2 | |
-- food | |
for j=1,2 do | |
body = physics.body(CIRCLE,r) | |
body.type = DYNAMIC | |
body.sensor = true | |
body.friction = 0.01 | |
body.x = WIDTH/2 + (math.random()-0.5)*WIDTH/1.2 | |
body.y = HEIGHT/2 + (math.random()-0.5)*HEIGHT/1.2 | |
body.mass = 100 | |
obstacle[j] = body | |
i = ms:addRect(body.x,body.y,2*r,2*r) | |
body.info = {i=i,w=2*r,h=2*r,["typ"]=OBJ_FOOD,["r"]=r,body=body} | |
local foodColor = color(58, 146, 55, 255) | |
ms:setRectColor(i,foodColor) | |
ms:setRectTex(i,0,0,1,1) | |
end | |
-- obstacles | |
for j=1,4 do | |
-- body = physics.body(POLYGON,vec2(-10,-100),vec2(-10,100),vec2(10,100), vec2(10,-100)) | |
r = 100 | |
body = physics.body(CIRCLE,r) | |
body.type = DYNAMIC | |
body.friction = 0.01 | |
body.x = WIDTH/2 + (math.random()-0.5)*WIDTH/1.2 | |
body.y = HEIGHT/2 + (math.random()-0.5)*HEIGHT/1.2 | |
body.fixedRotation = true | |
obstacle[#obstacle+1] = body | |
i = ms:addRect(body.x,body.y,20,200,0) | |
body.info = {i=i,w=2*r,h=2*r,["typ"]=OBJ_OBSTACLE,["r"]=r,body=body} | |
body.mass = 100 | |
local obstacleColor = color(70, 52, 30, 255) | |
ms:setRectColor(i,obstacleColor) | |
-- ms:setRectTex(i,0.5,0.5,0.1,0.1) | |
-- ms:setRectTex(i,0.4,0.4,0.2,0.2) | |
end | |
self.obstacle = obstacle | |
end | |
function World:draw() | |
local body,pos,i,d,v,_,w,h | |
local ms = self.ms | |
for _,body in pairs(self.obstacle) do | |
pos = body.position | |
-- print(unpack(body.info)) | |
v = body.info | |
i,w,h = v.i, v.w, v.h | |
ms:setRect(i,pos.x,pos.y,w,h) | |
body.linearVelocity = body.linearVelocity * 0 | |
end | |
-- noSmooth() | |
local w,h = pheromon.width, pheromon.height | |
w , h = WIDTH*w/(w-2) , HEIGHT *h/(h-2) | |
if pheromonVisible then sprite(pheromon,WIDTH/2,HEIGHT/2,w,h) end | |
-- smooth() | |
ms:draw() | |
end | |
function World:touched(touch) | |
local pos | |
local t = vec2(touch.x,touch.y) | |
if touch.state == BEGAN then | |
for i,obj in pairs(self.obstacle) do | |
pos = obj.position | |
if pos:dist(t)<obj.info.r then touchedObject = obj end | |
end | |
elseif touch.state == MOVING then | |
if touchedObject then touchedObject.position = t end | |
elseif touch.state == ENDED or touch.state == CANCELLED then | |
touchedObject = nil | |
end | |
end | |
--# Buttons | |
Buttons = class() | |
-- copyright JMV38 2013 - all rights reserved | |
function Buttons:init() | |
-- all the buttons | |
local side = "right" -- "right" or "left" | |
local w0 = 60 | |
local b = {} | |
self.b = b | |
local N = 11 | |
local but | |
-- show pheromons | |
but = Button(2,N,side,w0) | |
b[1] = but | |
but.enabled = true | |
but.onclick = function() pheromonVisible = not pheromonVisible end | |
but.txt = "pheros" | |
-- show ant design | |
but = Button(3,N,side,w0) | |
b[2] = but | |
but.enabled = false | |
but.onclick = function() | |
antDesignMode = not antDesignMode | |
antRunningMode = not antRunningMode | |
if antDesignMode then | |
self.b["antDesignStep"].txt = tostring(antDesignStep) | |
self.b["antDesignStep"].enabled = true | |
physics.pause() | |
else | |
self.b["antDesignStep"].enabled = false | |
physics.resume() | |
end | |
end | |
but.txt = "design" | |
-- not a button, just to show the current design step | |
but = Button(4,N,side,w0) | |
b[3] = but | |
but.enabled = false | |
local changeStep = function(d) | |
antDesignStep = antDesignStep + d | |
if antDesignStep > #redAnts.coords then antDesignStep = 1 end | |
if antDesignStep < 1 then antDesignStep = #redAnts.coords end | |
self.b["antDesignStep"].txt = "- "..tostring(antDesignStep).." +" | |
end | |
but.onRclick = function() changeStep(1) end | |
but.onLclick = function() changeStep(-1) end | |
but.txt = "data" | |
b["antDesignStep"] = but | |
-- settings | |
but = Button(1,N,side,w0) | |
b[4] = but | |
but.enabled = true | |
self.settings = false | |
but.onclick = function() | |
self.settings = not self.settings | |
b[2].enabled = self.settings | |
b[3].enabled = false | |
antDesignMode = false | |
antRunningMode = true | |
end | |
but.txt = "settings" | |
end | |
function Buttons:draw() | |
pushStyle() pushMatrix() | |
resetMatrix() resetStyle() | |
for i,b in ipairs(self.b) do b:draw() end | |
popStyle() popMatrix() | |
end | |
function Buttons:touched(touch) | |
for i,b in ipairs(self.b) do b:touched(touch) end | |
end | |
--# Button | |
Button = class() | |
-- copyright JMV38 2013 - all rights reserved | |
function Button:init(n,N,side,w0) | |
local x0 | |
if side=="left" then x0 = w0+3 else x0 = WIDTH - (w0 +3) end | |
self.x = x0 | |
self.y = HEIGHT/(N+1)*(N-n+1) | |
self.w = w0 | |
self.h = HEIGHT/(N+1)/2.1 | |
self.fontSize = WIDTH/30*8/N | |
self.gray = color(127, 127, 127, 255) | |
self.red = color(255, 0, 0, 255) | |
self.purple = color(197, 15, 204, 255) | |
self.black = color(60, 60, 60, 255) | |
self.white = color(255, 255, 255, 255) | |
self.butColor = self.gray | |
self.txtColor = self.black | |
self.txt = "button "..tostring(n) | |
self.enabled = false | |
self.onclick = function() end -- do nothing by default | |
self.onRclick = function() end -- do nothing by default | |
self.onLclick = function() end -- do nothing by default | |
end | |
function Button:draw() | |
if self.enabled then | |
rectMode(RADIUS) | |
fill(self.butColor) | |
rect(self.x,self.y,self.w,self.h) | |
fill(self.txtColor) | |
textMode(CENTER) | |
textWrapWidth(self.w*1.8) | |
fontSize(self.fontSize) | |
font("Baskerville-BoldItalic") | |
text(self.txt,self.x,self.y) | |
end | |
end | |
local abs = math.abs | |
function Button:touched(touch) | |
if self.enabled then | |
if touch.state == BEGAN then | |
if (abs(touch.x- self.x)<self.w) and (abs(touch.y- self.y)<self.h) | |
then | |
self.onclick() | |
if (touch.x > self.x) then self.onRclick() else self.onLclick() end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment