Stain of Life

Using the Game of Life rules, create a heatmap based on the patterns present.

Added basic support for RLE notation (left out the headers): conwaylife.com/wiki/run_length_encoded

Log in to post a comment.

Canvas.setpenopacity(0.2);

const turtle = new Turtle();
turtle.penup();

const BLOCK_COUNT = 16;
const BLOCK_SIZE = 200 / BLOCK_COUNT;
const BLOCK_HALF_SIZE = BLOCK_SIZE / 2;
const MIDDLE_POINT = BLOCK_COUNT / 2;
const LINE_SPACING = 0.5;
const GRID_LEFT = -100 - BLOCK_HALF_SIZE;
const GRID_TOP = -100 + BLOCK_HALF_SIZE;
const POP3 = [0, 1, 1, 2, 1, 2, 2, 3];

let matrix = new Array(BLOCK_COUNT).fill(0);
let buffer = new Array(BLOCK_COUNT).fill(0);

if (Math.random() < 0.5) {
    load_pattern(MIDDLE_POINT - 1, MIDDLE_POINT - 2, "bob$3o$obo$bob!");
} else {
    load_pattern(MIDDLE_POINT - 2, MIDDLE_POINT - 3, "obobo$o3bo$o3bo$o3bo$obobo!");
}

function walk(i) {
    render();
    next_generation();
    return i < 50;
}

function render() {
    turtle.pendown();

    for (let y = 0; y < BLOCK_COUNT; y++) {
        const rowBits = matrix[y];
        if (rowBits === 0) {
            continue;
        }

        const baseY = GRID_TOP + (y * BLOCK_SIZE);
        for (let cy = baseY; cy < baseY + BLOCK_SIZE; cy += LINE_SPACING) {
            let bits = rowBits;
            let x = 0;

            while (bits !== 0 && x < BLOCK_COUNT) {
                while ((bits & 1) === 0 && x < BLOCK_COUNT) {
                    bits >>>= 1;
                    x++;
                }

                if (x >= BLOCK_COUNT || bits === 0) {
                    break;
                }

                const start = x;
                while ((bits & 1) === 1 && x < BLOCK_COUNT) {
                    bits >>>= 1;
                    x++;
                }

                turtle.jump(GRID_LEFT + (start * BLOCK_SIZE), cy);
                turtle.goto(GRID_LEFT + (x * BLOCK_SIZE), cy);
            }
        }
    }

    turtle.penup();
}

function next_generation() {
    for (let y = 0; y < BLOCK_COUNT; y++) {
        const top = y > 0 ? matrix[y - 1] : 0;
        const mid = matrix[y];
        const bottom = y + 1 < BLOCK_COUNT ? matrix[y + 1] : 0;

        let nextRow = 0;
        for (let x = 0; x < BLOCK_COUNT; x++) {
            const alive = (mid >>> x) & 1;
            const mask = x === 0 ? 0x3 : 0x7;

            const topSlice = (top >>> (x === 0 ? 0 : x - 1)) & mask;
            const midSlice = (mid >>> (x === 0 ? 0 : x - 1)) & mask;
            const bottomSlice = (bottom >>> (x === 0 ? 0 : x - 1)) & mask;
            const neighbors = POP3[topSlice] + POP3[midSlice] + POP3[bottomSlice] - alive;

            if (neighbors === 3 || (alive === 1 && neighbors === 2)) {
                nextRow |= (1 << x);
            }
        }

        buffer[y] = nextRow;
    }

    const tmp = matrix;
    matrix = buffer;
    buffer = tmp;
}

function set(x, y, value) {
    if (value) {
        matrix[y] |= (1 << x);
    } else {
        matrix[y] &= ~(1 << x);
    }
}

function load_pattern(startx, starty, rle) {
    let x = startx;
    let y = starty;
    let runLen = 0;

    for (let i = 0; i < rle.length; i++) {
        const ch = rle[i];
        if (ch >= "0" && ch <= "9") {
            runLen = (runLen * 10) + (ch.charCodeAt(0) - 48);
            continue;
        }

        const count = runLen === 0 ? 1 : runLen;
        runLen = 0;

        if (ch === "o") {
            for (let j = 0; j < count; j++) {
                set(x + j, y, true);
            }
            x += count;
            continue;
        }

        if (ch === "b") {
            x += count;
            continue;
        }

        if (ch === "$") {
            y += count;
            x = startx;
            continue;
        }

        if (ch === "!") {
            break;
        }
    }
}