Artificial Life: Hd

About the project

A hugely improved and updated version of my Artificial Life project using the Pimoroni Unicorn HAT HD!

Project info

Difficulty: Moderate

Platforms: Raspberry PiPythonPimoroni

Estimated time: 1 hour

License: GNU General Public License, version 3 or later (GPL3+)

Items used in this project

Hardware components

Pimoroni heatsink Pimoroni heatsink x 1
Pimoroni Unicorn HAT HD Pimoroni Unicorn HAT HD x 1
Pimoroni raspberry pi 3 model a+ case Pimoroni raspberry pi 3 model a+ case x 1
Pimoroni raspberry pi 3 model a+ Pimoroni raspberry pi 3 model a+ x 1

Software apps and online services

Multitool, Screwdriver Multitool, Screwdriver


The project before

This is an update on a previously existing project: Artificial Life which I made way back in the old times of 2017. It is essentially running a simulation on life forms represented by LED's on a matrix and seeing how they interact and evolve.

It used the original Unicorn HAT which has an 8x8 array of RGB LEDs; perfect for drawing a board of life forms to move around. It was also running on an original Pi Zero - so the processing power wasn't so good, especially for expansion of the project.

It was very glitchy, the life forms could pass and overwrite each other the image blanked out for a small amount of time as each life form moved and there was tons of messy/bad code that mad PyCharm sad.

I find this a lot when revisiting old projects for revision/sequels; with the amount of stuff I've learnt since going back and seeing all the old mistakes and things I could do better, it just shows how valuable it is to keep learning and evolving.

Hardware updates

So the first thing that needed an upgrade was the old Pi Zero - even on an 8x8 board it would struggle when running higher amounts of life forms, on top of the sub-optimal code it was running.

I upgraded to a Pi 3 A+; which has a nice form factor for fitting the Unicorn HAT on and also has some decent power with a quad core and 512mb RAM. I put a heatsink on the SoC and then put it in a nice PiBow case.

The next item on the list was to get the Unicorn HAT HD which has 16x16 pixel matrix, allowing for 256 maximum entities at once, over the 64 of the old Unicorn HAT SD. It also comes with a nice diffuser top which makes the LEDs look really nice and less harsh on the eyes.

So nice.

So nice.

Software updates

The very first thing was to move all of the python2 era code up to python3.

The original project had a rather messy approach to customising the various arguments that could be passed into it, such as max entity lifetime, max population, speed of the simulation etc. Now it has argsparse! Which tidies up the entire thing, now its far easier to just pass in parameters in any order and also with error handling and help functions. Here are the new parameters:

  -ilc LIFE_FORM_TOTAL, --initial-lifeforms-count LIFE_FORM_TOTAL
                        Number of lifeforms to start with
                        Time for the loop delay, essentially the game-speed
                        (although, lower; faster)
  -p POP_LIMIT, --population-limit POP_LIMIT
                        Limit of the population at any one time
  -ttl MAX_TTL, --max-ttl MAX_TTL
                        Maximum time to live possible for life forms
  -ma MAX_AGGRO, --max-aggression MAX_AGGRO
                        Maximum aggression factor possible for life forms
  -mb MAX_BREED, --max-breed-threshold MAX_BREED
                        Maximum breed threshold for entities
  -dc DNA_CHAOS, --dna-chaos DNA_CHAOS
                        Percentage chance of random DNA upon breeding of
  -se STATIC_ENTITY, --static-entity STATIC_ENTITY
                        Percentage chance of an entity being static
  -mt MAX_MOVES, --max-move-time MAX_MOVES
                        Maximum possible time to move number for entities
                        If a life form collides with another and both of their
                        aggression levels are within this range and combining
                        is enabled, they will combine (move together)
                        Maximum possible time to move number for entities
  -c, --combine-mode    Enables life forms to combine into bigger ones
  -mc, --minecraft-mode
                        Enables Minecraft mode
  -tr, --trails         Stops the HAT from being cleared, resulting in trails
                        of entities
  -dcd, --disable_collision_detection
                        Whether entities can spawn over each other or not
  -g, --gravity         Gravity enabled, still entities will fall to the floor
  -rt, --retry          Whether the loop will automatically restart upon the
                        expiry of all entities
  -sim, --unicorn-hat-sim
                        Whether to use the Unicorn HAT simulator or not
  -hm {SD,HD,MINI,CUSTOM}, --hat-model {SD,HD,MINI,CUSTOM}
                        What type of HAT the program is using. CUSTOM only
                        works with Unicorn HAT Simulator
                        Logging level

I also added in a proper logger, of which the level of logging can be adjusted by one of the above parameters. You can also select which HAT to use, either the SD HD or the Mini Unicorn HATs.

The main logic loop was tidied up, I had a lot of useless duplication of entities information such as positions and numbers of entities; I reduced this all down to just gathering information from the class holder itself. The class holder is a dictionary that contains all of the instantiations of the life form class and so it can be used to measure the amount of entities and also looped through to gather all information from; as it is modified during the loop it is copied out as a list per while loop iteration.

I moved some of the global variables out into a separate dataclass which can be accessed easily and I plan in future to make the entire system be able to run multiple simulations and have them interact in someway, using this class to create separate 'sessions'.

All of the entity modification functions have now been moved to be within the class that is instantiated for each entity. The collision detection has been hugely optimised and fixed so that entities will no longer fly through each other - this could still use some optimisation however, but it does work better and run faster now, which is a good bonus.

