hexagonal game of life animation in a html5 canvas.
A Pen by Olivier BERNARD on CodePen.
hexagonal game of life animation in a html5 canvas.
A Pen by Olivier BERNARD on CodePen.
<canvas id="animation" height="300px" width="800px"></canvas> |
function start (canvas) { | |
if (!canvas || !canvas.getContext ) return; | |
var context = canvas.getContext('2d'); | |
var grid = new Grid(10); | |
update(); | |
draw(); | |
function update () { | |
grid.update(); | |
setTimeout(update, 1250); | |
} | |
function draw () { | |
context.clearRect(0, 0, canvas.width, canvas.height); | |
context.save(); | |
context.translate(canvas.width / 2, canvas.height / 2); | |
grid.draw(context); | |
context.restore(); | |
requestAnimationFrame(draw); | |
} | |
} | |
var YELLOW = [250, 197, 53]; | |
var BULE = [69, 180, 231]; | |
var GREY = [91, 114, 135]; | |
var GREEN = [154, 202, 73]; | |
var COLORS = [YELLOW, BULE, GREY, GREEN]; | |
function Grid (size) { | |
this.size = size; | |
this.content = []; | |
this.access = {}; | |
this.angle = 0; | |
this.zoom = 1; | |
this.rotationSpeed = Grid.ROTATION_SPEED; | |
this.zoomSpeed = Grid.ZOOM_SPEED; | |
this.init(); | |
} | |
Grid.ZOOM_MIN = 1; | |
Grid.ZOOM_MAX = 3; | |
Grid.ZOOM_SPEED = 0.001; | |
Grid.ROTATION_SPEED = 0.001; | |
Grid.ROTATION_PROB = 0.1; | |
Grid.prototype.init = function () { | |
for (var i = -this.size; i <= this.size; i++) { | |
for (var j = -this.size; j <= this.size; j++) { | |
if (Math.abs(i + j) > this.size) continue; | |
this.getHex(i, j); | |
} | |
} | |
[[0, 0], [0, 1], [1, 0], [1, 1]].forEach(function (coord, i) { | |
var hex = this.getHex.apply(this, coord); | |
hex.on(COLORS[i]); | |
}, this); | |
}; | |
Grid.prototype.update = function (context) { | |
if (Math.random() < Grid.ROTATION_PROB) this.rotationSpeed = -this.rotationSpeed; | |
this.content.forEach(function (hex) { | |
hex.update(); | |
}); | |
}; | |
Grid.prototype.updateRotation = function (context) { | |
this.angle += this.rotationSpeed; | |
}; | |
Grid.prototype.updateZoom = function (context) { | |
this.zoom += this.zoomSpeed; | |
if (this.zoom <= Grid.ZOOM_MIN) this.zoomSpeed = Grid.ZOOM_SPEED; | |
else if (this.zoom >= Grid.ZOOM_MAX) this.zoomSpeed = -Grid.ZOOM_SPEED; | |
}; | |
Grid.prototype.draw = function (context) { | |
this.updateRotation(); | |
this.updateZoom(); | |
context.save(); | |
context.rotate(this.angle); | |
this.content.forEach(function (hex) { | |
hex.draw(context); | |
}); | |
context.restore(); | |
}; | |
Grid.prototype.getHex = function (x, y) { | |
if (Math.abs(x) > this.size || Math.abs(y) > this.size || Math.abs(x + y) > this.size) return; | |
if (!(x in this.access)) this.access[x] = {}; | |
if (!(y in this.access[x])) { | |
var hex = new Hex(this, [x, y]); | |
this.access[x][y] = hex; | |
this.content.push(hex); | |
} | |
return this.access[x][y]; | |
}; | |
function Hex(grid, coord) { | |
this.grid = grid; | |
this.coord = coord; | |
this.rgb = null; | |
this.active = 0; | |
this.opacity = 0; | |
} | |
Hex.SIZE = 10; | |
Hex.FADE_SPEED = 0.01; | |
Hex.MAX_OPACITY = 0.6; | |
Hex.prototype.update = function (context) { | |
var active = this.neighbors().filter(function (hex) { | |
return hex.active; | |
}); | |
if (!this.active && (active.length == 2 || active.length == 3)) { | |
var colors = active.map(function (hex) { | |
return hex.rgb; | |
}); | |
colors = COLORS.filter(function (c) { | |
return !~colors.indexOf(c); | |
}); | |
this.on(colors[Math.floor(Math.random() * colors.length)]); | |
} | |
else if (this.active && active.length != 2 && active.length != 3) { | |
this.off(); | |
} | |
}; | |
Hex.prototype.draw = function (context) { | |
this.fade(); | |
if (!this.opacity) return; | |
var size = this.size(); | |
context.save(); | |
context.translate.apply(context, this.getCenter()); | |
context.beginPath(); | |
context.moveTo.apply(context, this.corner(0)); | |
for (var i = 1; i <= 6; i ++) { | |
context.lineTo.apply(context, this.corner(i)); | |
} | |
context.fillStyle = this.color(); | |
context.shadowColor = this.color(1); | |
context.shadowBlur = size / 2; | |
context.shadowOffsetX = 0; | |
context.shadowOffsetY = 0; | |
context.fill(); | |
context.restore(); | |
}; | |
Hex.prototype.on = function (color) { | |
setTimeout(function () { | |
this.rgb = color; | |
this.active = true; | |
}.bind(this), 0); | |
}; | |
Hex.prototype.off = function () { | |
setTimeout(function () { | |
this.active = false; | |
}.bind(this), 0); | |
}; | |
Hex.prototype.corner = function (i) { | |
var size = this.size() * 2/3; | |
var aDeg = 60 * i; | |
var aRad = aDeg * Math.PI / 180; | |
return [size * Math.cos(aRad), size * Math.sin(aRad)]; | |
}; | |
Hex.prototype.size = function () { | |
return Hex.SIZE * this.grid.zoom; | |
}; | |
Hex.prototype.color = function (opacity) { | |
opacity = opacity || this.opacity; | |
return 'rgba('+this.rgb.join(',')+','+this.opacity+')'; | |
}; | |
Hex.prototype.neighbors = function (i) { | |
var neighbors = [ | |
this.grid.getHex(this.coord[0] + 1, this.coord[1]), | |
this.grid.getHex(this.coord[0] - 1, this.coord[1]), | |
this.grid.getHex(this.coord[0] + 1, this.coord[1] - 1), | |
this.grid.getHex(this.coord[0] - 1, this.coord[1] + 1), | |
this.grid.getHex(this.coord[0], this.coord[1] + 1), | |
this.grid.getHex(this.coord[0], this.coord[1] - 1) | |
]; | |
return neighbors.filter(Boolean); | |
}; | |
Hex.prototype.fade = function () { | |
if (this.active) { | |
this.opacity = Math.min(this.opacity + Hex.FADE_SPEED, Hex.MAX_OPACITY); | |
} | |
else { | |
this.opacity = Math.max(this.opacity - Hex.FADE_SPEED, 0); | |
} | |
}; | |
Hex.prototype.getCenter = function () { | |
var width = Hex.SIZE * 2 * this.grid.zoom; | |
var height = width * Math.sqrt(3) / 2; | |
return [this.coord[0] * width * 3/4, (this.coord[1] + this.coord[0]/2) * height]; | |
}; | |
start(document.getElementById('animation')); | |
body { | |
text-align:center; | |
background-color: #181818; | |
} |