Rainbow Game of Life

An implementation of Conway’s Game of Life, with rainbow cells. There are three tests included, as well as a large number of pre-drawn cell patterns available through calling the functions in the cells namespace at the bottom of the file.

enum StillLife {
    Block,
    Beehive,
    Loaf,
    Boat,
    Tub
}

enum Oscillator {
    Blinker,
    Toad,
    Beacon,
    Pulsar,
    Pentadecathlon
}

enum Motion {
    Glider,
    LightWeight,
    Gospers,
    Simkins,
    SimkinsDouble,
    Engine,
    BlockLayer
}

enum OddCell {
    RPentomino,
    DieHard,
    Acorn
}

const width = screen.width;
const height = screen.height;
scene.setBackgroundImage(image.create(width, height));

// test1();
test2();
// test3();

function test1() {
    cells.createRandom(4000);
}

function test2() {
    for (let i = 0; i < 15; ++i) {
        for (let j = 0; j < 6; ++j) {
            cells.createOscillator(Oscillator.Pentadecathlon, 8 + 10 * i, 5 + 20 * j);
        }
    }
}
function test3() {
    for (let i = 0; i < 5; ++i) {
        cells.createMotion(Motion.Gospers, 15, 2 + i * 25);
    }
}

// buffers[bufferNum][x][y] corresponds to whether the cell at location (x,y)
// was alive in the given buffer
let buffers: boolean[][][];
let currentBuffer: number;
init();

game.onUpdateInterval(100, nextGeneration);
// game.onUpdate(nextGeneration);

function countNeighbors(src: boolean[][], x: number, y: number): number {
    const lX = x - 1; // left x
    const rX = x + 1; // right x
    const lY = y - 1; // left y
    const rY = y + 1; // right y

    let count = 0;
    if (src[lX][lY])++count;
    if (src[lX][y])++count;
    if (src[lX][rY])++count;
    if (src[x][lY])++count;
    if (src[x][rY])++count;
    if (src[rX][lY])++count;
    if (src[rX][y])++count;
    if (src[rX][rY])++count;
    return count;
}

function nextGeneration() {
    const lastGeneration = buffers[currentBuffer % 2];
    const currGeneration = buffers[++currentBuffer % 2];
    const bkgd = scene.backgroundImage();

    // leave 1 pixel of unused edge on each side
    // to avoid having to deal with oob checking
    for (let x = 1; x < width - 1; ++x) {
        for (let y = 1; y < height - 1; ++y) {
            const neighbors = countNeighbors(lastGeneration, x, y);
            if (lastGeneration[x][y] && (neighbors < 2 || neighbors > 3)) {
                // Previously alive cell has died due to under- or over-population
                currGeneration[x][y] = false;
                bkgd.setPixel(x, y, 0);
            } else if (!lastGeneration[x][y] && neighbors == 3) {
                // Previously empty location has new cell born
                currGeneration[x][y] = true;
                bkgd.setPixel(x, y, randint(1, 0xd));
            } else {
                // State is unchanged
                currGeneration[x][y] = lastGeneration[x][y];
            }
        }
    }
}

function init() {
    const bkgd = scene.backgroundImage();
    buffers = [[], []];
    for (let x = 0; x < width; x++) {
        buffers[0][x] = [];
        buffers[1][x] = [];
        for (let y = 0; y < height; y++) {
            buffers[0][x][y] = bkgd.getPixel(x, y) != 0;
            buffers[1][x][y] = false;
        }
    }

    currentBuffer = 0;
    // Draw a border around screen, as those pixels are counted as 'not alive'
    for (let x = 0; x < width; x++) {
        buffers[0][x][0] = false;
        buffers[0][x][height - 1] = false;
        bkgd.setPixel(x, 0, 1);
        bkgd.setPixel(x, height - 1, 1);
    }
    for (let y = 0; y < height; y++) {
        buffers[0][0][y] = false;
        buffers[0][width - 1][y] = false;
        bkgd.setPixel(0, y, 1);
        bkgd.setPixel(width - 1, y, 1);
    }
}

namespace cells {
    export function createStillLife(toDisplay: StillLife,
        x: number,
        y: number,
        src?: Image) {
        if (!src) src = scene.backgroundImage();

        let display: Image;
        switch (toDisplay) {
            case StillLife.Block: {
                display = img`
                    1 1
                    1 1
                `
                break;
            }
            case StillLife.Beehive: {
                display = img`
                    . 1 1 .
                    1 . . 1
                    . 1 1 .
                `
                break;
            }
            case StillLife.Loaf: {
                display = img`
                    . 1 1 .
                    1 . . 1
                    . 1 . 1
                    . . 1 .
                `
                break;
            }
            case StillLife.Boat: {
                display = img`
                    1 1 .
                    1 . 1
                    . 1 .
                `
                break;
            }
            case StillLife.Block: {
                display = img`
                    1 1
                    1 1
                `
                break;
            }
            case StillLife.Tub: {
                display = img`
                    . 1 .
                    1 . 1
                    . 1 .
                `
                break;
            }
            default: return;
        }
        src.drawImage(display, x, y);
    }

    export function createOscillator(toDisplay: Oscillator,
        x: number,
        y: number,
        src?: Image) {
        if (!src) src = scene.backgroundImage();

        let display: Image;
        switch (toDisplay) {
            case Oscillator.Blinker: {
                display = img`
                    1
                    1
                    1
                `
                break;
            }
            case Oscillator.Toad: {
                display = img`
                    . 1 1 1
                    1 1 1 .
                `
                break;
            }
            case Oscillator.Beacon: {
                display = img`
                    1 1 . .
                    1 1 . .
                    . . 1 1
                    . . 1 1
                `
                break;
            }
            case Oscillator.Pulsar: {
                display = img`
                    . . . . 1 . . . . . 1 . . . .
                    . . . . 1 . . . . . 1 . . . .
                    . . . . 1 1 . . . 1 1 . . . .
                    . . . . . . . . . . . . . . .
                    1 1 1 . . 1 1 . 1 1 . . 1 1 1
                    . . 1 . 1 . 1 . 1 . 1 . 1 . .
                    . . . . 1 1 . . . 1 1 . . . .
                    . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . 1 1 . . . .
                    . . 1 . 1 . 1 . 1 . 1 . 1 . .
                    1 1 1 . . 1 1 . 1 1 . . 1 1 1
                    . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . 1 1 . . . .
                    . . . . 1 . . . . . 1 . . . .
                    . . . . 1 . . . . . 1 . . . .
                `
                break;
            }
            case Oscillator.Pentadecathlon: {
                display = img`
                    1 1 1
                    . 1 .
                    . 1 .
                    1 1 1
                    . . .
                    1 1 1
                    1 1 1
                    . . .
                    1 1 1
                    . 1 .
                    . 1 .
                    1 1 1
                `
                break;
            }
            default: return;
        }
        src.drawImage(display, x, y);
    }

    export function createMotion(toDisplay: Motion,
        x: number,
        y: number,
        src?: Image) {
        if (!src) src = scene.backgroundImage();

        let display: Image;
        switch (toDisplay) {
            case Motion.Glider: {
                display = img`
                    . 1 .
                    . . 1
                    1 1 1
                `
                break;
            }
            case Motion.LightWeight: {
                display = img`
                    1 . . 1 .
                    . . . . 1
                    1 . . . 1
                    . 1 1 1 1
                `
                break;
            }
            case Motion.Gospers: {
                display = img`
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . 1 1 . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . 1 1 1 . . . . . . . . . .
                    . . . . . . . . . 1 . . . . . . . . . . . . . . . 1 1 . 1 . . . . . 1 1
                    . . . . . . . 1 . 1 . . . . 1 1 1 . . . . . . . . 1 . . 1 . . . . . 1 1
                    1 1 . . . . 1 . 1 . . . . . . . . . . . . . . . . 1 1 . 1 . . . . . . .
                    1 1 . . . 1 . . 1 . . . . . . . 1 . . 1 1 . . 1 1 1 . . . . . . . . . .
                    . . . . . . 1 . 1 . . . . . . . 1 . . . 1 . . 1 1 . . . . . . . . . . .
                    . . . . . . . 1 . 1 . . . . . . 1 . . 1 . . . . . . . . . . . . . . . .
                    . . . . . . . . . 1 . . . . . . . . 1 1 . . . . . . . . . . . . . . . .
                `;
                break;
            }
            case Motion.Simkins: {
                display = img`
                    1 1 . . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . .
                    1 1 . . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . 1 1 . 1 1 . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 . . . . . 1 . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . 1 . . 1 1
                    . . . . . . . . . . . . . . . . . . . . . 1 1 1 . . . 1 . . . 1 1
                    . . . . . . . . . . . . . . . . . . . . . . . . . . 1 . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . 1 1 . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . 1 . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 1 1 . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . . . .
                `
                break;
            }
            case Motion.SimkinsDouble: {
                display = img`
                    1 1 . . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . .
                    1 1 . . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . 1 1 . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . . 1 1 . 1 1 . . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 . . . . . 1 . . . . .
                    . . . . . . . . . . . . . . . . . . . . . 1 . . . . . . 1 . . 1 1
                    . . . . . . . . . . . . . . . . . . . . . 1 1 1 . . . 1 . . . 1 1
                    . . . . . . . . . . . . . . . . . . . . . . . . . . 1 . . . . . .
                `
                break;
            }
            case Motion.Engine: {
                display = img`
                    1 1 1 1 1 1 1 1 . 1 1 1 1 1 . . . 1 1 1 . . . . . . 1 1 1 1 1 1 1 . 1 1 1 1 1
                `
                break;
            }
            case Motion.BlockLayer: {
                display = img`
                    1 1 1 . 1
                    1 . . . .
                    . . . 1 1
                    . 1 1 . 1
                    1 . 1 . 1
                `
                break;
            }
            default: return;
        }

        src.drawImage(display, x, y);
    }

    export function createOddCell(toDisplay: OddCell,
        x: number,
        y: number,
        src?: Image) {
        if (!src) src = scene.backgroundImage();

        let display: Image;
        switch (toDisplay) {
            case OddCell.RPentomino: {
                display = img`
                    . 1 1
                    1 1 .
                    . 1 .
                `
                break;
            }
            case OddCell.DieHard: {
                display = img`
                    . . . . . . 1 .
                    . . . . . . . .
                    1 1 . . . . 1 .
                    . 1 . . . 1 1 1
                `
                break;
            }
            case OddCell.Acorn: {
                display = img`
                    . 1 . . . . .
                    . . . 1 . . .
                    1 1 . . 1 1 1
                `;
                break;
            }
            default: return;
        }

        src.drawImage(display, x, y);
    }

    export function createRandom(count: number, src?: Image) {
        if (!src) src = scene.backgroundImage();

        for (let i = 0; i < count; ++i)
            src.setPixel(randint(0, width), randint(0, height), 1);
    }
}