The idea is fairly straightforward: Given an array of cells (2d used here), each cell lives or dies by a set of simple rules that determine whether the cell dies due to over-population, under-population or survives to the next generation (one pass over the algorithm). A new cell can be born too depending on the same rules. This is determined by looking at the 8 neighbours for each cell.
The rules are encoded in the form of Bxyz/Sabc, which indicate under which conditions a cell can be born (if it has x, y or z number of neighbours) or survive (a, b or c number of neighbours). A cell which fails to meet either conditions dies (if it’s living).
The standard rule is B3/S23 which says a cell is born if it has 3 living neighbours and Survives if it has 2 or 3 neighbours. Another popular variation is B36/S23
Here it is in action:
I chose Godot Engine for this primarily because I was experimenting with the engine and it seemed like a fun way to go about learning the ropes.
Since the cells are basically arranged in a grid structure, I wrote two classes cell.gd and grid.gd to represent them. I didn’t want to go overboard and create classes for everything, so the grid class also has all the core logic of the game because ultimately the life simulation is about iterating over cells in the grid and determining which cell in the grid is born, survives or dies. The grid is life!
Anyway, to represent the state of cell for visualization, I also created a cell scene (in Godot parlance that’s the basic game object), which was a sprite with white texture and black border (so that when placed adjacent they become the grid border as can be seen in the gif above):
The cell state can be either DEAD (or not born) in which case the color is white; BORN in which case it would be blue; and SURVIVED in which case it would be black. The color switch is achieved by simply setting the modulate property of the sprite to the desired color.
All cells in the grid are in the default DEAD/not born state when the simulation first starts.
When the simulation runs, it iterates over every cell and checks its 8 neighbours to determine whether it would live or die depending on the rule that’s being followed.
What I did was basically duplicate the grid and have an index flag that would switch between the two every pass. A bit like a rendering back-buffer! Duplicating the grid is a bit wasteful as most of the times the grid is likely to be empty and therefore having two of these lying aboute in memory is sub-optimal. A better solution would be to have a dynamic array of cells that have changed in the current pass and save their old state. Then when updating we see if the cell is present in the Changed array and if so, use the state from there. If it’s not present in the Changed array, we know we can use the current state as-is.
But, given that my array size isn’t all that big, I decided to stick with duplication approach as it seemed fairly straightforward. YMMV.
Anyway, with the above implemented everything else is smooth sailing. Every iteration, for every cell in the grid, check the neighbours. If the cell state changes, change the color of the cell appropriately. At the end of the iteration, switch the active grid to the grid with the new states and use the old grid for writing new states in the next iteration.
It’s possible that after a few iterations, no cell changes state and the simulation becomes stale. To track this I use a variable called
_num_changes to track how many cells have changed in the current iteration. If it’s zero, I know that the simulation can end because there can be no more cell transitions.
That’s it really. It was fun to implement this and even though it’s a simple simulation, it’s possible to get wildly exotic and interesting effects by just changing a simple combination of starting cells and/or rules. The Wikipedia page lists some interesting patterns to try. It’s oddly satisfying to watch a bunch of cells leading to surging, hypnotic patterns across the grid.
Give it a go and tell me you don’t find it fascinating!