For a while now I have been thinking about using a Raspberry Pi with some sort of visual indicator as a form of artificial life simulator, I wanted to create something that would be able to spawn a number of entities that would have random properties and be able to move around and interact with each other, forming a sort of virtual ecosystem; and then experiment with it to see what happens.
So the other week I fired up a Raspberry Pi and began to work on a program with the following goals in mind:
- Create a number of artificial lifeforms that can move around a board and have colour/movement properties assigned to them via 3 random numbers; the ‘DNA’ of the life-form – and display them onto an easy-to-observe output.
- Have those artificial lifeforms be able to interact with each other to ‘breed’ and pass along their traits to offspring, as well as ‘kill’ each other to keep the population in check.
- Have random chance for ‘genetic chaos’ whereby instead of passing along a life-form’s properties to its offspring a random number is inserted into the offspring’s ‘DNA’.
- BONUS – plug the code into the Minecraft API and see what random patterns of blocks can be spawned from the artificial life-form’s movements and properties.
- Other generic stuff, monitor, peripherials etc. for using the Pi
As you can see above I opted to go for the original Unicorn HAT as a display for the project, it gives a nice platform to work on, allows a huge number of colours and patterns and also has a nice robust and easy to use API.
I started off using PiBakery to produce a generic Raspbian Jessie installation onto the SD Card (best to use Jessie as I don’t think the original Unicorn HAT has a curl installer for Stretch yet), and plugging the Unicorn HAT on the top of my Raspberry Pi Zero.
Once the basic installation of the OS is on and running do the usual:
sudo apt update && sudo apt dist-upgrade -y
Just to make sure everything is nicely up to date.
Now to get the Unicorn HAT working follow the instructions here and run some examples to make sure it’s working as expected.
A good few hours of work went into the code and it also went through a number of iterations – see the annotated code in the code section b to see what it does.
You can run the above code by downloading it as ‘PiLife.py’ to a RPi with a Unicorn HAT installed and typing in:
sudo python PiLife.py 1 2 1 10000 1000
Now you should see a little random colour dot appear and buzz around for a while, until it eventually disappears.
You can also type in:
sudo python PiLife.py /help
And the program will display what the numbers mean – I’ll also explain them here:
The first number is the number of Lifeforms the program will spawn on launch, in the case of the above; 1.
The second number determines the amount of seconds to wait between each loop, essentially determining the speed of the program; in this case 2 seconds per loop.
The third number is the maximum number of Lifeforms allowed at any one time, like a population limit; 1, in the above case.
The Fourth number determines the maximum time to live possible for all lifeforms, in this case 10,000 loops of the program.
The final number is the maximum aggression factor for all lifeforms, in the above case; 1000.
There is also an optional ‘-mc’ switch that can be added to the end that will enable it to interact with the Minecraft python API.
When run a number of entities are generated – dictated by the above input argument, these are all assigned a random colour and direction to go in, when they hit the edge of the Unicorn HAT they move in another random direction until they expire – or collide with another entity.
At this point 1 of 3 main things can happen:
- The entity can ‘breed’ with the one it collided with
- The entity can ‘attack’ the entity it collided with
- The entity can simply bounce off the one it collided with
These are all determined by the aggression factor of the entity currently moving – the loop handles one at a time in terms of processing and drawing to the Unicorn HAT’s LED array.
If the entity has a high aggression factor (above 850) it will attempt to attack the other entity, if the other entities aggression factor is lower it will lose and be removed from the grid and vice versa.
With an aggression factor lower than 850 the entity will attempt to breed with the other entity, passing along traits from each – with some randomisation involved, for example:
- Entity A has a lifeseed 1 of 525, lifeseed 2 of 8909 and a lifeseed 3 of 98
- Entity B has a lifeseed 1 of 6512, lifeseed 2 of 11 and a lifeseed 3 of 155
- Entity C – the offspring has a 40% chance of getting its lifeseed 1 as 525 a 40% chance of 6512 and a 20% chance of a totally new random number; and so on for the remaining lifeseeds
This ensures that the entities can share their ‘DNA’ and traits along to their offspring, with a small chance of change so that there can be random differences in the offspring – for good or for better for the offspring.
For instance, an offspring could take the lifeseed from the parent with the long maximum lifespan – instead of the parent with the short maximum lifespan, and the lifeseed that controls the number chosen between 0 and the maximum possible lifespan; resulting in an entity that potentially has an greatly improved lifespan over either of its parents.
Stick with me here.
If the offspring entity ends up with a brand new random lifeseed, say for aggression factor and it is now a super aggressive one – it will then begin to attack everything it comes into contact with, with the exception of entities with equal or higher aggression. But an entity coming into contact with it may attempt to breed, possibly resulting in a new form of entity which takes over the board due to its high aggression.
A similar idea is applied if an offspring placement is attempted where a lifeform currently resides on the board – if the entities aggression factor is above 250 it will attempt to erase the lifeform in the way – unless the other lifeform has a higher aggression factor; in which case the one that initiated the conflict will become deleted and no offspring produced. If the factor is under 250, it will give up attempted to created offspring and continue on its way.
In the event two entities have the same aggression factor (anywhere above 850) they will simply bounce off each other without incident.
The idea, in a summary is to create random ‘lifeforms’ with specific traits that move around and collide with each other, creating varied offspring; and those that have the traits that let them live and breed better should be the successful ones; this being reflected on the Unicorn LED grid.
With a chance for the lifeseed variation there can be the chance that their lifespans suddenly drop and they become, essentially, extinct.
I added in aggression factor and lifespan also as a way to try and keep the project interesting; in the early phases if 2 lifeforms sparked off they wouldn’t stop, ever. So I had to introduce the above genetic randomness and also the ability for them to attack each other etc.
When the program is ended, either by all lifeforms becoming extinct, or the user exiting the program a display is given of total number of lifeforms produced, maximum concurrent lifeforms that existed as well as a last count of total concurrent lifeforms.
The random number generator that controls the lifeseeds is a number between 1 and 1000000000000, meaning that there are at least (according to Microsoft calc) – 1.e+36 possible combinations, at the very least, as the argument inputs can enable further combinations. The Unicorn has uses LED’s that have approximately 16 million different colours possible, so there will be some variation between entities that look the same, for instance a bright blue entity can have a different lifespan from another identical bright blue entity on the board.
I have configured the random number generation and lifeseeds in such a way that the look of the entity should not have any predictable correlation between its properties – for example a bright blue entity will not always be a more aggressive one.
In theory, you should never see the same program outcome more than once.
At the moment this number is controlled by the default seed, which is the current time in Python; I have thought about adding in an external sensor such as a light or noise sensor to assist with the generation of the random numbers – by plugging in the candle power or decibels they are sensing into the random generation algorithm.
Below is a video covering the following configurations as experiments (Video contains flashing images):
1 entity that can last a long time:
sudo python PiLife.py 1 0 20 10000 1000
5 entities with long lifespans and a nice spread of aggressive to non-aggressive, max population of 30:
sudo python PiLife.py 5 0 30 10000 1000
lifeforms with a shorter lifespan and max population of 20:
sudo python PiLife.py 2 0 20 100 1000
mostly aggressive lifeforms with 64 starting with but a 10 population limit, meaning they will die or kill each other off until only 10 remain on the board to interact with each other:
sudo python PiLife.py 64 0 10 250 10000
10 lifeforms with a decent lifespan, max population of 64 so that there can be a lot of variation possible fromt the start:
sudo python PiLife.py 10 0 64 150 1000
64 lifeforms with a population limit of 10, but all non-aggressive/high lifespan, so they will slowly expire until only 10 remain to interact with each other:
sudo python PiLife.py 64 0 10 1000 250
As you can see I ran each experiment twice, once in light and once in the dark – originally to see which would be easier to see on video, but I left in both sets of results anyway, see below the overview of results:
Experiment 1, run 1: Total lifeforms produced: 1 Max concurrent Lifeforms was: 1 Experiment 1, run 2: All Lifeforms have expired. Total lifeforms produced: 1 Max concurrent Lifeforms was: 1
The conditions of experiment 1 are designed to allow you to see the movement of an entity in this program, when there are many at once it can be difficult to see exactly what each individual one is doing – the behaviour reminds me of an insect and is rarely the same twice. In the first iteration of the experiment the life-form moves around simply until it expires, in the second iteration the spawned entity is not met with RNG luck and expires almost immediately.
Experiment 2, run 1: Total lifeforms produced: 108 Max concurrent Lifeforms was: 30 Last count of active Lifeforms: 30 Experiment 2, run 2: Total lifeforms produced: 31 Max concurrent Lifeforms was: 30 Last count of active Lifeforms: 30
Experiment 2 has enough variation in its spawns that they breed successfully and last quite some time, in the first iteration they continue until the program is exited breeding fresh entities constantly as can be seen – in the second iteration they seem to have a longer lifespan in general as only 1 more than the initial set is produced.
Experiment 3, run 1: All Lifeforms have expired. Total lifeforms produced: 2 Max concurrent Lifeforms was: 2 Experiment 3, run 2: Total lifeforms produced: 43 Max concurrent Lifeforms was: 20 Last count of active Lifeforms: 20
Experiment 3 illustrates the difficulty of obtaining a constant experiment, as two entities alone will not often collide with each other in team or produce suitable offspring to survive – in iteration 2 it takes a couple of attempts in order to get a successful run and even then there is little variation in the entities.
Experiment 4, run 1: All Lifeforms have expired. Total lifeforms produced: 178 Max concurrent Lifeforms was: 64 Experiment 4, run 2: All Lifeforms have expired. Total lifeforms produced: 70 Max concurrent Lifeforms was: 64
The first iteration of experiment 4 is interesting as the most aggressive lifeforms whittle down the population to the cap very quickly – and then remain until the end of the experiment where they all seem to expire at the same time, I would surmise that due to the lack of variation they simply all had lower total life and too high aggression to breed sufficiently. Iteration 2 yielded similar results, except all entities expired far quicker this time.
Experiment 5, run 1: Total lifeforms produced: 252 Max concurrent Lifeforms was: 64 Last count of active Lifeforms: 64 Experiment 5, run 2: Total lifeforms produced: 167 Max concurrent Lifeforms was: 64 Last count of active Lifeforms: 64
Experiment 5 had a configuration where on both occasions there was much variation and the population went on for quite some time, until I manually stopped the program as above – also from prior experience these are the configurations that tend to last indefinitely.
Experiment 6, run 1: Total lifeforms produced: 158 Max concurrent Lifeforms was: 64 Last count of active Lifeforms: 10 Experiment 6, run 2: Total lifeforms produced: 1130 Max concurrent Lifeforms was: 64 Last count of active Lifeforms: 10
By far the most interesting experiment on both occasions; iteration 1 ran for a long time – sometimes seeming like the population might fizzle out, however managing to keep itself going; it appears the starting lifeforms had an incredibly high lifespan as few were produced during the length of the experiment. Iteration 2 you can see that eventually it is whittled down to one type of entity, however due to the random variation introduced into each offspring’s properties the entities begin to change and by the end there is more variation and random occurrences among the population – both of these I imagine would continue indefinitely, with slowly changing populations as time went on.
As mentioned above there is a ‘Minecraft mode’ in this program which can be enabled. This essentially links the program with the Minecraft API while the game is running and will draw blocks above the player in the same pattern as the entities on the board – each iteration moving up a layer, building upwards as the simulation goes on.
I thought this would be a cool idea to try and see what patterns are generated in Minecraft – like a sort of virtually organic algorithm that controls the construction.
The X and Y co-ordinates are used from each entity on the board in order to place a block above the player, with the R, G, and B values of the entity contributing towards the random number seed for a random integer between 1 and 22 for the block type. For example, no.1 block type is stone and so forth as described here.
Here are some patterns produced in Minecraft from running the program in Minecraft mode and watching the blocks be placed above the character, in the captions of each screenshot is the configuration used to launch the program – but even using the same configuration will probably never produce the same results again (I am re-using the above configurations for this test).
Experiment 1 configuration
Experiment 2 configuration
Experiment 3 configuration
Experiment 4 configuration
Experiment 5 configuration
Experiment 6 configuration
The most interesting patterns were of course those with the highest number of lifeforms, unfortunately these were also the ones with the most issues – on the last two I kept getting the error:
mcpi.connection.RequestError: player.getPos() failed
I had to re-run the program a number of times on some in order to get a pattern – I surmise that the combination of the program running and Minecraft also running results in a program accessing the Minecraft API – the CPU did seem maxed out.
I think, overall, I met my goals for the project – running through the varied experiments was great fun to see what happened and it’s extremely interesting to see an almost organic pattern to the way some of the entities move and bounce around.
It’s also interested to let the simulation run for hours and see what happens. Sometimes I come back and find they have all expired, sometimes they flourish for ages – many times I’ve also witnessed a new type of life-form appear among them and interact, causing a slow change to the overall pattern of the LED layout as its properties get passed on and mixed in with the others.
The idea of evolution is fascinating and hopefully I’ve managed to recreate a virtual microcosm of it with this project – I hope to one day expand upon.
Overall the idea is to experiment here and see what results are produced with varied arguments input, changing the max lifetime, max aggression etc. and seeing how it affects the journey of the simulation – you can see which lifeforms are more successful by watching the LED’s on the board, or even trace individuals by setting the loop time to a higher number and watching what happens at a slower pace.
Feel free to download the code and run it on a Unicorn HAT or adapt it for use on another form of visual display, also let me know if there are ways I can make the code tidier; I’m sure I’ve done some very messy and odd things in the code that can be improved upon.
Change the settings from the command line when running it, run it on a Minecraft instance and see what patterns can be made within Minecraft; the chances are you will never see the same one twice.
Also please feel free to use the code to make your own project and do something awesome with it and let me know!
The next logical step would be to buy a Unicorn HAT HD and put it on a Pi 3 and adapt the code for use on it, it shouldn’t be too hard – only a few variables need changing within the code in order for it to work with a HD hat.
With a larger range for rendering entities and also having more powerful hardware this should also enable larger and more complex random structures within Minecraft.
I’d also like to fix the issue with entities being able to stack on top of each other, again if any one has any insights on this please let me know!
I learnt a lot from this project, but there is so much more to learn and take away; I plan on using the skills and ideas I’ve learnt here to implement in future projects, such as the Pi Skull when I return to it – and other such experimental AI/AL goodness.