j kongerants
mountains and websites

ants

Posts tagged with ants

    ants devlog 4: what if an ant could sense when food's nearby?

    wait, let’s make use of the tools given to us by default: here’s the commit for this post’s code, on Github

    had i thought on this far enough ahead, perhaps we’d have had a PR with many commits, one for each change, but in that idea’s place let’s follow a simple three act structure (out of 5, as you will see very soon our work ends with a large and tragic turn)

    act i: setting the scene

    before anything else, let’s simplify the vars we use in our ant_move() code: previously, we referenced our current ant’s x and y via their object, but if we want to reference them more often in our movement function, as we plan to, it makes more sense to simplify those to just x and y. after all, its what we’ll mean in this the most

    any other code in this area is replacing these vars throughout

    act 2: a tired joke about not wanting to remove bugs

    this ones simple, and well worth taking note of. in our _init() we added a new variable debug_text, set to a blank string, and we end our _draw() with a call to print this variable to the screen (at the end, so we know it’s always on top of everything). at the moment, we don’t add anything to this string, as we’ve more or less cleaned thing up, but a simple call of debug_text = debug_text .. "whatever we wanna check on" keeps our business on the screen. very nice

    also: could we loop thru an array of strings to make this easier to add to? who cares! we can still do anything we want to later!

    act three: the actual changes we’ve made

    notice!

    this is wrong. this is very wrong. the sensing function we make is incredibly functional, but the way we choose to use it is very wrong. we just gotta pretend it isn’t for a bit of narrative. spoiler, howver, this will end up bad

    should be simple, right? we have a randomized movement function, let’s slot in some new movement types. under our list of movement options, let’s add an elseif and add in a new function for looking nearby

    find_nearby_dots

    ^^ see that up there, that’s what we’ll be calling our sensing function. interested and detail-oriented readers (of whom, at the time of writing, i have none) will notice this is not in the linked commit. that’s cause i messed up rebasing. i have no desire to fix this, so it stands

    that said, we can add the function in whole here now:

    function find_nearby_dots(x,y,dist)
       local vals={}
    
       for i=0,dist-1 do
          local x1,x2=x+dist-i,x-dist+i
          local y1,y2=y+i,y-i
    
          if(0==i) then
             add(vals, {x1,y1})
             add(vals, {x2,y1})
             add(vals, {y2,x1})
             add(vals, {y2,x2})
          else
             add(vals, {x1,y1})
             add(vals, {x2,y1})
             add(vals, {x1,y2})
             add(vals, {x2,y2})
          end
       end
    
       return vals
    end

    isn’t plaintext wonderful! the above is essentially nonsense requiring a day or two of doing math (i have an MFA in creative writing), graph paper, highlighters, coordinate writing, refusing to look up answers because I’m doing this to keep my own creative forces occupied, not to make the most technically proper game. i will not be adding these notes to the page as ive moved apartment recently and dont wanna hafta get me scanner out. in place, let’s just explain the finished process:

    in essense, thru guess-and-check i determined that for each new pixel of distance an ant is able to sense, another four pixels need to be sensed around it. i dont wanna go into the formula because i wrote it about a week ago and have since forgot it, but suffice it to say in the local variable lines at the top of the for loop we add or subtract a specific amount from the passed distance to create four new points of ‘vision’. they mirror each other quite well, so we can add them with only a few calculations. as you can see as well, the first loop works a little different in how its mirrored (not how our x and y variables are altered by the loop number: this is what causes this), so we get our points of vision differently

    each of these points is added to an array, which is then returned: this is an array of all our senses points

    math-brained types might notice this only gives us our outer ring of vision. yep: this only returns our outer ring. this is on purpose, as it adds a bit more flexability: we can use the function to determine what pixels are a certain distance of steps away with this one function or, by calling the function from within a loop, determine all the pixels that distance or less. i like that

    (for the moment, imagining myself saving memory wheter I am or not, i’m only looking at the outer ring in code later. but, as shown above, it’s an easy change if i’m incorrect)

    (also, not how easily this could be re-written to not need an x and y variable: we could simply return a distance amount rather than an absolute pixel. oh well, i realized that when writing this up. maybe, if it ends up convenient, we’ll change that later)

    using find_nearby_dots

    in this first paragraph here i’m insulting myself a week or so ago for not understanding how my earlier movement system worked. to skip ahead to the explanation, i was not balancing movement well. i was forgetting that my previous randomization took the option of no movement into account to create more jagged, natural seeming motions. this means that while there’s still only about a two percent chance to move each cardinal direction, there’s now a 92% chance to move toward a sensed food pixel, and no chance to stay put. this means that every frame, of which there are 60 in a second, our ants will move. that’s stupid fast, in practice

    back to our ant movement functions, surely we can just slot this into our movement functions, right?? surely this is simple. see line 105 in the github, see our local variable to hold these produced sensed squares, scent_squares. we’ve simply added another branch to our movement if statement, which is surely going to add a “more likely” movement choice, in comparison to our 2% chance movements from before

    we use our loop pixels function to loop thru the sensed pixels we’ve gathered and check: is it food? if so, we move towards it, prefering vertical movement over horizontal for the sole reason that i use the computer on my phone a lot, so i imagine computer screens as intrinsically tall.

    by the way, does this function move us multiple times each way as written? i think so. i haven’t tested this out visually for reasons we’ll explore below

    stop fucking around, show me why this sucks

    see that? we move too many times per second. its one thing to swarm. this is something else

    for next time, we’ll look into refactoring our movement decision code, so we can fix this

    ants devlog 3

    this is part of a series. see the previous post here


    let’s take the issue head on: my attempt to write this as a pseudo-tutorial has failed. i find the writing style draining, boring, forced, etc etc. worse, the more i put it off, the further i get from when i wrote the first part of my little ants simulation, so there ain’t nothing there (or at least there’s less and less there), to build on, it becomes an exercise in bland uninteresting recollection, i would prefer not to.

    thus a new beginning, more honest to what i’m actually doing when i write these: let’s look at my furthest draft of the code and reconstruct together what i think my ideas were

    the code itself

    we’re up to some 200 lines now, so lets put these into little tabs. explanation are after our little guy here, so scroll on by for more

    0: basic functions
    function _init() 
       -- allow mouse :)
       poke(0x5f2d, 1)
    
       cur_init()
       ants_init()
       food_init()
    end
    
    function _update60()
       cur_update()
       local click=stat(34)
       --left click
       if(1==click) then
          sfx(0,-1,4)
          ants_add(cur_x,cur_y)
       end
       --right click
       if(2==click) then
          food_add(cur_x,cur_y)
       end
       
       food_update()
       ants_update()
    end
    
    function _draw()
       cls(4)
       food_draw()
       ants_draw()
       --ants_count()
       cur_draw()
    end
    1: cursor
    function cur_init()
       cur_spr=1
       cur_x=60
       cur_y=60
    end
    
    function cur_draw()
       spr(cur_spr,cur_x,cur_y)
    end
    
    function cur_update()
       --mouse loc
       cur_x=stat(32)-1
       cur_y=stat(33)-1
    end
    
    2: ants
    
    function ants_init()
       ants={}
    end
    
    function ants_add(x,y)
       ants[#ants+1]={x=x,y=y,age=0}
    end
    
    function ants_loop(func)
       loop_pixels(ants,func)
    end
    
    function ants_draw()
       ants_loop(
          function(ant)
             pset(ant.x,ant.y,0)
          end
       )
    end
    
    function ants_move()
       ants_loop(ant_move)
    end
    
    function ant_move(ant)
       local movement=rnd(100)
       
       if(95<movement) then
          ant.x=ant.x+1
       elseif(90<movement) then
          ant.y=ant.y+1
       elseif(85<movement) then
          ant.x=ant.x-1
       elseif(80<movement) then
          ant.y=ant.y-1
       end
       
       ant.x=loop_screen(ant.x)
       ant.y=loop_screen(ant.y)
    end
    
    function ants_age()
       ants_loop(function(ant)
             -- basic rand change to die
             if(ant) then
    			local dead=
                   flr(rnd(2000-ant.age))
    			if(0==dead) then
                   del(ants,ant)
    			else
                   ant.age = ant.age+1
    			end
             end
       end)
    end
    
    function ants_count()
       local text="ants: " .. #ants
       print(text,2,120,6)
    end
    
    function ants_eat()
       -- we actually loop the food
       -- to easy unset it
       food_loop(function(food)
             if(food) then
    			local eat=pget(food.x,food.y)
    			if(eat==0) -- has ant on it
    			then
                   --make ant live longer
                   foreach(ants, function(ant)
                              if(ant.x==food.x and 
                                 ant.y==food.y) then
                                 ant.age=ant.age-100
                              end
                   end)
                   --remove the food
                   del(foods,food)
                   sfx(1)
    			end
             end
       end)
    end
    
    function ants_update()
       ants_move()
       ants_eat()
       ants_age()
    end
    3: loop helpers
    function loop_screen(pos)
       if(128<pos) return 0
       if(0>pos) return 128
    
       return pos
       end
    
    function loop_pixels(arr,func)
       for i=1,#arr do
          func(arr[i])
       end
    end
    
    4: food
    function food_init()
       f_sprite={2,3}
       foods={}
       food_can_add=0
    end
    
    function food_add(c_x,c_y)
       --only add if not delayed
       if(0<=food_can_add) return
       
       sfx(2)
       --reset delay
       food_can_add=25
       local s_offset=
          rnd(f_sprite) * 8
       
       -- loop the sprite to add
       -- pixels
       for x=0,7 do
          for y=0,7 do
             local pix=sget(x+s_offset,y)
             if(0!=pix) then
                foods[#foods+1]=
                   {x=c_x+x,y=c_y+y,col=pix}
             end
          end
       end
    end
    
    function food_draw()
       food_loop(
          function(food)
             pset(food.x,food.y,food.col)
          end
       )
    end
    
    function food_loop(func)
       loop_pixels(foods,func)
    end
    
    function food_update()
       food_can_add=food_can_add-1
    end
    

    the explanations

    i like accordions. let’s do that again:

    0: basic functions

    this one's mostly straightforward: call the init, update, and draw functions of further tabs. in init, we poke to enable mouse use, in update we handle clicks, and in draw we clear the screen.

    1: cursor

    another simple one. in `init`, we set our cursor sprite, in `update` we pull in the x and y position of our cursor with inexplicable functions even _i_ dont understand (minus 1, for some sweet visual alignment tricks: it makes sure we dont hide our single pixel ants), and draw draws a sprite to the screen at that position. i think its neat. its simple. its a software sort of thing

    2: ants

    now we're doing something! we've got a lot here. if you've noticed, i have a tendency to order functions in a sort of least-to-most order, aka 'reverse', so let's describe them in reverse also, starting at the bottom of our code:

    ants_update

    the simple one: we call all the functions we write above. our ants will move, eat, then age and possibly die

    ants_eat

    our first hard one, in part reliable on some functions in our food tab (so read ahead!). in short, we have an array of pixels of ‘food’ as well as their x and y positions. we then read the screen data of the foods x and y coords: if its palette is 0, which is an ant, we 1) make the ant live longer by subtracting from its age value and 2) delete the food from the food array (see that tab bb). we also play a sound effect

    ants_count

    debug func: prints the count of ants to the screen

    ants_age

    we loop through our ants via the to-be-described ants_loop function and try to kill them via rng. essentially: the closer their age gets to 2000, the higher the chance they die. if they live, we age them by one, increasing their chance to die later

    ants_move and ant_move

    loop through and move our ants. this parts lame as hell right now, just randomizing the direction and checking to loop the screen. this’ll be a HUGE change point in our shared future

    ants_draw

    loop our ants and set their pixels on the screen. simple as hell

    ants_loop

    and you thought you might learn? no! we call a loop pixels functions on our ant, described elsewhere!

    ants_add and ants_init

    simple: we add an ant object at x and y in ants_add, we create a global ants array in init. this is real simple stuff, unfortunately

    3: loop helpers

    aint the most complicated things so simple also? `loop_screen` makes sure our position is on the 128x128 screen, `loop_pixels` takes in an array and runs a function on every member of it

    why is these together, if so different? i dont’ know. i wrote this months ago

    4: food

    our last little bit of complexity, then we're through. You've got this, reader. I've got this, writer, too.

    in init, we set up three variables. f_sprite holds a list of sprites that we can produce as food, foods holds all the food pixels we’ve set on screen (the same way we’ve set pixels for ants), and food_can_add helps us set a little delay so that we can’t just paint food across our screen

    food_add adds food to the x+y off the cursor: first we made sure our food can be added by checking if our food_can_add is zero (or less---so we don’t have to worry about keeping the addable time to zero); if we’re good, we play a little sound, reset our food delay to 25 frames, then pick a sprite at random from our f_sprite variable set above. with that in place, we loop through the pixels of our food sprite and add them to our food array at the cursor location

    food_draw loops these pixels to add them to the screen; food_loop makes looping these pixels easier

    finally, on food_update, we count our food adding counter down one tick per frame

    so: that’s a lot. its also a small bit messy, but oh well. ants are insects who live in dirt, we might as well attempt their lifestyles if we’d like to simulate

    next up, back in the code for this, we’ll try to add an ability for our ants to scent out food near them and follow, as well as follow each others scents. let’s give it a go

    apologies to everyone for occasionally misspelling it as ‘aunts’. i will not go back and fix this

    ants devlog 2

    this is part of a series. see the previous post here

    ants

    now that we have a world, let’s create life in it. because we are working with systems it seems imperative we create a model of our life cycle. thus:

    → birth
        ↪ struggle
            ↪ death

    in other words, to create life true to life, we must allow our ants to live, to struggle, and to die. once we have these, we’ll have our most basic simulation

    notice!

    because struggle is often difficult to represent visually, we will be showing movement in its place

    birth

    but how will we birth our ants? ‘ants’ is of course a generalized term here, little more than metaphor, but if we follow our metaphor as example we open ourselves to a whole complex reproduction apparatus, queens and soldiers and social roles and all, which is more than we are ready to simulate now. we’ll need another way to birth them, with an artificially increased control

    a shame: we must reveal ourselves so early

    revealing the creators hand

    pico-8 offers optional mouse cursor support. it’s a sort of imitation secret, in that only its implementation seems obscure, its existence is known by most who use the program. also, more relevantly, it seems the perfect way to spawn out ants: use the mouse to select a pixel, click, and bring a single ant to life

    to enable the mouse, we need only call the below function somewhere in our program. don’t worry what it means now. the meanings mostly unimportant 1

     poke(0x5f2d, 1)

    at least now we have a use for our _init() function: to init our cursor. let’s put that at the top of our file:

    function _init()
    	poke(0x5f2d, 1)
    end

    try to run it now? does anything happen? no: we need to make our hand

    revealing the creator’s hand, pt 2

    hi! my name is j konger. its a pseudonym, of course. few parents give their child a single initial name. i had the idea this devlog would be fun to write in the voice of a tutorial, but the more i write on this the more i realize i need to drill down, explain things neither you or i find very interesting. instead of scrapping this work however (in accordance with my site’s first rule), i am changing course into simple narrative. i hope you don’t mind.

    a shame: we must change our course so early

    cursor

    code

    this is the image i used for my cursor. you’ll notice it’s the pico 8 cursor almost exactly, with the color changed. we’ll get to that later, maybe several posts from now

    and this is the code i used to make the cursor follow the mouse, which i put in a separate tab:

    -- cursor
    
    function cur_init()
    	cur_spr=1
    	cur_x=60
    	cur_y=60
    end
    
    function cur_draw()
    	spr(cur_spr,cur_x,cur_y)
    end
    
    function cur_update()
    	--mouse loc
    	cur_x=stat(32)-1
    	cur_y=stat(33)-1
    end

    and, under our main tab, we now have this:

    function _init() 
       -- allow mouse :)
       poke(0x5f2d, 1)
       cur_init()
    end
    
    function _draw()
    	cls(4)
    	cur_draw()
    end
    
    function _update60()
       cur_update()
    end

    explanation

    let’s start with that second part of code, as its simplest: for readability, we add a new init, update, and draw function to our cursor, then just call them in the generic version of each. we call this ‘separation of concerns’. we call this ‘elegant code’

    the other tab is simple also, just not as much. let’s go function by function, ok? i think that sounds nice:

    cur_init

    here, we just set some defaults. cur_spr is the index of our sprite, cur_x and cur_y the starting position of our cursor, which we add only because we need to call it in…

    cur_draw

    here were merely draw the sprite to screen: spr() draws a sprite, and cur_x and cur_y tell it where to go, x- and y-wise. as we’re using the mouse here, we are continuing to use obscurant functions. all we need to know for our purposes is that stat(32) returns our cursor’s x coordinate, stat(33) its y, and that we subtract one pixel distance from each so our cursor points at the coordinate rather than overlaps, thus looking overall more ‘real’ to our precious user (in truer words ‘familiar’)

    end

    so now we have a hand in our own creation. if nothing else, that’s worthy of our pride. reach out now, with your pointer device of choice, and touch your world. hit ctl-r on your keyboard to run the app and watch the cursor move

    full code and downloads

    cartridge

    (right click to save, open in pico-8)

    full code

    -- ants
    -- by j konger
    
    function _init() 
       -- allow mouse :)
       poke(0x5f2d, 1)
       cur_init()
    end
    
    function _draw()
    	cls(4)
    	cur_draw()
    end
    
    function _update60()
       cur_update()
    end
    
    
    -->8
    -- cursor
    
    function cur_init()
    	cur_spr=1
    	cur_x=60
    	cur_y=60
    end
    
    function cur_draw()
    	spr(cur_spr,cur_x,cur_y)
    end
    
    function cur_update()
    	--mouse loc
    	cur_x=stat(32)-1
    	cur_y=stat(33)-1
    end
    

    i’d like to have embeds here in a later post, once i figure out how to do that with my static site generator. perhaps that will exist by the time the game gets interesting




    Footnotes

    1. ok, you wanna know? here’s the explanation i used to learn it

    ants devlog 1

    to live is to struggle, of course, as well to die; pico-8 is a fantasy computer console.

    in pico-8 i am simulating bugs. it is clear they aren’t actual bugs, but bugs are the smallest creature i’ve seen that was also living (in high school, with microscopes, we made the bacteria die). therefore i am calling my creatures bugs, as a tip of the hat to the fact that they are living (or in other words outside of my control)

    world

    first we must create a world for them to live in. pico8 offers three default functions, _init(), _draw(), and _update() (or, in our case, the closely related _update60() to do something similar in 60fps) to simulate our world, so let’s start with these

    _init() creates our world. at the moment we will be existentialists and encounter it as given. thus we will not touch upon it now

    _update60() creates action in our world. now, as perhaps idealists, we will ignore this for now.

    _draw() in fact is all we need to create our world. software after all is not concerned with reality, only representation, thank god. thus, we need only add the color of dirt to our draw function, as dirt is the insects’ natural home. pico-8 uses a integer index-based color palatte, so let’s draw with that now:

    function _draw()
        cls(4)
    end

    what does this mean? _draw(), run every frame, will create an image of all we’ve made. within in, cls() paints the screen a single color 1, which we’ve set to 4, in other words brown, the color of earth

    every second, in other words, we will be reminded the earth is brown. i find this wonderful

    our world so far: so much dirt

    Footnotes

    1. clears the graphics buffer, so to say