var width = 960 / 2,
height = 500 / 2,
Conway's Game of Life is a very simple cellular automaton, which is fancy speak for "it's a simple system of rules that treats individual dots kind of like creatures." The creatures are usually called 'cells' and are represented as pixels, so we'll call them 'cells' here, and in this case, one cell is the same as one pixel.
First, some housekeeping: the size of the game. Divide the 'real' size by 2 and then size it up in CSS. Blurrier but faster as a result, since there are fewer pixels to think about.
var width = 960 / 2,
height = 500 / 2,
the colors of living and dead cells: this is in RGBA
space, so (255, 255, 255, 255) is white and (0, 0, 0, 255) is
black. We only set the red
channel of the cells in this case,
so living cells are cyan (0, 255, 255, 255)
and dead cells are white
(255, 255, 255, 255)
alive = 0,
dead = 255,
canvas = document.getElementById('c');
canvas.width = width;
canvas.height = height;
var ctx = canvas.getContext('2d');
seed();
tick();
First, seed the world with random data given to us by Math.random()
function seed() {
.createImageData
asks a drawing context for a new, blank pixel
array. Pixel arrays are simple - they're just really long arrays
of pixel values, like [r, g, b, a, r, g, b, a... ]
, so indexing into
them isn't too tricky.
var after = ctx.createImageData(width, height);
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
for each pixel, color it either alive or dead randomly.
write(after, x, y, (Math.random() > 0.5) ? alive : dead);
}
}
and then 'put' all of those random values into the picture, all at once.
ctx.putImageData(after, 0, 0);
}
The tick
: this is the core of the game - some might also call it a 'step'.
In each tick, we decide whether each cell should live or die, and at the
end of the tick we put that new status, living or dead, into the world.
function tick() {
get both the state now and a new blank canvas to draw the results of our decisions into.
var before = ctx.getImageData(0, 0, width, height),
after = ctx.createImageData(width, height);
for (var x = 0; x < width; x++) {
for (var y = 0; y < height; y++) {
get our decision for whether this x, y
position on the canvas
should live or die.
var state = shouldLive(before, x, y);
write that decision into the image.
write(after, x, y, state ? alive : dead);
}
}
ctx.putImageData(after, 0, 0);
window.setTimeout(tick, 100);
}
function shouldLive(before, x, y) {
get whether this cell is still alive.
var self = cell(before, x, y),
get the number of neighbors still alive
neighbors = neighborsLiving(before, x, y);
We can simplify these rules by only testing for the ones that are true, since if they aren't true, then the answer is false.
return (self && (neighbors === 2 || neighbors === 3) ||
(!self && neighbors === 3));
}
Is a cell at x, y
still alive? This answers with a 1 or a 0. It would
be technically more accurate to say true
or false
, but it's convenient
to use 1 and 0 because...
function cell(before, x, y) {
return (before.data[(x * 4) + (y * width * 4)] === alive) ? 1 : 0;
}
For our computation of living neighbors, we simply add together the values
of calling cell
on every neighbor in each of the 8 directions.
function neighborsLiving(before, x, y) {
return cell(before, x - 1, y) +
cell(before, x + 1, y) +
cell(before, x + 0, y + 1) +
cell(before, x + 0, y - 1) +
cell(before, x + 1, y + 1) +
cell(before, x + 1, y - 1) +
cell(before, x - 1, y + 1) +
cell(before, x - 1, y - 1);
}
Finally, our really simple method to write the result of our decision
for each cell. This doesn't need to return a value, since Javascript
doesn't make a copy of the after
array that we give as an argument -
changes that we make to it are reflected everywhere.
function write(after, x, y, val) {
var index = (x * 4) + (y * width * 4);
after.data[index + 0] = val;
after.data[index + 1] = 255;
after.data[index + 2] = 255;
after.data[index + 3] = 255;
}