The articles from these past couple of weeks kinda burned me out. After wrapping it up, I decided I was going to take it easy, do something simple and brainless in a nice comfy high-level language, and just not worry about what kind of writeups I could get from it.
This backfired, of course, and now I have a whole new project I want to do. But in the meantime, I also spent a nice lazy afternoon with PICO-8 making a port of Simulated Evolution.
Getting all of this working was mostly a matter of bringing together various techniques I’ve already had to use in other contexts—some of which aren’t even PICO-8. Below the fold I’ll talk about what challenges I faced, how I solved them, and link to the related older projects that helped me along the way.
Implementing the Simulation
The first challenge, of course, was that I had to write the simulation itself. My earlier implementations were all in C or assembly language, and PICO-8 needs to be programmed in its own dialect of Lua. Happily, Lua is, itself, pretty comfy, so adapting the C code was straightforward. It was not, however, perfectly exact.
The original BASIC code I had adapted was a little bit buggy. It intended to loop through all the bugs in each simulation step, but because of the way iterations interacted with births and deaths, sometimes a bug would have its turn skipped if some other bug died or fissioned near it in memory. My C code made this more consistent while retaining much of the structure of the BASIC original. The Lua version, however, diverges a little bit in its design, and as a result while I did keep things consistent, it’s differently consistent.
All my previous implementations kept all the bug creatures in an array. If a bug died, the last bug in the array would be moved over to take its place and the total size of the array would drop. To ensure that the moved bug didn’t miss its turn—this was the issue in the BASIC original—it would meddle with the iterator to reprocess the same index with its new bug. A similar process was done when a bug fissioned: one of the newborns would replace the original parent, while the other was appended to the array. All of this was in the service of safely modifying a collection while iterating through it.
My Lua implementation elected to instead simply not do that. It instead made use of something more like a “for-each” loop, which makes it much less feasible to meddle with the iterator index in this way. However, the closest thing that Lua has to arrays are hashtables with integer keys, which in turn means they’re easy to create, grow, and shrink. For the Lua implementation I created BORN and DIED tables to collect the necessary changes along the way, and then use its table-manipulation functions after the iteration to delete any dead bugs and introduce any newly-born ones. However, that does introduce our discrepancy: in my previous implementations, the newborns had a simulation tick on the same turn they were created, while in the Lua one it does not. Neither result is truly crucial to the correctness of the simulation, but I’ll take either of these over the original behavior where exactly one of the newborns got a simulation tick on their birth turn.
Screen Memory as Real Memory
I wanted to replicate the trick I did in my Cyclic Cellular Automaton project, where the spritesheet is used as a sort of extra backbuffer for the screen. This would let me efficiently both track and render the plankton without declaring a supplemental 15,000-element array. There’s just one problem: that spritesheet is indexed as a 128×128-pixel region, and I need it to be a 150×100 one.
... continue reading