02Strip Automaton Whose Next Row Pattern Follows a Single Rule-Number Value
A single row of cells is compared, three neighbors at a time, against a binary rule number to decide the next row's state, and each generation is stacked one row lower so triangular stripe patterns accumulate.
Best forBackground texture generation, illustrative diagrams for algorithm explainers
RULEcopyWithinfillRect

<main class="stage">
<div class="rv is-demo" tabindex="0" role="button" aria-label="Rule strip running -- click or press space to pause, R to reseed">
<canvas class="rv__cv" width="480" height="300"></canvas>
<p class="rv__hint">Click or press space to pause, R to reseed</p>
</div>
</main>@use "../../../../site/static/demo-tokens" as *;
@include stage($stage-yellow);
.rv {
position: relative;
width: min(92vw, calc(160vh - 12.8vw));
aspect-ratio: 480 / 300;
margin: 0 auto;
border-radius: $r-card;
overflow: hidden;
cursor: pointer;
box-shadow: $shadow-raised;
outline-offset: 4px;
&:focus-visible {
outline: 3px solid $subject-ink;
}
}
.rv__cv {
display: block;
width: 100%;
height: 100%;
}
.rv__hint {
position: absolute;
left: $sp-3;
bottom: $sp-3;
margin: 0;
padding: $sp-1 $sp-3;
border-radius: $r-pill;
font-size: 12px;
line-height: 1.4;
font-weight: 500;
background: rgba(255, 247, 230, .85);
color: $subject-ink;
pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
.rv {
cursor: pointer;
}
}html, body {
margin: 0;
overflow: hidden;
}
.stage {
box-sizing: border-box;
min-height: 100vh;
display: grid;
place-items: center;
margin: 0;
background: #ffd23f;
font-family: -apple-system, "Segoe UI", "Noto Sans KR", sans-serif;
padding: clamp(8px, 4vw, 24px);
}
.rv {
position: relative;
width: min(92vw, 160vh - 12.8vw);
aspect-ratio: 480/300;
margin: 0 auto;
border-radius: 16px;
overflow: hidden;
cursor: pointer;
box-shadow: 0 14px 24px rgba(23, 20, 26, 0.32);
outline-offset: 4px;
}
.rv:focus-visible {
outline: 3px solid #17141a;
}
.rv__cv {
display: block;
width: 100%;
height: 100%;
}
.rv__hint {
position: absolute;
left: 12px;
bottom: 12px;
margin: 0;
padding: 4px 12px;
border-radius: 999px;
font-size: 12px;
line-height: 1.4;
font-weight: 500;
background: rgba(255, 247, 230, 0.85);
color: #17141a;
pointer-events: none;
}
@media (prefers-reduced-motion: reduce) {
.rv {
cursor: pointer;
}
}(function(){
'use strict';
// 395-09(진행률 채움 로더)와 다른 축: 규칙 번호 값이 다음 줄 무늬를 직접 결정하는 계산이 주인공.
// 매 세대 한 줄을 규칙표와 대조해 계산하고 화면 전체를 아래로 밀어 fillRect 로 다시 그린다.
function makeLcg(seed){
let s = seed >>> 0;
return function(){
s = (s * 1664525 + 1013904223) >>> 0;
return s / 4294967296;
};
}
const W = 480, H = 300;
const CELL = 5, COLS = W / CELL, ROWS = H / CELL;
const YELLOW = '#d2c7a4';
const ALIVE_COLOR = '#93a4ca';
let SEED = 30071;
let rnd = makeLcg(SEED);
const RULE = 30; // 울프럼 초등 오토마타 규칙 번호(이진 8비트) — 왼쪽·자신·오른쪽 조합마다 생사 결정
const ruleBits = new Uint8Array(8);
for (let p = 0; p < 8; p++) ruleBits[p] = (RULE >> p) & 1;
let grid = new Uint8Array(COLS * ROWS);
function idx(x, y){ return y * COLS + x; }
function clearGrid(){
grid.fill(0);
}
function seedRow(seedX){
grid[idx(seedX, ROWS - 1)] = 1;
}
function step(){
grid.copyWithin(0, COLS, ROWS * COLS);
const base = (ROWS - 2) * COLS;
const outBase = (ROWS - 1) * COLS;
for (let x = 0; x < COLS; x++){
const l = grid[base + ((x - 1 + COLS) % COLS)];
const m = grid[base + x];
const r = grid[base + ((x + 1) % COLS)];
const pattern = l * 4 + m * 2 + r;
grid[outBase + x] = ruleBits[pattern];
}
}
const cvEl = document.querySelector('.rv__cv');
const ctx = cvEl.getContext('2d');
const rootEl = document.querySelector('.rv');
function render(){
ctx.fillStyle = YELLOW;
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = ALIVE_COLOR;
for (let y = 0; y < ROWS; y++){
for (let x = 0; x < COLS; x++){
if (grid[idx(x, y)] === 1){
ctx.fillRect(x * CELL, y * CELL, CELL - 1, CELL - 1);
}
}
}
}
const ARIA_PLAY = 'Rule strip running -- click or press space to pause, R to reseed';
const ARIA_PAUSE = 'Rule strip paused -- click or press space to resume, R to reseed';
function updateAria(){
rootEl.setAttribute('aria-label', paused ? ARIA_PAUSE : ARIA_PLAY);
}
let paused = false;
const reduceMQ = window.matchMedia('(prefers-reduced-motion: reduce)');
function human(){ rootEl.classList.remove('is-demo'); }
function reseed(){
SEED = (SEED * 1103515245 + 12345) >>> 0;
rnd = makeLcg(SEED || 1);
clearGrid();
const seedX = Math.floor(rnd() * COLS);
seedRow(seedX);
}
function togglePause(){
paused = !paused;
updateAria();
}
function onActivate(e){
if (e.type === 'keydown'){
if (e.key === 'r' || e.key === 'R'){
human();
reseed();
render();
return;
}
if (e.key !== 'Enter' && e.key !== ' ') return;
e.preventDefault();
}
human();
if (reduceMQ.matches){
step();
render();
} else {
togglePause();
}
}
function onKey(e){
if (e.key === 'r' || e.key === 'R' || e.key === 'Enter' || e.key === ' '){
onActivate(e);
}
}
const GEN_INTERVAL_MS = 32;
let acc = 0, last = 0;
function frame(now){
if (!reduceMQ.matches){
if (!last) last = now;
const dt = now - last;
last = now;
if (!paused){
acc += dt;
while (acc >= GEN_INTERVAL_MS){
step();
acc -= GEN_INTERVAL_MS;
}
render();
}
requestAnimationFrame(frame);
}
}
function onResize(){ render(); }
rootEl.addEventListener('click', onActivate);
rootEl.addEventListener('keydown', onKey);
window.addEventListener('resize', onResize);
clearGrid();
seedRow(Math.floor(COLS / 2));
updateAria();
render();
if (!reduceMQ.matches){
requestAnimationFrame(frame);
}
})();import { useEffect, useRef } from 'react';
import styles from './ElementaryRuleStrip.module.scss';
function ctx2d(cv: HTMLCanvasElement): CanvasRenderingContext2D {
const c = cv.getContext('2d');
if (!c) throw new Error('no 2d context');
return c;
}
function makeLcg(seed: number): () => number {
let s = seed >>> 0;
return function(){
s = (s * 1664525 + 1013904223) >>> 0;
return s / 4294967296;
};
}
const W = 480, H = 300;
const CELL = 5, COLS = W / CELL, ROWS = H / CELL;
const YELLOW = '#d2c7a4';
const ALIVE_COLOR = '#93a4ca';
let SEED = 30071;
let rnd = makeLcg(SEED);
const RULE = 30; // 울프럼 초등 오토마타 규칙 번호(이진 8비트) — 왼쪽·자신·오른쪽 조합마다 생사 결정
const ruleBits = new Uint8Array(8);
for (let p = 0; p < 8; p++) ruleBits[p] = (RULE >> p) & 1;
const grid = new Uint8Array(COLS * ROWS);
function idx(x: number, y: number): number { return y * COLS + x; }
function clearGrid(): void {
grid.fill(0);
}
function seedRow(seedX: number): void {
grid[idx(seedX, ROWS - 1)] = 1;
}
function step(): void {
grid.copyWithin(0, COLS, ROWS * COLS);
const base = (ROWS - 2) * COLS;
const outBase = (ROWS - 1) * COLS;
for (let x = 0; x < COLS; x++){
const l = grid[base + ((x - 1 + COLS) % COLS)];
const m = grid[base + x];
const r = grid[base + ((x + 1) % COLS)];
const pattern = l * 4 + m * 2 + r;
grid[outBase + x] = ruleBits[pattern];
}
}
function render(ctx: CanvasRenderingContext2D): void {
ctx.fillStyle = YELLOW;
ctx.fillRect(0, 0, W, H);
ctx.fillStyle = ALIVE_COLOR;
for (let y = 0; y < ROWS; y++){
for (let x = 0; x < COLS; x++){
if (grid[idx(x, y)] === 1){
ctx.fillRect(x * CELL, y * CELL, CELL - 1, CELL - 1);
}
}
}
}
const ARIA_PLAY = '규칙 스트립 진행 중 — 클릭하거나 스페이스바로 정지, R 로 재시드';
const ARIA_PAUSE = '규칙 스트립 정지됨 — 클릭하거나 스페이스바로 재생, R 로 재시드';
export default function ElementaryRuleStrip(){
const rootRef = useRef<HTMLDivElement | null>(null);
const cvRef = useRef<HTMLCanvasElement | null>(null);
useEffect(() => {
const root = rootRef.current;
const cv = cvRef.current;
if (!root || !cv) return;
const rootEl: HTMLDivElement = root;
const cvEl: HTMLCanvasElement = cv;
const ctx = ctx2d(cvEl);
function updateAria(): void {
rootEl.setAttribute('aria-label', paused ? ARIA_PAUSE : ARIA_PLAY);
}
let paused = false;
const reduceMQ = window.matchMedia('(prefers-reduced-motion: reduce)');
function human(): void { rootEl.classList.remove('is-demo'); }
function reseed(): void {
SEED = (SEED * 1103515245 + 12345) >>> 0;
rnd = makeLcg(SEED || 1);
clearGrid();
const seedX = Math.floor(rnd() * COLS);
seedRow(seedX);
}
function togglePause(): void {
paused = !paused;
updateAria();
}
function onActivate(e: Event): void {
if (e.type === 'keydown'){
const ke = e as KeyboardEvent;
if (ke.key === 'r' || ke.key === 'R'){
human();
reseed();
render(ctx);
return;
}
if (ke.key !== 'Enter' && ke.key !== ' ') return;
ke.preventDefault();
}
human();
if (reduceMQ.matches){
step();
render(ctx);
} else {
togglePause();
}
}
const GEN_INTERVAL_MS = 32;
let acc = 0, last = 0, rafId = 0;
function frame(now: number): void {
if (!reduceMQ.matches){
if (!last) last = now;
const dt = now - last;
last = now;
if (!paused){
acc += dt;
while (acc >= GEN_INTERVAL_MS){
step();
acc -= GEN_INTERVAL_MS;
}
render(ctx);
}
rafId = requestAnimationFrame(frame);
}
}
function onResize(): void { render(ctx); }
rootEl.addEventListener('click', onActivate);
rootEl.addEventListener('keydown', onActivate);
window.addEventListener('resize', onResize);
clearGrid();
seedRow(Math.floor(COLS / 2));
updateAria();
render(ctx);
if (!reduceMQ.matches){
rafId = requestAnimationFrame(frame);
}
return () => {
rootEl.removeEventListener('click', onActivate);
rootEl.removeEventListener('keydown', onActivate);
window.removeEventListener('resize', onResize);
if (rafId) cancelAnimationFrame(rafId);
};
}, []);
return (
<main className={styles.stage}>
<div ref={rootRef} className={`${styles.rv} is-demo`} tabIndex={0} role="button" aria-label="규칙 스트립 진행 중 — 클릭하거나 스페이스바로 정지, R 로 재시드">
<canvas ref={cvRef} className={styles.rv__cv} width={480} height={300} />
<p className={styles.rv__hint}>클릭·스페이스바로 정지, R 로 재시드</p>
</div>
</main>
);
}02 Strip Automaton Whose Next Row Pattern Follows a Single Rule-Number Value — Measured area 53.91% · moving frames 23/23Original by GODRICH · SCSS and React ship in the zip · Conway Game of Life in Canvas, 9 Patterns
The post this demo lives in
Conway Game of Life in Canvas, 9 PatternsConway game of life: The grid looks random, but a live-neighbor value decides life or death, ages into fading color, and See all demos in this post
If you like this effect - similar demos
Creative UI01 The three-rule flock — a minimal swarm on separation, alignment, cohesion9 Canvas Boids Algorithm Flocking Birds Simulation
Creative UI01 The basic fall — one rule: move down when the cell below is emptyFalling Sand Simulator, 9 Ways (No Library)
Creative UI09 Wallpaper export — turning the pattern into a repeating tileKaleidoscope Drawing: 9 Canvas Symmetry Tools