A few weeks ago I stumbled across a tiny development board called the LilyGo T-Display-S3 which I have had for a very long time. Its an ESP32-based microcontroller with a built-in 1.9 inch colour LCD, two buttons, and a price tag of around £10-20. I wanted to do something interesting with it, so I decided to run one of the most famous simulations in computing history on it: Conway's Game of Life.
What started as a simple weekend project turned into something much more ambitious and turned into a self-evolving simulation with a genetic algorithm that learns over time, and a two-team battle mode where colonies fight each other for territory. Here's the story of how it came together.


What is Conway's Game of Life?
Before diving into the build, it's worth explaining what Conway's Game of Life actually is... Because despite the name, it isn't really a game in the traditional sense. It was invented by British mathematician John Conway in 1970 and is what's known as a cellular automaton: a simulation that runs on a grid of cells, where each cell is either alive or dead, and the fate of every cell in the next generation is decided by a simple set of rules based on its neighbours.
Those rules are:
- A live cell with 2 or 3 live neighbours survives
- A live cell with fewer than 2 or more than 3 live neighbours dies
- A dead cell with exactly 3 live neighbours comes alive
That's it. Three rules. And yet from these three rules, extraordinarily complex and beautiful patterns emerge, making creatures such as gliders that travel across the grid, oscillators that pulse back and forth, and stable structures that sit perfectly still forever. It's one of the most compelling demonstrations in all of science that complexity doesn't require complex rules.

The Hardware
The LilyGo T-Display-S3 is a remarkably capable little board for its price. It runs on an ESP32-S3 chip which has a dual-core 240MHz processor with 8MB of flash and 8MB of PSRAM as well as the which contains 170×320 pixels; ST7789 LCD driven over a parallel 8-bit bus, which makes it fast enough to update the whole screen many times per second.
The board has exactly two buttons: a BOOT button on GPIO 0 and a side button on GPIO 14. The challenge was building an entire UI and control scheme around just those two inputs, which turned out to be more interesting than I expected.
The Build Process
Getting the display working was the first hurdle. The TFT_eSPI library handles the display driver, but it requires manual configuration to tell it which pins are connected to which signals, and the T-Display-S3 uses a parallel 8-bit interface rather than the more common SPI bus, so getting the configuration file pointing at the right setup was important. Once that clicked into place, the display came to life immediately.
The Game of Life simulation itself is straightforward to implement, for each generation, you count the live neighbours of every cell and apply the three rules. The main performance consideration on a small microcontroller is avoiding redraws of cells that haven't changed, so the code only updates pixels that are different from the previous generation. This keeps the frame rate smooth even at the 85×155 cell grid size the display supports at 2 pixels per cell.
The two-button control scheme required some creative thinking. A single short press of each button does something different, holding both buttons simultaneously for a second triggers pause, and a press on the mode select screen acts as a toggle versus a confirm. Getting all of that to feel natural without any accidental triggers took a fair bit of button debounce tuning.
Adding a Genetic Algorithm
Once the basic simulation was running, I wanted to make it more interesting, so I added a genetic algorithm that learns over time which starting configurations produce the longest-lived, most expansive colonies.
A genetic algorithm is a technique inspired by biological evolution. Rather than trying to find a good solution by hand, you start with a population of random candidates, measure how well each one performs, keep the best ones, breed them together to create new candidates, and repeat. Over many generations the population converges on increasingly good solutions, much like natural selection.
In this project, each candidate (called a chromosome) encodes four parameters that describe how the starting grid is seeded:
- Seed radius: how large the initial cluster of cells is
- Density: how many cells within that cluster start alive
- Noise: how many scattered cells appear outside the main cluster
- Shape: a blend between a circular and square starting shape
The simulation runs a pool of 8 chromosomes one at a time. Each one gets scored on how long it survives and how far the colony spreads across the screen. After all 8 have run, the top 4 are kept and bred together, their genes are mixed and slightly mutated then to produce 4 new candidates. This cycle repeats indefinitely.
The results are genuinely surprising. Early on the colonies tend to die out quickly or settle into small stable patterns. But after a few dozen rounds you start to see seeds that grow aggressively outward from the centre, filling most of the screen before finally stabilising. The algorithm finds solutions you wouldn't think to design by hand.
Battle Mode
The most fun addition was battle mode. Instead of one colony evolving on its own, two teams start at opposite ends of the display and expand toward each other. Team A (green by default) seeds in the top quarter of the screen; Team B (red by default) seeds in the bottom quarter. The middle becomes a contested no-man's-land.
To make the teams actually interact rather than just running two independent simulations side by side, the birth rule is modified: when a dead cell has exactly 3 live neighbours, it is born as whichever team has the majority of those neighbours. If it's 2 green and 1 red, a green cell is born. If it's 2 red and 1 green, a red cell is born. A tie produces no birth. This creates genuine territorial combat; advancing fronts, contested zones, and colonies that can be pushed back and overwhelmed.
Each team has its own independent genetic algorithm pool, so they evolve separately in response to each other. In theory this should produce an arms race over time, with each team developing strategies that work against whatever the other team is currently doing.

The Result
The finished project runs entirely on the tiny board with no external components beyond a USB power supply. The full UI includes a welcome screen, a mode selection screen, a live HUD showing generation count and elapsed time, end-of-round splash screens with stats and a countdown timer, pause functionality, colour cycling, and a few surprises I'll leave for you to discover.
All the code is open source and available on GitHub, if you have a LilyGo T-Display-S3 the setup guide in the README should get you up and running in under 30 minutes.
GitHub: github.com/Noppitlabs/conways-game-of-life-t-display-s3
Where It Lives Now
Mine currently has a permanent home on the pegboard next to my desk, so I can keep an eye on it throughout the day. There's something oddly satisfying about glancing over and watching the colonies evolve, seeing the genetic algorithm slowly figure out better and better starting seeds, and occasionally catching a particularly dramatic battle mode round where one team makes a comeback from the brink of extinction. It's become a little piece of living art on my wall, and it's quietly getting smarter every hour it runs.
If you build one, I'd love to hear how it goes!