Skip to content

Instantly share code, notes, and snippets.

@neoascetic
Last active August 11, 2016 21:35
Show Gist options
  • Save neoascetic/3ac49306eacd98b47c41 to your computer and use it in GitHub Desktop.
Save neoascetic/3ac49306eacd98b47c41 to your computer and use it in GitHub Desktop.
'use strict';
var GoL = function () {
function touch(ctx, x, y, alive) {
ctx[alive ? 'fillRect' : 'clearRect'](x * 11, y * 11, 10, 10);
return alive;
}
function renderDiff(ctx, diff) {
for (var d of diff) touch(ctx, d[0], d[1], d[2]);
}
function calcDiff(world, diff, callback) {
var newdiff = [], checked = new Set;
for (var d of diff) {
for (var n of neighbors(d[0], d[1], world.length, world[0].length)) {
var x = n[0], y = n[1];
if (!checked.has(x + ':' + y)) {
var s = callback(world, x, y);
if (s !== world[y][x]) {
newdiff[newdiff.length] = [x, y, s];
}
checked.add(x + ':' + y);
}
}
}
return newdiff;
}
function applyDiff(world, diff) {
for (var d of diff) world[d[1]][d[0]] = d[2];
return world;
}
function willSurvive(world, x, y) {
var sum = 0,
n = neighbors(x, y, world.length, world[0].length);
for (var nl = 0; nl < n.length; nl++) {
sum += world[n[nl][1]][n[nl][0]];
}
return sum !== 4 ? sum === 3 : world[y][x];
}
function neighbors(x, y, height, width) {
var n = [];
for (var i = -1; i <= 1; i++) {
for (var j = -1; j <= 1; j++) {
n[n.length] = [(width + i + x) % width, (height + j + y) % height];
}
}
return n;
}
function createLife(world) {
var d = [];
for (var y = 0; y < world.length; y++) {
for (var x = 0; x < world[y].length; x++) {
if (!Math.floor(Math.random() * 10)) {
d[d.length] = [x, y, true];
}
}
}
return d;
}
function updateSize(w, h, c, ctx, color, world, diff) {
var newW = w - w % 11, newH = h - h % 11,
wHeight = Math.floor(h / 11), wWidth = Math.floor(w / 11);
c.width = newW;
c.height = newH;
ctx.fillStyle = color;
world = world.slice(0, wHeight);
diff = diff.filter(function(d) { return d[1] < wHeight && d[0] < wWidth; });
for (var y = 0; y < wHeight; y++) {
if (!world[y]) {
world[y] = new Array(wWidth).fill(false);
} else if (world[y].length > wWidth) {
world[y] = world[y].slice(0, wWidth);
} else {
world[y] = world[y].concat(new Array(wWidth - world[y].length).fill(false));
}
for (var x = 0; x < world[y].length; x++) {
// mark borders as changed
if (y === 0 || y === world.length - 1 || x === 0 || x === world[y].length - 1) {
diff[diff.length] = [x, y, world[y][x]];
}
if (world[y][x]) {
touch(ctx, x, y, world[y][x]);
}
}
}
return [world, diff];
}
return function (el, config) {
var color = (config && config.color) || 'lightgreen',
step = (config && config.step) || 1000,
c = document.createElement('canvas'),
ctx = c.getContext('2d');
c.style.cssText = 'position: absolute';
if (window.getComputedStyle(el).position === 'static') {
el.style.position = 'relative';
}
el.insertBefore(c, el.firstChild);
var [world, diff] = updateSize(el.clientWidth, el.clientHeight, c, ctx, color, [], []);
(function timeout(initDiff) {
diff = initDiff || calcDiff(world, diff, willSurvive);
world = applyDiff(world, diff);
renderDiff(ctx, diff);
setTimeout(timeout, step);
})(createLife(world));
var prevW = el.clientWidth,
prevH = el.clientHeight;
requestAnimationFrame(function frame() {
if (el.clientWidth !== prevW || el.clientHeight !== prevH) {
[world, diff] = updateSize(el.clientWidth, el.clientHeight, c, ctx, color, world, diff);
prevW = el.clientWidth;
prevH = el.clientHeight;
}
requestAnimationFrame(frame);
});
c.addEventListener('click', function (e) {
var x = Math.floor(e.offsetX / 11),
y = Math.floor(e.offsetY / 11);
diff[diff.length] = [x, y, !world[y][x]]
world[y][x] = touch(ctx, x, y, !world[y][x]);
});
}
}();
<!DOCTYPE html>
<title>Game of Life</title>
<body>
<style>
html, body { margin: 0; padding: 0; height: 100%; }
</style>
<script src="./gol.js"></script>
<script>
GoL(document.body, { color: 'lightblue', step: 500 });
GoL(document.body, { color: 'lightgrey' });
GoL(document.body, { step: 100 });
</script>
</body>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment