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:
- 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];
- count[this.get(x - 1, y - 1)]++;
- count[this.get(x, y - 1)]++;
- count[this.get(x + 1, y - 1)]++;
- count[this.get(x - 1, y)]++;
- count[this.get(x + 1, y)]++;
- 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 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
- <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 looks 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];
-
- count[this.get(x - 1, y - 1)]++;
- count[this.get(x, y - 1)]++;
- count[this.get(x + 1, y - 1)]++;
-
- count[this.get(x - 1, y)]++;
- count[this.get(x + 1, y)]++;
-
- 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" >
-
- 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);
- var alive = 0;
- var kind = _torus1.get(x, y);
- if (kind > 0) {
-
- var count = neighbours[kind];
- alive = (count == 2 || count == 3) ? kind : 0;
- }
- else {
-
-
- 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;
- _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) {
-
- 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() {
-
- var context = canvas2.getContext('2d');
- context.strokeStyle = '#808080';
- context.beginPath();
- for (var i = 0; i <= _size; i++) {
-
- context.moveTo(i * _cellSize + 0.5, 0.5);
- context.lineTo(i * _cellSize + 0.5, _size * _cellSize);
-
- 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) {
-
- var i = Math.floor(x / _cellSize);
- var j = Math.floor(y / _cellSize);
-
- 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: