Cellular automata

Rule 30

One row of cells, each either on or off. One rule that looks at three neighbours and decides the cell below. Repeat. The left side settles into stripes, the right side never settles into anything.

EXP-005

Move the cursor over the canvas — it is part of the simulation.

Drag your finger across the canvas — it is part of the simulation.

What to look for

The asymmetry. Same rule applied to both sides of a symmetric starting condition, and one side produces order while the other produces something that passes every statistical test for randomness.

The maths

c′ = l XOR (c OR r)

The whole rule. l, c and r are the three cells above. That single boolean expression is the entire program.

00011110₂ = 30

The name: write the outputs for the eight possible neighbourhoods in order and read them as a binary number.

How it is built

A Uint8Array one cell per 3px of width, starting with a single cell set in the middle. One row is drawn per frame and the array is replaced by its successor. When the bottom is reached it clears and starts over from the same single cell, and it draws exactly the same thing again.

The code, step by step

Cut straight from lab.js. These are the real lines that run above, not a simplified version.

01

One row, one cell on

const seed = c => { row = new Uint8Array(c); row[c >> 1] = 1; y = 0; };

The entire initial condition. A byte array with a single 1 in the middle: everything you see grows from that one cell.

02

Apply the rule

const R = P.rule | 0, next = new Uint8Array(cols);
for (let i = 0; i < cols; i++) {
  const l = row[(i - 1 + cols) % cols], c = row[i], r = row[(i + 1) % cols];
  const idx = (l << 2) | (c << 1) | r;       // vecindario como número 0..7
  next[i] = (R >> idx) & 1;                  // el bit idx de la regla
}
row = next; y++;

The three neighbours are packed into a number 0-7, and the answer is that bit of the rule number. Rule 30 is 00011110 in binary. Move the slider and you can run all 256 elementary automata.

03

Never clear the canvas

persist: true,

This flag tells the engine to skip the clear. Each frame paints exactly one new row on top of everything already drawn, which is how the triangle accumulates.

Why it matters

Wolfram used the centre column of this automaton as the random number generator in Mathematica for years. It is one line of boolean logic producing a sequence nobody has been able to predict or compress.

The whole thing

Everything above, in one piece. This is the entire experiment as it lives in lab.js.

FULL

lab.js · rule30

{ id: 'rule30',
      name: L('Rule 30', 'Regla 30'),
      note: L('One line, one rule, applied forever. The result looks random.',
              'Una línea, una regla, aplicada infinitas veces. El resultado parece azar.'),
      params: [
        { key: 'rule', label: L('Rule number', 'Número de regla'), min: 0, max: 255, step: 1, def: 30 },
        { key: 'cell', label: L('Cell size', 'Tamaño de celda'),   min: 2, max: 8,   step: 1, def: 3, unit: 'px' }
      ],
      make(w) {
        let cols = 0, row = null, y = 0, lastCell = 0;
        const seed = c => { row = new Uint8Array(c); row[c >> 1] = 1; y = 0; };
        return {
          persist: true,
          resize() { cols = 0; },
          reset() { cols = 0; },
          step(ctx, w, h, t, acc, P, M) {
            const CELL = P.cell | 0;
            if (!cols || CELL !== lastCell) {
              lastCell = CELL; cols = Math.max(8, Math.floor(w / CELL));
              seed(cols); ctx.clearRect(0, 0, w, h);
            }
            if (y * CELL > h) { seed(cols); ctx.clearRect(0, 0, w, h); }
            ctx.fillStyle = `rgba(${acc},0.8)`;
            for (let i = 0; i < cols; i++) if (row[i]) ctx.fillRect(i * CELL, y * CELL, CELL - 0.6, CELL - 0.6);
            const R = P.rule | 0, next = new Uint8Array(cols);
            for (let i = 0; i < cols; i++) {
              const l = row[(i - 1 + cols) % cols], c = row[i], r = row[(i + 1) % cols];
              const idx = (l << 2) | (c << 1) | r;       // vecindario como número 0..7
              next[i] = (R >> idx) & 1;                  // el bit idx de la regla
            }
            row = next; y++;
          }
        };
      }
    },