Conway's Game of Life Using HTML5

Introduction

The "game" is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input. One interacts with the Game of Life by creating an initial configuration and observing how it evolves. First we create the main function in JavaScript, then call it in the body and then apply CSS. 

To make things more interesting and efficient, I used two Canvas elements, one on top of the other rather than a single Canvas. The Canvas at the bottom of the Z-order (display order) is the first and I use it to draw the grid and background colour, the second Canvas is used only to render the life forms.

I implemented the main code in JavaScript class called Life, like this:

function
Life(size) {
              this.size = size;
               var count = size * size;
               this.torus = new Array(count);
                this.clear = function () {
                   for (var i = 0; i < count; i++)
                       this.torus[i] = 0;
               };
               this.getNeighbours = function (x, y) {
                   var count = [0, 0, 0, 0, 0];
                   // prev row
                   count[this.get(x - 1, y - 1)]++;
                   count[this.get(x, y - 1)]++;
                   count[this.get(x + 1, y - 1)]++;
                   // this row
                   count[this.get(x - 1, y)]++;
                   count[this.get(x + 1, y)]++;
                   // next row
                   count[this.get(x - 1, y + 1)]++;
                   count[this.get(x, y + 1)]++;
                   count[this.get(x + 1, y + 1)]++;
                   return count;
               };
               this.get = function (x, y) {
                   return this.torus[this.getIndex(x, y)];
               };
               this.set = function (x, y, value) {
                   this.torus[this.getIndex(x, y)] = value;
               };
                this.getIndex = function (x, y) {
                   if (x < -1 || y < -1 || x > size || y > size)
                       throw "Index is out of bounds";
                   if (x == -1)
                       x = size - 1;
                   else if (x == size)
                       x = 0;
                   if (y == -1)
                       y = size - 1;
                   else if (y == size)
                       y = 0;
                   return x + y * this.size;
               };
               this.clear();
           }

The class implements an NxN array but stored internally as a one-dimensional array. This is basically what the getIndex() function does, but with a slight twist to implement the torus. So the row at -1 is mapped to the row at N-1 and row N is mapped to row 0; similarly for the columns. The getIndex function is in turn used by a simple set value and get value function and these are in turn used by the main function called getNeighbours() which returns an array of length 5 where the first element is not used and the other four elements are the counts of each type of life form. 

The reason the first element is not used is to simplify the code is because the life forms are stored as integers in the grid, e.g. a cell value of 0
 corresponds to empty, a value of 1 corresponds to life form type 1. The only other function is a clear() which sets all values to 0 (empty).
 
The HTML used to accomplish this is shown below

<
div style="position:relative">
    <canvas id='canvas2' width='641' height='641' on></canvas>
    <canvas id='canvas1' width='641' height='641' on>Canvas is not supported by this browser.</canvas>
</
div>
 
I positioned the two Canvas elements using CSS. The key point is that they need to be placed in a <div> that has position: relative and the embedded style sheet for Canvas is set to position: absolute and top and bottom set to 0.
 
The whole program look like this:
 
<head>
    <title>Mouse Game</title>
    <style type="text/css">
       body
       {
              font-size: 11pt;
              font-family: verdana, arial, sans-serif;
       }
       select
       {
              font-size: 11pt;
       }
       div#params
       {
              margin: 11px;
       }
       canvas
       {
              border-color: Gray;
              border-width: thin;
              position: absolute;
              top: 0px;
              left: 0px;
       }
       #canvas2
       {
           background-color: #f5f5f5;
       }
       button
       {
              width: 80px;
              color: #393939;
       }
       </style>
       <script type="text/javascript" >
           function Life(size) {
               this.size = size;
               var count = size * size;
               this.torus = new Array(count);
               this.clear = function () {
                   for (var i = 0; i < count; i++)
                       this.torus[i] = 0;
               };
               this.getNeighbours = function (x, y) {
                   var count = [0, 0, 0, 0, 0];
                   // previous row
                   count[this.get(x - 1, y - 1)]++;
                   count[this.get(x, y - 1)]++;
                   count[this.get(x + 1, y - 1)]++;
                   // this row
                   count[this.get(x - 1, y)]++;
                   count[this.get(x + 1, y)]++;
                   // next row
                   count[this.get(x - 1, y + 1)]++;
                   count[this.get(x, y + 1)]++;
                   count[this.get(x + 1, y + 1)]++;
                   return count;
               };
                   this.get = function (x, y) {
                   return this.torus[this.getIndex(x, y)];
               };
                   this.set = function (x, y, value) {
                   this.torus[this.getIndex(x, y)] = value;
               };
               this.getIndex = function (x, y) {
                   if (x < -1 || y < -1 || x > size || y > size)
                       throw "Index out of bounds";
                   if (x == -1)
                       x = size - 1;
                   else if (x == size)
                       x = 0;
                   if (y == -1)
                       y = size - 1;
                   else if (y == size)
                       y = 0;
                   return x + y * this.size;
               };
               this.clear();
           }
           function relMouseCoords(event) {
               var totalOffsetX = 0;
               var totalOffsetY = 0;
               var canvasX = 0;
               var canvasY = 0;
               var currentElement = this;
               do {
                   totalOffsetX += currentElement.offsetLeft;
                   totalOffsetY += currentElement.offsetTop;
               }
               while (currentElement = currentElement.offsetParent)
               canvasX = event.pageX - totalOffsetX;
               canvasY = event.pageY - totalOffsetY;
               return { x: canvasX, y: canvasY }
           }
           HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;
       </script>
