// Wolfram Elementary 1D Cellular Automaton
//
// Each cell's next state depends on its own state and its two neighbors
// (3 cells = 8 possible combinations = 256 possible rules, numbered 0-255).
// The canvas shows a spacetime diagram: each row is one generation.
// When the canvas fills, the content scrolls up and new rows appear at the bottom.
//
// Famous rules: 30 (chaotic), 90 (Sierpinski triangle), 110 (Turing-complete)
export const WARMUP = { framesBeforeReady: 0 };
export const PARAMS = {
rule: { value: 30, min: 0, max: 255, step: 1, label: "Wolfram Rule (0-255)", folder: "Structure", rebuildOnChange: true },
cellSize: { value: 3, options: ["1", "2", "3", "4", "6"], label: "Cell Size (px)", folder: "Structure", rebuildOnChange: true },
init: { value: "Single", options: ["Single", "Random", "Gradient"], label: "Initial Row", folder: "Structure", rebuildOnChange: true },
living: { value: "#e8d8a8", type: "color", label: "Living", folder: "Appearance" },
bg: { value: "#0a0a0a", type: "color", label: "Background", folder: "Appearance" },
};
let state;
function hexToRgb(hex) {
const v = parseInt(hex.replace('#', ''), 16);
return [(v >> 16) & 255, (v >> 8) & 255, v & 255];
}
function makeInitRow(cols, mode) {
const row = new Uint8Array(cols);
if (mode === "Single") {
row[Math.floor(cols / 2)] = 1;
} else if (mode === "Random") {
for (let i = 0; i < cols; i++) row[i] = Math.random() < 0.5 ? 1 : 0;
} else {
// Gradient: density increases from edges to center
const mid = cols / 2;
for (let i = 0; i < cols; i++) {
const dist = Math.abs(i - mid) / mid; // 0 at center, 1 at edge
const density = 1 - dist;
row[i] = Math.random() < density ? 1 : 0;
}
}
return row;
}
export function sketchSetup(p, w, h, params) {
const cs = parseInt(params.cellSize);
const rule = Math.floor(params.rule);
const cols = Math.floor(w / cs);
const rowsOnScreen = Math.floor(h / cs);
p.pixelDensity(1);
p.background(10);
const current = makeInitRow(cols, params.init);
const next = new Uint8Array(cols);
// Track which canvas row we're drawing into (in cell units)
state = { cs, cols, rowsOnScreen, current, next, rule, drawRow: 0, full: false };
return state;
}
function stepRow(current, next, cols, rule) {
for (let col = 0; col < cols; col++) {
const l = current[(col - 1 + cols) % cols];
const c = current[col];
const r = current[(col + 1) % cols];
const idx = (l << 2) | (c << 1) | r;
next[col] = (rule >> idx) & 1;
}
current.set(next);
}
export function sketchDraw(p, w, h, params) {
const { cs, cols, rowsOnScreen, current, next } = state;
const rule = Math.floor(params.rule);
// Compute one generation per frame
stepRow(current, next, cols, rule);
const [lr, lg, lb] = hexToRgb(params.living);
const [br, bg2, bb] = hexToRgb(params.bg);
if (state.drawRow >= rowsOnScreen) {
// Scroll the canvas up by one cell row
p.copy(p, 0, cs, w, h - cs, 0, 0, w, h - cs);
state.drawRow = rowsOnScreen - 1;
}
// Draw the new row at the bottom of the current content
p.loadPixels();
const pd = p.pixels;
const rowY = state.drawRow * cs;
for (let col = 0; col < cols; col++) {
const alive = current[col];
const r = alive ? lr : br;
const g = alive ? lg : bg2;
const b = alive ? lb : bb;
for (let py = 0; py < cs; py++) {
for (let px = 0; px < cs; px++) {
const pi = ((rowY + py) * w + (col * cs + px)) * 4;
pd[pi] = r;
pd[pi + 1] = g;
pd[pi + 2] = b;
pd[pi + 3] = 255;
}
}
}
p.updatePixels();
state.drawRow++;
}