x2c

A little life in your terminal.

Conway's Game of Life, drawn in a terminal with the termbox2 package.

Complete program
import "termbox2" with Termbox;
#include "typed-array.x"
#include <stdlib.h>
#include <time.h>

static void _draw(Termbox term, ArrayChar cells, int width, int height) {
  term.clear();
  for (int y = 0; y < height; y++)
    for (int x = 0; x < width; x++) {
      if (!cells[y * width + x]) continue;
      int run = 1;
      while (x + run < width && cells[y * width + x + run]) run++;
      term.fill(x, y, run, 1, %"#", TB_GREEN | TB_BOLD, TB_DEFAULT);
      x += run - 1;
    }
  term.present();
}

static ArrayChar _world(int width, int height) {
  ArrayChar cells = ArrayChar.new();
  for (int i = 0; i < width * height; i++)
    cells.push(rand() > RAND_MAX / 2);
  return cells;
}

static void _advance(ArrayChar cells, ArrayChar next, int width, int height) {
  for (int y = 0; y < height; y++)
    for (int x = 0; x < width; x++) {
      int neighbors = 0;
      for (int dy = -1; dy <= 1; dy++)
        for (int dx = -1; dx <= 1; dx++)
          if (dx || dy)
            neighbors += cells[((y + dy + height) % height) * width +
                               (x + dx + width) % width];
      int i = y * width + x;
      next[i] = neighbors == 3 || (cells[i] && neighbors == 2);
    }
}

int main(void) {
  srand((unsigned int) time(NULL));
  Termbox term = Termbox.open();
  defer term.close();
  term.hide_cursor();

  int width = term.width(), height = term.height();
  ArrayChar cells = _world(width, height), next = _world(width, height);

  while (1) {
    if (width != term.width() || height != term.height()) {
      width = term.width();
      height = term.height();
      cells = _world(width, height);
      next = _world(width, height);
    }
    _draw(term, cells, width, height);
    if (term.peek(80).is_key()) break;
    _advance(cells, next, width, height);
    ArrayChar swap = cells;
    cells = next;
    next = swap;
  }
  return 0;
}

Drawing the world.

A twenty-second recording of green Game of Life cells evolving in a terminal.

The animation records this program. _draw paints each run of adjacent living cells with one fill call, then presents the frame.

The rules of Life.

Two ArrayChar values hold the world: one for the current generation, one for the next. A cell survives with two or three living neighbors. An empty cell becomes alive with exactly three. Everything else dies or stays empty.

Each update reads only from cells and writes only to next, so every cell sees the same generation. The main loop swaps the two arrays after an update, reusing their storage for the following generation.

The modulo expressions wrap both coordinates. A cell on the left edge has neighbors on the right, and the top row touches the bottom. There are no special edge rules.

_world starts roughly half the cells alive. Change that comparison to try a sparser starting population.

Keep the world running.

Opening the terminal and registering its cleanup sit together: defer term.close() restores it when the function exits. Hiding the cursor keeps it out of the animation.

The world takes its dimensions from the terminal. Resizing replaces both arrays with fresh random cells at the new size.

Each frame is drawn before peek(80) waits up to 80 milliseconds for input. A keypress leaves the loop. Otherwise, the program calculates the next generation and swaps the arrays. Lower the timeout for a faster simulation, or raise it to watch patterns evolve more slowly.

Full example / Package guide

Bring a real terminal.

The termbox2 package needs its own dependency preparation, and its release bundles are built on macOS and Linux, on both arm64 and x86_64. ./configure --packages termbox2 reports missing prerequisites; it does not install them. The build then prepares the pinned dependency as needed. Interactive play does not require Expect.

The package guide covers drawing, input, and the terminal’s lifetime.

Your turn

Run it locally.

Build x2c, prepare termbox2, and run the simulation in your terminal. Any key exits.

You need a GCC- or Clang-compatible C compiler, ar, GNU Make, Python 3, and Bash. The build guide covers setup in detail.

From a new checkout
git clone https://github.com/gwf/x2c.git
cd x2c
git checkout --detach 53c1ed815d792b4a1af2b182a63e32feb1a5e007
make build-safe

./configure --packages termbox2
make -C packages/termbox2 short-example
./packages/termbox2/builds/game-of-life