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 color, the second Canvas is used only to render the life forms.
 
I implemented the main code in JavaScript class called Life, like this:
  1. function Life(size) {  
  2.  this.size = size;  
  3.  var count = size * size;  
  4.  this.torus = new Array(count);  
  5.  this.clear = function() {  
  6.   for (var i = 0; i < count; i++)  
  7.    this.torus[i] = 0;  
  8.  };  
  9.  this.getNeighbours = function(x, y) {  
  10.   var count = [0, 0, 0, 0, 0];  
  11.   count[this.get(x - 1, y - 1)]++;  
  12.   count[this.get(x, y - 1)]++;  
  13.   count[this.get(x + 1, y - 1)]++;  
  14.   count[this.get(x - 1, y)]++;  
  15.   count[this.get(x + 1, y)]++;  
  16.   count[this.get(x - 1, y + 1)]++;  
  17.   count[this.get(x, y + 1)]++;  
  18.   count[this.get(x + 1, y + 1)]++;  
  19.   return count;  
  20.  };  
  21.  this.get = function(x, y) {  
  22.   return this.torus[this.getIndex(x, y)];  
  23.  };  
  24.  this.set = function(x, y, value) {  
  25.   this.torus[this.getIndex(x, y)] = value;  
  26.  };  
  27.  this.getIndex = function(x, y) {  
  28.   if (x < -1 || y < -1 || x > size || y > size)  
  29.    throw "Index is out of bounds";  
  30.   if (x == -1)  
  31.    x = size - 1;  
  32.   else if (x == size)  
  33.    x = 0;  
  34.   if (y == -1)  
  35.    y = size - 1;  
  36.   else if (y == size)  
  37.    y = 0;  
  38.   return x + y * this.size;  
  39.  };  
  40.  this.clear();  
  41. }  
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 setvalue() and getvalue() 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
  1. <div style="position:relative">  
  2.     <canvas id='canvas2' width='641' height='641' on></canvas>  
  3.     <canvas id='canvas1' width='641' height='641' on>Canvas is not supported by this browser.</canvas>  
  4. </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 looks like this:
  1. <head>  
  2.     <title>Mouse Game</title>  
  3.     <style type="text/css">  
  4.        body  
  5.        {  
  6.               font-size: 11pt;  
  7.               font-family: verdana, arial, sans-serif;  
  8.        }  
  9.        select  
  10.        {  
  11.               font-size: 11pt;  
  12.        }  
  13.        div#params  
  14.        {  
  15.               margin: 11px;  
  16.        }  
  17.        canvas  
  18.        {  
  19.               border-color: Gray;  
  20.               border-width: thin;  
  21.               position: absolute;  
  22.               top: 0px;  
  23.               left: 0px;  
  24.        }  
  25.        #canvas2  
  26.        {  
  27.            background-color: #f5f5f5;  
  28.        }  
  29.        button  
  30.        {  
  31.               width: 80px;  
  32.               color: #393939;  
  33.        }  
  34.        </style>  
  35.     <script type="text/javascript" >   
  36.            function Life(size) {  
  37.                this.size = size;  
  38.                var count = size * size;  
  39.                this.torus = new Array(count);  
  40.                this.clear = function () {  
  41.                    for (var i = 0; i < count; i++)  
  42.                        this.torus[i] = 0;   
  43.                };  
  44.                this.getNeighbours = function (x, y) {  
  45.                    var count = [0, 0, 0, 0, 0];  
  46.                    // previous row  
  47.                    count[this.get(x - 1, y - 1)]++;  
  48.                    count[this.get(x, y - 1)]++;  
  49.                    count[this.get(x + 1, y - 1)]++;  
  50.                    // this row  
  51.                    count[this.get(x - 1, y)]++;  
  52.                    count[this.get(x + 1, y)]++;  
  53.                    // next row  
  54.                    count[this.get(x - 1, y + 1)]++;  
  55.                    count[this.get(x, y + 1)]++;  
  56.                    count[this.get(x + 1, y + 1)]++;  
  57.                    return count;  
  58.                };  
  59.                    this.get = function (x, y) {  
  60.                    return this.torus[this.getIndex(x, y)];  
  61.                };  
  62.                    this.set = function (x, y, value) {  
  63.                    this.torus[this.getIndex(x, y)] = value;  
  64.                };  
  65.                this.getIndex = function (x, y) {  
  66.                    if (x < -1 || y < -1 || x > size || y > size)  
  67.                        throw "Index out of bounds";  
  68.                    if (x == -1)  
  69.                        x = size - 1;  
  70.                    else if (x == size)  
  71.                        x = 0;  
  72.                    if (y == -1)  
  73.                        y = size - 1;  
  74.                    else if (y == size)  
  75.                        y = 0;  
  76.                    return x + y * this.size;  
  77.                };  
  78.                this.clear();  
  79.            }  
  80.            function relMouseCoords(event) {  
  81.                var totalOffsetX = 0;  
  82.                var totalOffsetY = 0;  
  83.                var canvasX = 0;  
  84.                var canvasY = 0;  
  85.                var currentElement = this;  
  86.                do {  
  87.                    totalOffsetX += currentElement.offsetLeft;  
  88.                    totalOffsetY += currentElement.offsetTop;  
  89.                }  
  90.                while (currentElement = currentElement.offsetParent)  
  91.                canvasX = event.pageX - totalOffsetX;  
  92.                canvasY = event.pageY - totalOffsetY;  
  93.                return { x: canvasX, y: canvasY }  
  94.            }  
  95.            HTMLCanvasElement.prototype.relMouseCoords = relMouseCoords;   
  96.          
  97.     </script>  
  98. </head>  
  99. <body>  
  100.     <div id='params'>  
  101.         <button onclick="clearGame()">Clear</button>  
  102.         <button onclick="advance()" >Next</button>  
  103.         <button id="btnAnimate" onclick="animate()">Animate</button>  
  104.         <select id="color_menu0" name="color_menu0"  style="width: 60px">  
  105.             <option style="background-color:#00ced1" value="#00ced1" selected="selected"/>  
  106.             <option style="background-color:#ff8c70" value="#ff8c70"/>  
  107.             <option style="background-color:#008b8b" value="#008b8b"/>  
  108.             <option style="background-color:#ff1493" value="#ff1493"/>  
  109.         </select>  
  110.         <span id="generation" style="width: 130">Generation: 0</span>  
  111.         <span id="population" style="width: 130">Population: 0</span>  
  112.     </div>  
  113.     <div style="position:relative">  
  114.         <canvas id='canvas2' width='641' height='641' on></canvas>  
  115.         <canvas id='canvas1' width='641' height='641' on>Canvas is not supported by this browser.</canvas>  
  116.     </div>  
  117.     <script type="text/javascript" >  
  118.     // Keep a torus for the current and next generation  
  119.     var _size = 64;  
  120.     var _cellSize = 10;  
  121.     var _torus1 = new Life(_size);       
  122.     var _torus2 = new Life(_size);       
  123.     var _animate = false;  
  124.     var _generation = 0;  
  125.     var isMouseDown = false;  
  126.     function clearGame() {  
  127.         _torus1.clear();  
  128.         _generation = 0;  
  129.         generation.textContent = "Generation: 0";  
  130.         render();  
  131.         updatePopulation();  
  132.     }  
  133.     function animate() {  
  134.         _animate = !_animate;  
  135.         if (_animate) {  
  136.             advance();  
  137.             btnAnimate.textContent = "Stop";  
  138.         } else {  
  139.             btnAnimate.textContent = "Animate";  
  140.         }  
  141.     }  
  142.     function advance() {  
  143.        var _population = 0;  
  144.         for (var x = 0; x < _size; x++)  
  145.             for (var y = 0; y < _size; y++) {  
  146.                 var neighbours = _torus1.getNeighbours(x, y); // dim 5 array  
  147.                 var alive = 0;  
  148.                 var kind = _torus1.get(x, y);  
  149.                 if (kind > 0) {  
  150.                     // its alive and  stay alive if it has 2 or 3 neighbours  
  151.                     var count = neighbours[kind];  
  152.                     alive = (count == 2 || count == 3) ? kind : 0;  
  153.                 }  
  154.                 else {  
  155.                     // Its dead but will be born if any "kind" has exactly 3 neighbours  
  156.                     // This isn't "fair" but we use the first kind that has three neightbours  
  157.                     for (kind = 1; kind <= 4 && alive == 0; kind++) {  
  158.                         if (neighbours[kind] == 3)  
  159.                             alive = kind;  
  160.                     }  
  161.                 }  
  162.                 _torus2.set(x, y, alive);  
  163.                 if (alive)  
  164.                     _population++;  
  165.             }  
  166.         var temp = _torus1; // arrays are only references!  
  167.         _torus1 = _torus2;  
  168.         _torus2 = temp;  
  169.         render();  
  170.         generation.textContent = "Generation: " + String(++_generation);  
  171.         population.textContent = "Population: " + String(_population);  
  172.         if (_animate)  
  173.             setTimeout("advance()", 50);  
  174.     }  
  175.     function renderCanvas(canvas, size, torus) {  
  176.         // read from Life and write to canvas                
  177.         var context = canvas.getContext('2d');  
  178.         context.fillStyle = '#ff7f50';  
  179.         context.clearRect(0, 0, size * _cellSize, size * _cellSize);  
  180.         for (var x = 0; x < size; x++)  
  181.             for (var y = 0; y < size; y++) {  
  182.                 var kind = _torus1.get(x, y) - 1;  
  183.                 if (kind >= 0) {  
  184.                     context.fillStyle = color_menu0.options[kind].value;  
  185.                     context.fillRect(x * _cellSize, y * _cellSize, _cellSize, _cellSize);  
  186.                 }  
  187.             }  
  188.     }  
  189.     function render() {  
  190.         renderCanvas(canvas1, _size, _torus1);  
  191.     }  
  192.     function drawGrid() {  
  193.         // Only ever called once!  
  194.         var context = canvas2.getContext('2d'); // canvas2 is the background canvas  
  195.         context.strokeStyle = '#808080';  
  196.         context.beginPath();  
  197.         for (var i = 0; i <= _size; i++) {  
  198.             // Draw vertical lines  
  199.             context.moveTo(i * _cellSize + 0.5, 0.5);  
  200.             context.lineTo(i * _cellSize + 0.5, _size * _cellSize);  
  201.             // Draw horizontal lines  
  202.             context.moveTo(0.5, i * _cellSize + 0.5);  
  203.             context.lineTo(_size * _cellSize, i * _cellSize + 0.5);  
  204.         }  
  205.         context.stroke();  
  206.     }  
  207.     drawGrid();  
  208.     canvas1.onmousedown = function canvasMouseDown(ev) {  
  209.         isMouseDown = true;  
  210.         var x = ev.pageX - this.offsetLeft;  
  211.         var y = ev.pageY - this.offsetTop;  
  212.         var coords = this.relMouseCoords(ev);  
  213.         setPoint(coords.x, coords.y);  
  214.     }  
  215.     canvas1.onmouseup = function canvasMouseDown(ev) {  
  216.         isMouseDown = false;  
  217.     }  
  218.     canvas1.onmousemove = function canvasMouseDown(ev) {  
  219.         if (isMouseDown) {  
  220.             var coords = this.relMouseCoords(ev);  
  221.             setPoint(coords.x, coords.y);  
  222.         }  
  223.     }  
  224.     function setPoint(x, y) {  
  225.         // convert to torus coords  
  226.         var i = Math.floor(x / _cellSize);  
  227.         var j = Math.floor(y / _cellSize);  
  228.         // Which kind  
  229.         var kind = 1 + color_menu0.selectedIndex;  
  230.         _torus1.set(i, j, kind);  
  231.         render();  
  232.         updatePopulation();  
  233.     }  
  234.     function updatePopulation() {  
  235.         var _population = 0;  
  236.         for (var x = 0; x < _size; x++)  
  237.             for (var y = 0; y < _size; y++) {  
  238.                 if (_torus1.get(x, y))  
  239.                     _population++;  
  240.             }  
  241.         population.textContent = "Population: " + String(_population);  
  242.     }  
  243.   
  244.     </script>  
  245. </body>  
Our output looks like this:
 
a1.jpg
 
a2.jpg