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.

// You can find the Turtle API reference here: https://turtletoy.net/syntax
Canvas.setpenopacity(0.2);

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

//note: can't exceed 32 due to integer bits.
//note: 16 seems the minimum size for the patterns.
const BLOCK_COUNT = 16;
const BLOCK_SIZE = 200 / BLOCK_COUNT;
const BLOCK_HALF_SIZE = BLOCK_SIZE / 2;
const MIDDLE_POINT = BLOCK_COUNT / 2;

//note: has major impact on the performance.
const LINE_SPACING = 0.5;

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

// Initialize a random pattern in the middle.
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!");
}

// The walk function will be called until it returns false.
function walk(i) {
    // Render frame.
    render();

    // Calculate next generation.
    next_generation();
    
    // Should be enough. :)
    return i < 50;
}

function render() {
    for(x = 0; x < BLOCK_COUNT; x++) {
        for(y = 0; y < BLOCK_COUNT; y++) {
            if(isset(x, y)) {
                draw(x, y);
            }
        }
    }
}

function draw(x, y) {
    let nx = -100 - BLOCK_HALF_SIZE + (x * BLOCK_SIZE);
    let ny = -100 + BLOCK_HALF_SIZE + (y * BLOCK_SIZE);
    
    for(cy = ny; cy < ny + BLOCK_SIZE; cy += LINE_SPACING) {
        turtle.goto(nx, cy);
        turtle.pendown();
        turtle.goto(nx + BLOCK_SIZE, cy);
        turtle.penup();
    }
}

function next_generation() {
    // Write grid to buffer.
    for (i = 0; i < BLOCK_COUNT; i++) {
        buffer[i] = matrix[i];
    }

    // Write new generation to buffer.
    for (x = 0; x < BLOCK_COUNT; x++) {
        for (y = 0; y < BLOCK_COUNT; y++) {
            if (should_toggle_state(x, y)) {
                buffer[y] ^= (1 << x);
            }
        }
    }

    // Write buffer to grid.
    for (i = 0; i < BLOCK_COUNT; i++) {
        matrix[i] = buffer[i];
    }
}

function should_toggle_state(x, y) {
    let isAlive = (matrix[y] & (1 << x)) == (1 << x);
    let numberOfNeighbors = get_number_of_neighbors(x, y, isAlive);

    if (isAlive && (numberOfNeighbors == 2 || numberOfNeighbors == 3)) {
        return false;
    }

    if (!isAlive && numberOfNeighbors == 3) {
        return true;
    }

    return isAlive;
}

function get_number_of_neighbors(x, y, isAlive) {
    let sum = 0;

    for(cy = y - 1; cy <= y + 1; cy++) {
        if (cy < 0 || cy >= BLOCK_COUNT) {
            continue;
        }

        let v = matrix[cy] >> (x - 1) & 0x7;
        v = v - ((v >> 1) & 0x55555555);
        v = (v & 0x33333333) + ((v >> 2) & 0x33333333);
        sum += (((v + (v >> 4)) & 0xF0F0F0F) * 0x1010101) >> 24;
    }

    return isAlive ? sum - 1 : sum;
}

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

function isset(x, y) {
    return (matrix[y] & (1 << x)) == (1 << x);
}

/* 
    Load pattern from RLE format. 
    b = Dead
    o = Alive
    $ = Newline
*/
function load_pattern(startx, starty, rle) {
    let x = startx, y = starty;
    let pattern = rle.replace(/(\d+)(\w)/g, 
        function(m,n,c){
            return new Array(parseInt(n,10)+1).join(c);
        }
    );
    
    for(ci = 0; ci < pattern.length; ci++) {
        switch(pattern[ci]) {
            //case "b":
            //    set(x, y, false);
            //    break;
            case "o":
                set(x, y, true);
                break;
            case "$":
              x = startx;
              y++;
              continue;
        }

        x++;
    }
}