library / retro & cyberpunk / game-of-life
live - 60fps 1920 x 1080
game-of-life.html - self-contained
<!DOCTYPE html>
<html>
<head>
<title>Game of Life</title>
<style>
    :root { --bg-color: #05070d; --cell-new: #34d399; --cell-old: #164e63; }
    * { margin: 0; padding: 0; }
    body { height: 100vh; background: var(--bg-color); overflow: hidden; }
    canvas { display: block; }
</style>
</head>
<body>
    <canvas id="c"></canvas>
<script>
    const cv = document.getElementById('c'), ctx = cv.getContext('2d');
    const css = getComputedStyle(document.documentElement);
    const bg = (css.getPropertyValue('--bg-color') || '#05070d').trim();
    function hex(h){h=(h||'').trim().replace('#','');if(h.length===3)h=h.split('').map(c=>c+c).join('');const n=parseInt(h,16);return[(n>>16)&255,(n>>8)&255,n&255];}
    const cNew = hex(css.getPropertyValue('--cell-new') || '#34d399');
    const cOld = hex(css.getPropertyValue('--cell-old') || '#164e63');
    const CELL = 9;
    let cols, rows, cur, nxt, age, w, h;
    function seed() { for (let i = 0; i < cols * rows; i++) { cur[i] = Math.random() < 0.16 ? 1 : 0; age[i] = 0; } }
    function build() {
        w = cv.width = innerWidth; h = cv.height = innerHeight;
        cols = Math.ceil(w / CELL); rows = Math.ceil(h / CELL);
        cur = new Uint8Array(cols * rows); nxt = new Uint8Array(cols * rows); age = new Uint16Array(cols * rows);
        seed();
    }
    addEventListener('resize', build); build();
    function tick() {
        let alive = 0;
        for (let y = 0; y < rows; y++) for (let x = 0; x < cols; x++) {
            let n = 0;
            for (let dy = -1; dy <= 1; dy++) for (let dx = -1; dx <= 1; dx++) {
                if (!dx && !dy) continue;
                const xx = (x + dx + cols) % cols, yy = (y + dy + rows) % rows;
                n += cur[yy * cols + xx];
            }
            const i = y * cols + x, a = cur[i];
            const live = a ? (n === 2 || n === 3) : (n === 3);
            nxt[i] = live ? 1 : 0;
            if (live) { age[i] = a ? Math.min(255, age[i] + 1) : 0; alive++; }
        }
        [cur, nxt] = [nxt, cur];
        if (alive < cols * rows * 0.02) {  // reseed a soup when it fizzles out
            for (let k = 0; k < cols * rows * 0.06; k++) cur[(Math.random() * cols * rows) | 0] = 1;
        }
    }
    function draw() {
        ctx.fillStyle = bg; ctx.fillRect(0, 0, w, h);
        for (let y = 0; y < rows; y++) for (let x = 0; x < cols; x++) {
            const i = y * cols + x; if (!cur[i]) continue;
            const t = Math.min(1, age[i] / 40);
            ctx.fillStyle = `rgb(${cNew[0]+(cOld[0]-cNew[0])*t|0},${cNew[1]+(cOld[1]-cNew[1])*t|0},${cNew[2]+(cOld[2]-cNew[2])*t|0})`;
            ctx.fillRect(x * CELL + 1, y * CELL + 1, CELL - 2, CELL - 2);
        }
    }
    let acc = 0;
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) { for (let i = 0; i < 12; i++) tick(); draw(); }
    else (function loop() { acc++; if (acc % 4 === 0) tick(); draw(); requestAnimationFrame(loop); })();
</script>
</body>
</html>

About this animation

Conway's Game of Life plays out forever — gliders, oscillators, and colonies bloom and die across a glowing cellular grid.

Source Code
Copied to clipboard