Feature flags are decision points in an application that change it's behavior based on context and a set of rules. This is a fun little project that implements Conway's Game of Life using feature flag rules, a raspberry pi, and a neopixel matrix.
Surprisingly complex and fascinating behavior emerges from having the cells follow just 3 simple rules
![](https://blog.tarq.io/content/images/2022/10/dji_mimo_20221001_145410_279_1664651025270_video-Animated-Image--Large-.gif)
The hardware
This being a quick weekend project, we'll start by hastily wiring a raspberry pi to a level shifter and finally to our LED matrix.
![](https://blog.tarq.io/content/images/2022/10/IMG_5423.jpg)
![](https://blog.tarq.io/content/images/2022/10/IMG_5424.jpg)
The flags
Our first step is to create a couple feature flags for controlling the grid. Our application will run in a loop, evaluating feature flags for each cell to determine it's next state. We will pass a couple of custom attributes to power the targeting rules:
- column and row: indicating the x and y coordinates of the cell on the grid
- alive: boolean indicating whether or not the cell is "alive"
- neighbors: the number of neighboring cells that are alive
- iteration: total number of iterations (loops) have run so far
- since: the iteration when the cells state last changed
- age: how long the cell has been in this state (iteration - since)
Config: Cell Alive
Determine if the cell is alive
![](https://blog.tarq.io/content/images/2022/10/image.png)
![](https://blog.tarq.io/content/images/2022/10/IMG_5426.jpg)
Config: Cell Color
Determine the color of the LED representing the cell
![](https://blog.tarq.io/content/images/2022/10/image-1.png)
Config: Steps Per Second
Determine how many iterations should be run per second
![](https://blog.tarq.io/content/images/2022/10/image-2.png)
Rules to Live By
We now have an excellent base to implement our automaton. During each loop, we will evaluate the config-cell-alive
flag for every cell to determine its next state:
const nextState = await variation('config-cell-alive', this.cellUser({
column, row,
alive,
since,
neighbors: liveNeighbors,
age: Math.min(0, this.iteration - since),
}), defaultState)
return {
alive: nextState,
since: nextState == alive ? since : this.iteration
}
In Conway's Game of Life, cells follow a set of simple rules:
- Any live cell with fewer than two live neighbours dies, as if by underpopulation.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overpopulation.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
These rules, which compare the behavior of the automaton to real life, can be condensed into the following:
- Any live cell with two or three live neighbours survives.
- Any dead cell with three live neighbours becomes a live cell.
- All other live cells die in the next generation. Similarly, all other dead cells stay dead.
source: Wikipedia
We can implement the simplified rules using the targeting rules in Config: Cell Alive:
![](https://blog.tarq.io/content/images/2022/10/image-4.png)
Since we start with an empty world, let's add one more rule that randomly spawns live cells on the first iteration:
![](https://blog.tarq.io/content/images/2022/10/image-5.png)
![](https://blog.tarq.io/content/images/2022/10/IMG_5427-Animated-Image--Large-.gif)
Neopixels are represented by a flat array with their indices determined by how they are wired together. In my smaller 8x8 matrix, converting x and y coordinates to a pixel index is very simple:
cellCoordinatesToIndex(x, y) {
return (y * this.width) + x
}
With my larger 16x16 matrix, it turned out that every other row gets flipped! I visualized this by setting all cells in the first column (`x = 0`) to green:
![](https://blog.tarq.io/content/images/2022/10/image-6.png)
![](https://blog.tarq.io/content/images/2022/10/IMG_5430.jpg)
In order to support many types of panels without changing the code-base, I opted to create a feature flag to enable remapping the column of even rows in the renderPixels
function:
async function renderPixels() {
for(let i = 0; i < game.state.length; i++) {
const cell = game.state[i];
const [x,y] = game.cellIndexToCoordinates(i)
const color = await variation(`config-cell-color`,
game.cellUser({
row: y,
column: x,
age: game.iteration - cell.since,
...cell
}), cell.alive ? 0x00ff00 : 0x000000);
// the feature flag returns a hex string
const colorNumber = typeof color == 'string' ? parseInt(color, 16) : color
// Seperentine matrixes require remapping on even rows
if (await variation('enable-serpentine-matrix-remap', createContext(), false)
&& y % 2 == 0) {
const remapX = (game.width - x - 1) % game.width
pixels[game.cellCoordinatesToIndex(remapX, y)] = colorNumber
} else {
pixels[i] = colorNumber
}
}
neopixel.render()
}
Once we toggle the flag on, everything works as expected:
![](https://blog.tarq.io/content/images/2022/10/IMG_5431.jpg)
Other tricks
![](https://blog.tarq.io/content/images/2022/10/dji_mimo_20221001_143440_274_1664649321433_video-Animated-Image--Large-.gif)
Using segments, we can easily save common patterns such as:
- Still lifes: patterns that live forever
- Oscillators: a cycle of patterns that return to their original state after a finite number of generations
- Spaceships: patterns that travel across the grid
- Generators: patterns that spawn other patterns/cells
Finishing up
![](https://blog.tarq.io/content/images/2022/10/dji_mimo_20221001_150106_281_1664651011548_video-Animated-Image--Large--1.gif)
To liven up the visuals a bit, I am going to add some additional rules to the cell color flag:
![](https://blog.tarq.io/content/images/2022/10/image-8.png)
And finally, make the game automatically reset to iteration 0 when no cells are alive on the grid:
async function runGame() {
while(running) {
game.step()
await renderPixels()
const population = game.state.filter(x => x.alive).length
if(population == 0) game.reset()
await sleep(1000 / await conf('steps-per-second', 1))
}
console.log('done running')
}
Now we have a nice loop that will keep running without human intervention.
Future ideas
I plan to combine four 16x16 grids for a total of 1024 cells and a pretty frame to hang on my wall as a digital art piece. Eventually, I'd like to create a Web UI that allows users to see the grid and paint cells to life by clicking on them.