The way the entities interact with each other is now more dynamic, with changes to how they 'fight' and 'kill' each other; now entities have their own scale of aggression vs willingness to breed so you can now have super aggressive entities that will breed with each other rather than just purely annihilate each other. Entities can now also group up and move about as one combined entity if they have matching configurable ranges of aggression/breeding willingness so you will see clumps of them moving/buzzing around together.

Also now when entities engage in 'combat' there's the ability for the entity to take the dead ones life span and add it to its own, resulting in it 'living' longer. This results in some fairly vicious entities that buzz around the board and eat everything in sight.

Overall the breeding and combat chances have been much more balanced out so that it doesn't end up with just purely breeding or purely killing entities (of course this is tweakable with the arguments).

I also added in a 'gravity' mode, where non-moving entities will fall downwards until they move again, hit the bottom of the matrix or another entity. This has resulted in some really interesting situations and creations that I will show in the video and screenshots below.

There's also a retry mode now where if all the entities die, the loop will re-build from the initial conditions and start again.

On top of all of the customisable parameters there is now a file under config/ where all of these args have their defaults set. So you can run the program with no args being passed in and it will just use the settings here, allowing for cleaner run commands if you don't plan on changing much.

Also there is now a requirements.txt allowing for easy installation of all required libraries with:

sudo pip install -r requirements.txt

I have found that the Unicorn SD HAT requires sudo to run for some reason so here is the line I use to run the entire thing:

sudo env PATH="$PATH" python

The HD and Mini HATs may not require this, however and if you are using a different version of the RPi OS (I think I was using legacy throughout this) it may not be required also, give it a try yourself and see what happens. You can see the entire code plainly on GitHub before you clone out or run so you can check to see what it's doing before running it with sudo.

On top of all of that I am now using the Unicorn HAT Simulator; so if you are running on a PC you can still simulate the HAT with this neat library! If you run the installation for the requirements it will install everything it can then stop at the 3 Unicorn HAT libraries which don't exist on x86-64 Python PIP and when you run the program it will detect that the libraries aren't installed and put it in simulation mode, now has an argument and a parameter that allows for this mode to be run on anything as well as a custom size so you can make a 64x64 virtual Unicorn HAT :

Simulating a simulator.

Simulating a simulator.

That covers most of it - there was a ton of other changes but I think those are the most important and have the most effect for how the simulation runs; which we will dive into down below...

The project now

The updated code has resulted in a much more dynamic and interesting simulation with all kinds of strange structures and 'superlifeforms' (basically multiple entities clumped together that move around). Sometimes they buzz about like flies and consume everything in their wake.

Check out the video below for examples of runs with some commentary on what can happen with the varying parameters:

With gravity on certain structures are created that resemble buildings on the 'ground' rather than just random clumps:

The entities in the more open space were all moving around at that time, except the ones at the very top which somehow defied the gravity and became a 'cloud' of sorts.

The entities on the left there are 'raining' down other entities, I'm not entirely sure how this happens I can only assume that the entities are all linked together and trying to move up, but are all bumping into each other constantly and spawning more entities which somehow all start falling down? It's honestly something that baffles me and I wrote the code. I think it may be a bug, but one that has serendipitously become a really awesome feature.

Then there are structures that form in the upper part of the screen that occasionally send out other entities, but they seem to stay there for ages (the one on the left in the photos below lasted for hours before breaking down):

1 / 2

And then later on another formed on the right side:

And here are some screenshots of the program running without gravity enabled:


I am very happy with how this has turned out; it now runs much more smoothly (even on the old Pi Zero) and also produces far more interesting results.

The strange structures that seem to build up while other entities fly about reminds me of a city and then you see it all get destroyed by a cloud of seemingly acid rain producing entities floating in the sky, then another set of structures emerges and so on. It's like the beginning episode of Futurama while Fry is in the cryo-chamber.

It's really cool when unexpected things happen, such as the layer at the bottom where super strong entities appear to evolve and they just consume everything that falls on them. I'm not sure how but sometimes these are the ones that serve as foundations for structures. As well as the rain clouds and icicles that seem to form; I really need to trace through the code and see how on earth these things are coming into existence as still entities should fall down with gravity on and yet somehow there are clumps of them resisting the pull.

With 'gravity' off much different things happen, its more as if you are looking down at the board of entities rather than from the side so they all move around like something alive in a digital petri-dish. But you still get clusters of entities that stay around for a long time - shooting out other entities that fly around and do other crazy things, possibly making clouds of their own.

I have tried so many different configurations of parameters and it always shows really different and mostly unexpected results - I have even left it on for an entire day and just let it go, occasionally glancing over to see what oddities have grown or whatever chaos is flying around the board - only to look back a few minutes later and everything has changed.

There's a few things I need to work on such as getting the Thanos snap functionality back in, which I added in with my Infinity Gauntlet project. The Minecraft mode also needs a total re-work to be much better; I will be continuing to update the code so keep an eye on the GitHub repo and give it a star or even fork it out and try changing it.

I also may try adding in some AI/ML into the project at some point to try and make each life form smarter somehow - this is a very vague idea at the moment but hopefully it forms into something awesome. Suggestions welcome.

Please do feel free to get this code running on a Raspberry Pi with a Unicorn HAT or PC and share the results!

Thanks for reading and see you in the next project.

Artificial Life: Scary Edition

Artificial Life: Scary Edition


Artificial Life Python code

Instructions for the code are within the repo


Photo of 314Reactor


Technology loving nerd with a passion for trying to bring SciFi to life.


Leave your feedback...