</
head>
<
body>
<
div id='params'>
       <button onclick="clearGame()">Clear</button>
       <button onclick="advance()" >Next</button>
       <button id="btnAnimate" onclick="animate()">Animate</button>
       <select id="color_menu0" name="color_menu0"  style="width: 60px">
              <option style="background-color:#00ced1" value="#00ced1" selected="selected"/>    
              <option style="background-color:#ff8c70" value="#ff8c70"/>    
              <option style="background-color:#008b8b" value="#008b8b"/>    
              <option style="background-color:#ff1493" value="#ff1493"/>    
       </select>
       <span id="generation" style="width: 130">Generation: 0</span>
       <span id="population" style="width: 130">Population: 0</span>
</
div>
<
div style="position:relative">
    <canvas id='canvas2' width='641' height='641' on></canvas>
    <canvas id='canvas1' width='641' height='641' on>Canvas is not supported by this browser.</canvas>
</
div>
<
script type="text/javascript" >
    // Keep a torus for the current and next generation
    var _size = 64;
    var _cellSize = 10;
    var _torus1 = new Life(_size);    
    var _torus2 = new Life(_size);    
    var _animate = false;
    var _generation = 0;
    var isMouseDown = false;
    function clearGame() {
        _torus1.clear();
        _generation = 0;
        generation.textContent = "Generation: 0";
        render();
        updatePopulation();
    }
    function animate() {
        _animate = !_animate;
        if (_animate) {
            advance();
            btnAnimate.textContent = "Stop";
        } else {
            btnAnimate.textContent = "Animate";
        }
    }
    function advance() {
       var _population = 0;
        for (var x = 0; x < _size; x++)
            for (var y = 0; y < _size; y++) {
                var neighbours = _torus1.getNeighbours(x, y); // dim 5 array
                var alive = 0;
                var kind = _torus1.get(x, y);
                if (kind > 0) {
                    // its alive and  stay alive if it has 2 or 3 neighbours
                    var count = neighbours[kind];
                    alive = (count == 2 || count == 3) ? kind : 0;
                }
                else {
                    // Its dead but will be born if any "kind" has exactly 3 neighbours
                    // This isn't "fair" but we use the first kind that has three neightbours
                    for (kind = 1; kind <= 4 && alive == 0; kind++) {
                        if (neighbours[kind] == 3)
                            alive = kind;
                    }
                }
                _torus2.set(x, y, alive);
                if (alive)
                    _population++;
            }
        var temp = _torus1; // arrays are only references!
        _torus1 = _torus2;
        _torus2 = temp;
        render();
        generation.textContent = "Generation: " + String(++_generation);
        population.textContent = "Population: " + String(_population);
        if (_animate)
            setTimeout("advance()", 50);
    }
    function renderCanvas(canvas, size, torus) {
        // read from Life and write to canvas             
        var context = canvas.getContext('2d');
        context.fillStyle = '#ff7f50';
        context.clearRect(0, 0, size * _cellSize, size * _cellSize);
        for (var x = 0; x < size; x++)
            for (var y = 0; y < size; y++) {
                var kind = _torus1.get(x, y) - 1;
                if (kind >= 0) {
                    context.fillStyle = color_menu0.options[kind].value;
                    context.fillRect(x * _cellSize, y * _cellSize, _cellSize, _cellSize);
                }
            }
    }
    function render() {
        renderCanvas(canvas1, _size, _torus1);
    }
    function drawGrid() {
        // Only ever called once!
        var context = canvas2.getContext('2d'); // canvas2 is the background canvas
        context.strokeStyle = '#808080';
        context.beginPath();
        for (var i = 0; i <= _size; i++) {
            // Draw vertical lines
            context.moveTo(i * _cellSize + 0.5, 0.5);
            context.lineTo(i * _cellSize + 0.5, _size * _cellSize);
            // Draw horizontal lines
            context.moveTo(0.5, i * _cellSize + 0.5);
            context.lineTo(_size * _cellSize, i * _cellSize + 0.5);
        }
        context.stroke();
    }
    drawGrid();
    canvas1.onmousedown = function canvasMouseDown(ev) {
        isMouseDown = true;
        var x = ev.pageX - this.offsetLeft;
        var y = ev.pageY - this.offsetTop;
        var coords = this.relMouseCoords(ev);
        setPoint(coords.x, coords.y);
    }
    canvas1.onmouseup = function canvasMouseDown(ev) {
        isMouseDown = false;
    }
    canvas1.onmousemove = function canvasMouseDown(ev) {
        if (isMouseDown) {
            var coords = this.relMouseCoords(ev);
            setPoint(coords.x, coords.y);
        }
    }
    function setPoint(x, y) {
        // convert to torus coords
        var i = Math.floor(x / _cellSize);
        var j = Math.floor(y / _cellSize);
        // Which kind
        var kind = 1 + color_menu0.selectedIndex;
        _torus1.set(i, j, kind);
        render();
        updatePopulation();
    }
    function updatePopulation() {
        var _population = 0;
        for (var x = 0; x < _size; x++)
            for (var y = 0; y < _size; y++) {
                if (_torus1.get(x, y))
                    _population++;
            }
        population.textContent = "Population: " + String(_population);
    }
</
script>
</
body>

Our output looks like this:

a1.jpg

a2.jpg

Up Next
    Ebook Download
    View all
    Learn
    View all