Making Time for a Little Life on the ZX81
Cellular automaton simulation of Conway’s Game of Life.
I’d read about and studied cellular automata but had never programmed any simulations for them. Conway’s Game of Life is the go-to simulation and something others had already implemented on the ZX81, even when I was a kid. I finally got around to typing in some of those programs but found them slow. My version aims to correct that.
# Getting started.
The first simulation I tried was actually a more generic version from “Creating Simulation Games on Your Computer,” 1986 by Tim Hartnell. It was designed to run on multiple computers and didn’t require much work to get running on the ZX81. However, it was incredibly slow. Written in BASIC and looping through all the cells, it took quite some time to execute.
Tim’s version cheated a bit by mirroring the image, keeping the array small, but it was still slow. An obvious speed-up on the ZX81 was to use FAST
mode. This helped, but I felt I could do better. So, I wrote my own version in assembly.
# Making a faster simulation.
Writing a machine code version of Life was straightforward. I initialize the screen and then loop through each generation. The core code looks like this:
main_loop:
call init_life ; Initialize Game of Life
; Run simulation
life_loop:
call live_or_die ; Run life simulation
call copy_to_screen ; Copy new life to screen
call delay_and_test ; Test for break key or gen > 100
; Was a key pressed or generations hit 100?
or a
jp z,life_loop ; No, display next generation
jp main_loop ; Otherwise, start again!
I had to write a small random routine to create the initial culture, ensuring each run differs from the last, making the program more like a screensaver. A counter resets each run, limiting it to 100 generations. This ensures a good simulation cycle without getting stuck in a loop. You can also press a key to exit early.
Life, ZX81 Busy Screenshot, 2024 by Steven Reid
# Making life run.
The live-or-die routine is the heart of the program, handling all the heavy lifting. To keep things simple, I use the primary screen for the current generation and a buffer for the next. The program loops through the screen, testing each location—sort of.
You may have noticed an unused line around the screen. That’s intentional. I need to count all the live cells surrounding the current cell, and leaving those edge cells blank simplifies the code, keeping it fast.
The counting routine actually shifts around the screen address, pushes a copy to the stack, and then moves to check all adjacent cells. I reuse a simple test routine that increments a counter if the cell isn’t zero.
count_life:
ld a,(hl) ; Get cell
or a ; Is it alive?
ret z ; No, return
inc c ; Yes, increment count
ret
Once that’s done, the actual simulation begins. The rules of Life are simple: If the current cell is occupied and has exactly 2 or 3 live neighbors, it survives. Otherwise, it dies. Here’s the code:
; Check if cell dies (c <> 2 and c <> 3)
cp 2 ; Check if 2
jr z,lod_alive ; If 2, stays alive!
cp 3 ; Check if 3
jr z,lod_alive ; If 3, stays alive!
xor a ; Otherwise, cell dies
jr lod_skip ; Done
But if the cell is empty and exactly 3 neighbors are alive, a new cell is born. This code is even simpler:
lod_empty:
; Check if lives
cp 3 ; Check if 3
ld a,0 ; Assume empty cell
jr nz,lod_skip ; Not 3, skip ahead
lod_alive:
ld a,$80 ; Yes, cell is born
After that, the new cell is stored in the buffer. This repeats for all rows and columns. The game then copies the buffer to the screen. All of this happens quickly, producing a smooth animation.
# Enjoying simulations.
After finishing my program, I found other versions of Life in ZX81 books I owned. They were also in BASIC and used relatively small grids. I like where I landed in terms of speed and presentation.
That said, there’s always room for improvement. An obvious enhancement would be using different characters or graphics—perhaps even randomizing them—for added visual appeal. I could also make the program more interactive by allowing variable generation limits.
If I were really ambitious, I could create a “paint your own simulation” feature, letting users experiment with different configurations. I could even include stamps for common patterns like spinners. But those are enhancements few would take advantage of.
The only real change I need to make is adding a splash screen requiring a button press to start. One issue with emulators is that they don’t vary the machine state, which affects the randomness of the number generator—a problem that doesn’t occur on a real ZX81.
As always, comments and ideas are welcome!