Game of Life, 3D.
Context
Conway's Game of Life is the cellular-automaton entry point — a 2D grid of cells that live, die or are born on each tick depending on the count of their eight neighbours. Lift the grid into a third dimension and the neighbourhood balloons from eight cells to twenty-six. The rule thresholds that hold the population stable have to be re-derived; the patterns that emerge are stranger, more brittle, more alien. This sketch is a Processing-based playground for watching that 3D variant evolve.
How it works
▸ The cell space
A ConwaysCube backs the simulation — an indexed lattice that tracks which integer coordinates are alive and stretches its extent as the population spreads. Each iterate() step recomputes the next generation by counting the 26 neighbours of every live cell (and of every dead cell adjacent to a live one), then applying the 3D birth/survive rule. The pattern is seeded from a small 2D-slice descriptor at startup, which is convenient for replaying interesting initial states.
▸ The rule, in code
The 2D ruleset (born on 3, survive on 2–3) doesn't survive contact with 26 neighbours — the population always blows up or starves. The thresholds in this iteration were picked by hand to keep things interesting: a live cell survives only with 6–12 live neighbours, and a dead cell is born when surrounded by 11–15 live ones. The bounding box of the search grows by two cells per iteration so a runaway colony has room to expand into.
public void iterate() {
ConwaysCube buffer = clone();
int newMinExtent = getMinExtent() - 2;
int newPosExtent = getPosExtent() + 2;
range(newMinExtent, newPosExtent).forEach(x ->
range(newMinExtent, newPosExtent).forEach(y ->
range(newMinExtent, newPosExtent).forEach(z -> {
int alive = buffer.countAliveNeighbours(x, y, z);
if (buffer.isAlive(x, y, z) && (5 >= alive || alive >= 13)) {
put(x, y, z, '.'); // dies
} else if (!buffer.isAlive(x, y, z)
&& 11 <= alive && alive <= 15) {
put(x, y, z, '#'); // born
}
})));
}
public int countAliveNeighbours(int x, int y, int z) {
int alive = 0;
for (int i = -1; i <= 1; i++) {
for (int j = -1; j <= 1; j++) {
for (int k = -1; k <= 1; k++) {
if (i == 0 && j == 0 && k == 0) continue; // skip self
if (isAlive(x + i, y + j, z + k)) alive++;
}
}
}
return alive;
}▸ Rendering the living set
The renderer walks the cube's coordinates and emits one box(10) per live cell, translated to its world-space position. RGB is tied to the absolute value of each axis (|x|, |y|, |z|), so the further a cell sits from the origin the deeper its tint — a quiet way of giving the cluster three-dimensional legibility without lighting tricks.
private void drawCubes(int extent) {
cube.getCoords().stream().forEach(t -> {
if (cube.isAlive(t._1, t._2, t._3)) {
pushMatrix();
translate(t._1 * scaleC, t._2 * scaleC, t._3 * scaleC);
stroke(abs(t._1) * 4 * colourScale / 5,
abs(t._2) * 4 * colourScale / 5,
abs(t._3) * 4 * colourScale / 5);
fill(abs(t._1) * colourScale,
abs(t._2) * colourScale,
abs(t._3) * colourScale);
box(10);
popMatrix();
}
});
}▸ The orbiting camera
Every frame the camera advances by 0.2° around the origin, with the orbit radius and elevation tied to the mouse — mouseX pushes the camera back, mouseY tilts it above or below the equator. The radius also scales with the live cube's extent, so a colony that explodes outward gets framed automatically instead of running off the canvas.
▸ The step machine
The renderer redraws constantly, but the simulation only ticks once per second — enough time to read the population, parse the shape, anticipate what's about to die. A simple last-tick timestamp gates the call to cube.iterate(). A HUD in the top-left prints the iteration number, the count of alive cells, and a short hash of the current state — useful when hunting for periodic configurations.
Controls
space pause / resume the simulation
s save the current frame as GOL###.png
mouse-X push the camera in / out
mouse-Y raise / lower the cameraStatus
Under construction — the source still needs a longer write-up on the rule selection, the hashing scheme used for cycle detection, and a clip of a few of the more arresting initial conditions running to fixed-point. More to come.