$(document).ready(
function() {
// From JavaScript: The good parts - Chapter 6. Arrays,
// Section 6.7. Dimensions
Array.matrix = function (m, n, initial) {
var a, i, j, mat = [];
for (i = 0; i < m; i += 1) {
a = [];
for (j = 0; j < n; j += 1) {
a[j] = 0;
}
mat[i] = a;
}
return mat;
};
var Life = {};
Life.CELL_SIZE = 8;
Life.X = 320;
Life.Y = 320;
Life.WIDTH = Life.X / Life.CELL_SIZE;
Life.HEIGHT = Life.Y / Life.CELL_SIZE;
Life.DEAD = 0;
Life.ALIVE = 1;
Life.DELAY = 500;
Life.STOPPED = 0;
Life.RUNNING = 1;
Life.minimum = 2;
Life.maximum = 3;
Life.spawn = 3;
Life.state = Life.STOPPED;
Life.interval = null;
Life.grid = Array.matrix(Life.HEIGHT, Life.WIDTH, 0);
Life.counter = 0;
Life.updateState = function() {
var neighbours;
var nextGenerationGrid = Array.matrix(Life.HEIGHT, Life.WIDTH, 0);
for (var h = 0; h < Life.HEIGHT; h++) {
for (var w = 0; w < Life.WIDTH; w++) {
neighbours = Life.calculateNeighbours(h, w);
if (Life.grid[h][w] !== Life.DEAD) {
if ((neighbours >= Life.minimum) &&
(neighbours <= Life.maximum)) {
nextGenerationGrid[h][w] = Life.ALIVE;
}
} else {
if (neighbours === Life.spawn) {
nextGenerationGrid[h][w] = Life.ALIVE;
}
}
}
}
Life.copyGrid(nextGenerationGrid, Life.grid);
Life.counter++;
};
Life.calculateNeighbours = function(y, x) {
var total = (Life.grid[y][x] !== Life.DEAD) ? -1 : 0;
for (var h = -1; h <= 1; h++) {
for (var w = -1; w <= 1; w++) {
if (Life.grid
[(Life.HEIGHT + (y + h)) % Life.HEIGHT]
[(Life.WIDTH + (x + w)) % Life.WIDTH] !== Life.DEAD) {
total++;
}
}
}
return total;
};
Life.copyGrid = function(source, destination) {
for (var h = 0; h < Life.HEIGHT; h++) {
/*
for (var w = 0; w < Life.WIDTH; w++) {
destination[h][w] = source[h][w];
}
*/
destination[h] = source[h].slice(0);
}
};
function Cell(row, column) {
this.row = row;
this.column = column;
};
var gridCanvas = document.getElementById("grid");
var counterSpan = document.getElementById("counter");
var controlLink = document.getElementById("controlLink");
var clearLink = document.getElementById("clearLink");
var minimumSelect = document.getElementById("minimumSelect");
var maximumSelect = document.getElementById("maximumSelect");
var spawnSelect = document.getElementById("spawnSelect");
controlLink.onclick = function() {
switch (Life.state) {
case Life.STOPPED:
Life.interval = setInterval(function() {
update();
}, Life.DELAY);
Life.state = Life.RUNNING;
break;
default:
clearInterval(Life.interval);
Life.state = Life.STOPPED;
}
};
clearLink.onclick = function() {
Life.grid = Array.matrix(Life.HEIGHT, Life.WIDTH, 0);
Life.counter = 0;
clearInterval(Life.interval);
Life.state = Life.STOPPED;
updateAnimations();
}
minimumSelect.onchange = function() {
clearInterval(Life.interval);
Life.state = Life.STOPPED;
Life.minimum = minimumSelect.value;
}
maximumSelect.onchange = function() {
clearInterval(Life.interval);
Life.state = Life.STOPPED;
Life.maximum = maximumSelect.value;
}
spawnSelect.onchange = function() {
clearInterval(Life.interval);
Life.state = Life.STOPPED;
Life.spawn = spawnSelect.value;
}
function update() {
Life.updateState();
//updateInput();
//updateAI();
//updatePhysics();
updateAnimations();
//updateSound();
//updateVideo();
};
function updateAnimations() {
for (var h = 0; h < Life.HEIGHT; h++) {
for (var w = 0; w < Life.WIDTH; w++) {
if (Life.grid[h][w] === Life.ALIVE) {
context.fillStyle = "#000";
} else {
context.fillStyle = "#eee";
//context.clearRect();
}
context.fillRect(
w * Life.CELL_SIZE +1,
h * Life.CELL_SIZE +1,
Life.CELL_SIZE -1,
Life.CELL_SIZE -1);
}
}
counterSpan.innerHTML = Life.counter;
};
if (gridCanvas.getContext) {
var context = gridCanvas.getContext('2d');
var offset = Life.CELL_SIZE;
for (var x = 0; x <= Life.X; x += Life.CELL_SIZE) {
context.moveTo(0.5 + x, 0);
context.lineTo(0.5 + x, Life.Y);
}
for (var y = 0; y <= Life.Y; y += Life.CELL_SIZE) {
context.moveTo(0, 0.5 + y);
context.lineTo(Life.X, 0.5 + y);
}
context.strokeStyle = "#fff";
context.stroke();
function canvasOnClickHandler(event) {
var cell = getCursorPosition(event);
var state = Life.grid[cell.row][cell.column]
== Life.ALIVE ? Life.DEAD : Life.ALIVE;
Life.grid[cell.row][cell.column] = state;
updateAnimations();
};
function getCursorPosition(event) {
var x;
var y;
if (event.pageX || event.pageY) {
x = event.pageX;
y = event.pageY;
} else {
x = event.clientX
+ document.body.scrollLeft
+ document.documentElement.scrollLeft;
y = event.clientY
+ document.body.scrollTop
+ document.documentElement.scrollTop;
}
x -= gridCanvas.offsetLeft;
y -= gridCanvas.offsetTop;
var cell = new Cell(Math.floor((y - 4) / Life.CELL_SIZE),
Math.floor((x - 2) / Life.CELL_SIZE));
return cell;
};
gridCanvas.addEventListener("click", canvasOnClickHandler, false);
} else {
alert("Canvas is unsupported in your browser.");
}
}
);
The state of the code is still in flux, that is, it is still quite rough around the edges and within the next couple of days, I will try to both refactor and tidy it up a bit.
Over the course of the next couple of weeks, I will add the appropriate comments to the Game of Life's JavaScript source.
The code has a bug related to the dynamic updating of the game's behaviour with respect to the 'minimum,' 'maximum,' and 'spawn' variables.
If you have any questions or comments with regards to my JavaScript/Canvas implementation of Conway's Game of Life, just let me know: brettkromkamp@gmail.com.