Software | Web | Project Management

HTML5 Canvas Game of Life

Conway's game of life is a model of cellular automation that produces intriguing, organic looking patterns and animation as it evolves. Like my fractal generator, I think it's another example of fascinating imagery that is produced with very simple rules.

Cells are modelled on a grid, and they evolve depending on their number of neighbours. With enough 'living' neighbours a new cell is born. However a living cell must have two neighbours to continue into the next frame. Too few represents isolation and too many overcrowding, thus creating a constantly shifting pattern

To create this model I used the HTML5 canvas again, simply drawing the blocks as needed and recalculating using setInterval, which calls a function every defined number of milliseconds, providing the browser is fast enough.


var canvas = document.getElementById("gameoflife");
canvas.setAttribute('width',canvas.clientWidth);
canvas.setAttribute('height',canvas.clientHeight);
var ctx = canvas.getContext("2d");
var canvasData = ctx.getImageData(0, 0, canvas.width, canvas.height);

I decided to display cells that would be born or die in the next frame with separate colours. This isn't necessary, but I think adds to the effect


var cell_color = {born : '#006652',
                  living : '#258800',
                  dying_under : '#9f8500',
                  dying_over : '#9f6300',
                  dead:'#000'}; // Colours for each cell
var matrix_size = 100; // Number of cells
var margin = 1; // Margin between cells
var cell_size = Math.floor(canvas.width / matrix_size) - margin; // Calculate cell size for drawing
var cell = [];

Cells are stored in a multidimensional array, which needs to be initiated in a loop. I take this opportunity to populate the cells randomly to create a staring pattern to run the life algorithm on. I also create a 'dead' buffer around the cells for the purposes of calculating the neighbours of cells on the edge. This will mean life will not be able to sustain itself on the edges, but another approach would be to wrap to the other side.

As we need to know the current and calculate the state of the next frame, I created a construct (or 'class') cellUnit to encapsulate this neatly.


// Store the status and colour of a cell for quick reference
function cellUnit(status) {
    this.status = status; // 1 for living 0 for dead
    this.status_next = status; // The status be in the next frame

    this.setColor = function(){
     if (this.status == 1)
            this.color = cell_color.living;
        else
            this.color = cell_color.dead;
    }
}

// Initialise cell array and populate randomly
function initialiseCells() {
    for (x=-1; x < matrix_size + 1; x++) {
        cell[x] = []; // Initialise next dimension
        for (y=-1; y < matrix_size + 1; y++) {
            if (x < 0 || y < 0 || x == matrix_size || y == matrix_size)
                cell[x][y] = new cellUnit(0); // Buffer of empty cells around the perimeter
            else
                cell[x][y] = new cellUnit(Math.round(Math.random()));
            cell[x][y].setColor();
        }
    }
}

The main loop counts the number of neighbours for each cell and sets its status for the next frame. As I want to colour some cells according to their future state, the colour is set here as well. Then the cell is drawn as a (square) rectangle onto the canvas.


 for (x=0; x < matrix_size; x++) {
        for (y=0; y < matrix_size; y++) {
            var x_pos = (x * cell_size) + (x * margin);
            var y_pos = (y * cell_size) + (y * margin);
            // Calculate number of neighbours and set next cell status and colour for next frame
            var neighbours = cell[x-1][y-1].status + cell[x][y-1].status + cell[x+1][y-1].status
                            + cell[x-1][y].status + cell[x+1][y].status
                            + cell[y+1][x-1].status + cell[y+1][x].status + cell[y+1][x+1].status;
            if (cell[x][y].status == 1 && (neighbours < 2 || neighbours > 3)) {
                cell[x][y].status_next = 0;
                if (neighbours < 2)
                    cell[x][y].color = cell_color.dying_under;
                else
                    cell[x][y].color = cell_color.dying_over;
            } else if (cell[x][y].status == 0 && neighbours == 3) {
                cell[x][y].status_next = 1;
                cell[x][y].color = cell_color.born;
            }
            // Draw cell on canvas
            ctx.fillStyle = cell[x][y].color;
            ctx.fillRect(x_pos, y_pos, cell_size, cell_size);
        }
    }

Now the complete state of the next frame is known, the cellUnit array can be turned over to the next frame. If have called the loop again to do this, but would like to try a more elegant solution. Perhaps by building a more sophisticated datatype aware of the arrays.


    // update the frame
    for (x=0; x < matrix_size; x++) {
        for (y=0; y < matrix_size; y++) {
            cell[x][y].status = cell[x][y].status_next;
            cell[x][y].setColor();
        }
    }

My demo contains a slider to vary the frame rate, so you can observe the life pattern in different ways. This means changing the interval which must be done by calling clearInterval first. Javascript makes this slightly painful because you must provide indentified returned when previously calling setInterval. I opted to store this in a global intervalID, as it won't otherwise be available in the context when the event listener is triggered


// Set approx frame rate
function updateInterval(speed) {
    interval_rate = 1000/speed.value;
    // Update frame according to interval in ms
    clearInterval(intervalID);
    intervalID = setInterval(function(){drawCells()},interval_rate);
}

Add a comment

captcha