This is my implementation of Conway's Game of Life in JavaScript. While I know that it runs slowly, I do not ask for performance improvements, but for a review on the code quality in general from professional JavaScript programmers.
Working HTML/CSS/JS files can be found here.
Here is the main part of the code:
(function(){
'use strict';
function getControl(api){
var buttonsControll = document.getElementsByTagName('canvas'),
buttonsRuleAlive = document.querySelectorAll('.btn_ruleAlive'),
buttonsRuleBorn = document.querySelectorAll('.btn_ruleBorn'),
button;
function drawRectangle(canvas, x){
canvas.fillRect(x, 1, 5, 10);
}
function drawTriangle(canvas, x, direction){
canvas.beginPath();
canvas.moveTo(x, 1);
canvas.lineTo(x, 11);
canvas.lineTo(x + 10 * direction, 6);
canvas.lineTo(x, 1);
canvas.fill();
}
function getIntervalTime(){
return parseInt(document.getElementById('tb_msPerFrame').value);
}
function getFields(){
return parseInt(document.getElementById('tb_fields').value);
}
function setNeighbors(){
var neighbors = parseInt(document.getElementById('sl_neighbors').value);
for(var i = 0; i < buttonsRuleAlive.length; i++){
if(i <= neighbors){
if(buttonsRuleAlive[i].className == 'btn_rule btn_ruleAlive btn_none'){
buttonsRuleAlive[i].className = 'btn_rule btn_ruleAlive btn_dieing';
buttonsRuleBorn[i].className = 'btn_rule btn_ruleBorn btn_dieing';
}
}
else{
buttonsRuleAlive[i].className = 'btn_rule btn_ruleAlive btn_none';
buttonsRuleBorn[i].className = 'btn_rule btn_ruleBorn btn_none';
}
}
api.setNeighbors(neighbors);
}
function usePreset(){
var preset = document.getElementById('sl_presets').value,
indexOf = preset.indexOf('/');
for(var i = 0; i < buttonsRuleAlive.length; i++){
setRulesAlive(i, 0);
setRulesBorn(i, 0);
}
for(var i in preset){
if(i < indexOf){
setRulesAlive(preset[i], 1);
}
else if(i > indexOf){
setRulesBorn(preset[i], 1);
}
}
}
function setRulesAlive(i, rule){
var ruleName = ['dieing', 'alive'];
if(buttonsRuleAlive[i].className != 'btn_rule btn_ruleAlive btn_none'){
buttonsRuleAlive[i].className = 'btn_rule btn_ruleAlive btn_' + ruleName[rule];
}
api.setRulesAlive(i, rule);
}
function setRulesBorn(i, rule){
var ruleName = ['dieing', 'born'];
if(buttonsRuleBorn[i].className != 'btn_rule btn_ruleBorn btn_none'){
buttonsRuleBorn[i].className = 'btn_rule btn_ruleBorn btn_' + ruleName[rule];
}
api.setRulesBorn(i, rule);
}
function setColorful(){
api.setColorful(document.getElementById('cb_colorful').checked);
}
function setEndless(){
api.setEndless(document.getElementById('cb_endless').checked);
}
for(i = 0; i < buttonsControll.length; i++){
buttonsControll[i].height = parseInt(buttonsControll[i].parentNode.scrollHeight) - 11;
buttonsControll[i].width = parseInt(buttonsControll[i].parentNode.scrollWidth) - 11;
}
button = document.getElementById('btn_fastRewind');
button.onclick = function(){
api.fastRewind();
}
buttonsControll = document.getElementById('canvas_fastRewind').getContext('2d');
drawRectangle(buttonsControll, 5);
drawTriangle(buttonsControll, 20, -1);
drawTriangle(buttonsControll, 30, -1);
button = document.getElementById('btn_rewind');
button.onclick = function(){
api.rewind();
}
buttonsControll = document.getElementById('canvas_rewind').getContext('2d');
drawTriangle(buttonsControll, 17, -1);
drawTriangle(buttonsControll, 27, -1);
button = document.getElementById('btn_lastFrame');
button.onclick = function(){
api.lastFrame();
}
buttonsControll = document.getElementById('canvas_lastFrame').getContext('2d');
drawTriangle(buttonsControll, 18, -1);
drawRectangle(buttonsControll, 21);
button = document.getElementById('btn_play');
button.onclick = function(){
api.play();
}
buttonsControll = document.getElementById('canvas_play').getContext('2d');
drawTriangle(buttonsControll, 13, 1);
button = document.getElementById('btn_pause');
button.style.display = 'none';
button.onclick = function(){
api.pause();
}
buttonsControll = document.getElementById('canvas_pause').getContext('2d');
drawRectangle(buttonsControll, 11);
drawRectangle(buttonsControll, 19);
button = document.getElementById('btn_nextFrame');
button.onclick = function(){
api.nextFrame();
}
buttonsControll = document.getElementById('canvas_nextFrame').getContext('2d');
drawRectangle(buttonsControll, 9);
drawTriangle(buttonsControll, 17, 1);
button = document.getElementById('btn_forward');
button.onclick = function(){
api.forward();
}
buttonsControll = document.getElementById('canvas_forward').getContext('2d');
drawTriangle(buttonsControll, 8, 1);
drawTriangle(buttonsControll, 18, 1);
button = document.getElementById('btn_fastForward');
button.onclick = function(){
api.fastForward();
}
buttonsControll = document.getElementById('canvas_fastForward').getContext('2d');
drawTriangle(buttonsControll, 5, 1);
drawTriangle(buttonsControll, 15, 1);
drawRectangle(buttonsControll, 25);
button = document.getElementById('btn_stop');
button.onclick = function(){
api.stop();
}
buttonsControll = document.getElementById('canvas_stop').getContext('2d');
buttonsControll.fillRect(13, 1, 10, 10);
document.getElementById('btn_msPerFrame').onclick = function(){
api.setIntervalTime(getIntervalTime());
}
document.getElementById('btn_fields').onclick = function(){
api.setFields(getFields());
}
document.getElementById('sl_neighbors').onchange = function(){
setNeighbors();
}
document.getElementById('sl_presets').onchange = function(){
usePreset();
}
for(var i in buttonsRuleAlive){
buttonsRuleAlive[i].onclick = (function(i){
return function(){
if(this.className == 'btn_rule btn_ruleAlive btn_dieing'){
this.className = 'btn_rule btn_ruleAlive btn_alive';
setRulesAlive(i, 1);
}
else if(this.className == 'btn_rule btn_ruleAlive btn_alive'){
this.className = 'btn_rule btn_ruleAlive btn_dieing';
setRulesAlive(i, 0);
}
}
}(i));
}
for(var i in buttonsRuleBorn){
buttonsRuleBorn[i].onclick = (function(i){
return function(){
if(this.className == 'btn_rule btn_ruleBorn btn_dieing'){
this.className = 'btn_rule btn_ruleBorn btn_born';
setRulesBorn(i, 1);
}
else if(this.className == 'btn_rule btn_ruleBorn btn_born'){
this.className = 'btn_rule btn_ruleBorn btn_dieing';
setRulesBorn(i, 0);
}
}
}(i));
}
document.getElementById('cb_colorful').onclick = setColorful;
document.getElementById('cb_endless').onclick = setEndless;
setColorful();
setEndless();
setNeighbors();
usePreset();
api.setIntervalTime(getIntervalTime());
api.setFields(getFields());
}
function constructCanvas(){
var cells = [[]],
canvas = document.getElementById('canvas'),
cellWidth = 16,
cellSpacing = 4,
colors,
colors0 = ['white', 'white', 'red', 'yellow', 'green'],
colors1 = ['white', 'white', 'white', 'black', 'black'],
ctx = canvas.getContext('2d'),
endless,
fields,
hist = [],
interval,
intervalTime,
module,
rulesAlive = [],
rulesBorn = [],
speed;
function constructHelperSquareTiles(){
var ratio = 2;
function getOffset(x){
return x * (cellWidth + cellSpacing);
}
return {
clearCanvas: function(){
for(var i = 1, w = 1, h = 1, x = 0, y = 0; i <= fields; i++){
cells[x][y] = emptyCell(x, y);
if(y + 1 == h && i >= Math.round(ratio * h * (h + 1))){
h++;
x = 0;
y++;
}
else if(y + 1 < h){
y++;
}
else{
x++;
if(i >= h * w){
cells[x] = [];
w++;
y = 0;
}
}
}
},
drawCanvas: function(){
var x = (function(){
if(fields == 2){
return 2;
}
for(var i = 0; Math.floor(1 / ratio * Math.pow(i, 2) - i + 1 - getProperModulo(i + ratio / 2, ratio) * (i / ratio - 1)) <= fields; i++);
return i - 1;
}()),
y = Math.round(Math.sqrt(fields / ratio));
return [getOffset(x) - cellSpacing + 3, getOffset(y) - cellSpacing + 3];
},
getOffset: getOffset
}
}
function constructModule3(){
var cellHeight = getPythagorasLeg(cellWidth, cellWidth / 2),
cellSpacingHeight = getPythagorasLeg(cellSpacing, cellSpacing / 2);
function getOffsetX(x){
var w = Math.floor(x / 4) * (3 * cellWidth + 3 * cellSpacing) + x % 2 * (cellWidth + cellSpacing);
if(x % 4 == 2 || x % 4 == 3){
w += 1.5 * cellWidth + 1.5 * cellSpacing;
}
return w;
}
function getOffsetY(y){
return y * (cellHeight + cellSpacingHeight);
}
return {
clearCanvas: function(){
for(var i = 0, w = 1, h = 0, x = 0, y = 0; i < fields; i++){
cells[x][y] = emptyCell(x, y);
if(x % 2 == 0){
x++;
if(!cells[x]){
cells[x] = [];
}
}
else{
if(x == w && y == h){
w += 2;
h++;
x++;
y = y % 2 == 0
? 1
: 0;
cells[x] = [];
}
else if(y < h){
x--;
y += 2;
if(y == h){
x = y % 2 == 0
? 0
: 2;
}
}
else{
x += 3;
}
}
}
},
drawCanvas: function(){
var x = (function(){
for(var i = 0, n = 0, x = 0; x < fields; i++){
if((i + 1) % 2 == 0 && i > 6){
n += Math.ceil((i - 6) / 4) * 4;
}
x = i + n;
}
return (x == fields)
? i - 2
: i - 3;
}()),
y = (function(){
for(var i = 1, x = 0; x < fields; i++){
x = Math.pow(i, 2) - i + 1;
}
return (x == fields)
? i - 2
: i - 3;
}());
return [getOffsetX(x) + cellWidth + 3, getOffsetY(y) + cellWidth + 3];
},
drawCell: function(x, y){
x = getOffsetX(x) + 1;
y = getOffsetY(y) + 1;
ctx.arc(x + cellWidth / 2, y + cellWidth / 2, cellWidth / 2, 0 , 2 * Math.PI);
},
getNeighbors: function(x, y){
var neighbors = 0;
if(x % 2 == 0){
if(cells[x - 1]){
neighbors += isAliveOrBorn(cells[x - 1][y - 1]);
neighbors += isAliveOrBorn(cells[x - 1][y + 1]);
}
if(cells[x + 1]){
neighbors += isAliveOrBorn(cells[x + 1][y]);
}
}
else{
if(cells[x - 1]){
neighbors += isAliveOrBorn(cells[x - 1][y]);
}
if(cells[x + 1]){
neighbors += isAliveOrBorn(cells[x + 1][y - 1]);
neighbors += isAliveOrBorn(cells[x + 1][y + 1]);
}
}
return neighbors;
},
selectCell: function(x, y){
var h = Math.floor(y / (cellHeight + cellSpacingHeight)),
w = h % 2 == 0
? x / (cellWidth + cellSpacing)
: (x + cellWidth / 2 + cellSpacing / 2) / (cellWidth + cellSpacing),
w = Math.floor(Math.floor(w) * 4 / 3);
if(h % 2 == 1 && w % 4 == 0){
w--;
}
if(getDistance(x, getOffsetX(w) + cellWidth / 2 + 1, y, getOffsetY(h) + cellWidth / 2 +1) < cellWidth / 2){
return [w, h];
}
return true;
}
}
}
function constructModule4(){
var helper = constructHelperSquareTiles();
return {
clearCanvas: helper.clearCanvas,
drawCanvas: helper.drawCanvas,
drawCell: function(x, y){
x = helper.getOffset(x) + 1;
y = helper.getOffset(y) + 1;
ctx.moveTo(x + cellWidth * .25, y);
ctx.lineTo(x + cellWidth * .75, y);
ctx.lineTo(x + cellWidth * .75, y + cellWidth * .25);
ctx.lineTo(x + cellWidth, y + cellWidth * .25);
ctx.lineTo(x + cellWidth, y + cellWidth * .75);
ctx.lineTo(x + cellWidth * .75, y + cellWidth * .75);
ctx.lineTo(x + cellWidth * .75, y + cellWidth);
ctx.lineTo(x + cellWidth * .25, y + cellWidth);
ctx.lineTo(x + cellWidth * .25, y + cellWidth * .75);
ctx.lineTo(x, y + cellWidth * .75);
ctx.lineTo(x, y + cellWidth * .25);
ctx.lineTo(x + cellWidth * .25, y + cellWidth * .25);
ctx.lineTo(x + cellWidth * .25, y - 1);
},
getNeighbors: function(x, y){
var neighbors = 0;
neighbors += isAliveOrBorn(cells[x][y - 1]);
neighbors += isAliveOrBorn(cells[x][y + 1]);
if(cells[x - 1]){
neighbors += isAliveOrBorn(cells[x - 1][y]);
}
if(cells[x + 1]){
neighbors += isAliveOrBorn(cells[x + 1][y]);
}
return neighbors;
},
selectCell: function(x, y){
var w = Math.floor(x / (cellWidth + cellSpacing)),
h = Math.floor(y / (cellWidth + cellSpacing));
if((isInsideAngle(helper.getOffset(w) + cellWidth * .25, helper.getOffset(h), x, y, 1.5, 2) == 0 && isInsideAngle(helper.getOffset(w) + cellWidth * .75, helper.getOffset(h) + cellWidth, x, y, .5, 1) == 0) || (isInsideAngle(helper.getOffset(w), helper.getOffset(h) + cellWidth * .25, x, y, 1.5, 2) == 0 && isInsideAngle(helper.getOffset(w) + cellWidth, helper.getOffset(h) + cellWidth * .75, x, y, .5, 1) == 0)){
return [w, h];
}
return true;
}
}
}
function constructModule6(){
var cellSize = cellWidth * 1.25,
cellHeight = getPythagorasLeg(cellSize, cellSize / 2),
cellSpacingHeight = getPythagorasLeg(cellSpacing, cellSpacing / 2),
offsetX = [cellSize / 4 + 1, cellSize + cellSpacingHeight + 1],
offsetY = [1, cellHeight / 2 + cellSpacing / 2 + 1];
function getOffsetX(x){
return Math.floor(x / 2) * (cellSize * 1.5 + cellSpacingHeight * 2);
}
function getOffsetY(y){
return Math.floor(y / 2) * (cellHeight + cellSpacing);
}
return {
clearCanvas: function(){
for(var i = 0, x = 0, y = 0; i < fields; i++){
cells[x][y] = emptyCell(x, y);
if(x == y){
x++;
y = y % 2 == 0
? 1
: 0;
cells[x] = [];
}
else if(y + 2 < x){
y += 2;
}
else if(y + 2 == x){
x = x % 2 == 0
? 0
: 1;
y += 2;
}
else{
x += 2;
}
}
},
drawCanvas: function(){
var x = (function(){
for(var i = 0; Math.ceil(Math.pow(i, 2) / 2 + 1) <= fields; i++);
return i;
}()),
y = (function(){
return fields < (Math.pow(x, 2) - x) / 2 + 1
? x - 1
: x;
}());
return [getOffsetX(x) + x % 2 * (cellSize + cellSpacingHeight) + (x + 1) % 2 * cellSize / 4 + 3, getOffsetY(y) + y % 2 * cellHeight + (y + 1) % 2 * (cellHeight / 2 - cellSpacing / 2) + 3];
},
drawCell: function(x, y){
x = getOffsetX(x) + offsetX[x % 2];
y = getOffsetY(y) + offsetY[y % 2];
ctx.moveTo(x, y);
ctx.lineTo(x + cellSize / 2, y);
ctx.lineTo(x + cellSize * .75, y + cellHeight / 2);
ctx.lineTo(x + cellSize / 2, y + cellHeight);
ctx.lineTo(x, y + cellHeight);
ctx.lineTo(x - cellSize / 4, y + cellHeight / 2);
ctx.lineTo(x, y);
},
getNeighbors: function(x, y){
var neighbors = 0;
neighbors += isAliveOrBorn(cells[x][y - 2]);
neighbors += isAliveOrBorn(cells[x][y + 2]);
if(cells[x - 1]){
neighbors += isAliveOrBorn(cells[x - 1][y - 1]);
neighbors += isAliveOrBorn(cells[x - 1][y + 1]);
}
if(cells[x + 1]){
neighbors += isAliveOrBorn(cells[x + 1][y - 1]);
neighbors += isAliveOrBorn(cells[x + 1][y + 1]);
}
return neighbors;
},
selectCell: function(x, y){
var w = Math.floor(x * 2 / (cellSize * 1.5 + cellSpacingHeight * 2)),
h = y / (cellHeight + cellSpacing),
h = w % 2 == 0
? Math.floor(h) * 2
: Math.floor(h - .5) * 2 + 1,
offsetX2 = getOffsetX(w) + w % 2 * (cellSize * .75 + cellSpacingHeight) + 1,
offsetY2 = getOffsetY(h) + h % 2 * (cellHeight / 2 + cellSpacing / 2) + 1,
isInside = isInsideAngle(offsetX2, offsetY2 + cellHeight / 2, x, y, 5 / 3, 7 / 3);
if(isInside == 0 && (isInsideAngle(offsetX2 + cellSize * .75, offsetY2, x, y, 1, 5 / 3) != 0 || isInsideAngle(offsetX2 + cellSize * .75, offsetY2 + cellHeight, x, y, 1 / 3, 1) != 0)){
return true;
}
else if(isInside == 1 && isInsideAngle(offsetX2 - cellSize * .75 - cellSpacingHeight, offsetY2 - cellSpacing / 2, x, y, 5 / 3, 1 / 3) == 0 && isInsideAngle(offsetX2 - cellSpacingHeight, offsetY2 - cellHeight / 2 - cellSpacing / 2, x, y, 1, 5 / 3) == 0 && isInsideAngle(offsetX2 - cellSpacingHeight, offsetY2 + cellHeight / 2 - cellSpacing / 2, x, y, 1 / 3, 1) == 0){
w -= 1;
h -= 1;
}
else if(isInside == -1 && isInsideAngle(offsetX2 - cellSize * .75 - cellSpacingHeight, offsetY2 + cellHeight + cellSpacing / 2, x, y, 5 / 3, 1 / 3) == 0 && isInsideAngle(offsetX2 - cellSpacingHeight, offsetY2 + cellHeight / 2 + cellSpacing / 2, x, y, 1, 5 / 3) == 0 && isInsideAngle(offsetX2 - cellSpacingHeight, offsetY2 + cellHeight * 1.5 + cellSpacing / 2, x, y, 1 / 3, 1) == 0){
w -= 1;
h += 1;
}
return [w, h];
}
}
}
function constructModule7(){
var helper = constructHelperSquareTiles(),
ratio = 2;
function getOffsetX(x, y){
return helper.getOffset(x) + (y % 4 == 2 || y % 4 == 3) * (cellWidth / 2 + cellSpacing / 2);
}
function getOffsetY(y){
return helper.getOffset(y);
}
return {
clearCanvas: helper.clearCanvas,
drawCanvas: function(){
var x = (function(){
if(fields == 2){
return 2;
}
for(var i = 0; Math.floor(1 / ratio * Math.pow(i, 2) - i + 1 - getProperModulo(i + ratio / 2, ratio) * (i / ratio - 1)) <= fields; i++);
return i - 1;
}()),
y = Math.round(Math.sqrt(fields / ratio));
return [getOffsetX(x) - cellSpacing + (y > 2) * (cellWidth / 2 + cellSpacing / 2) + 3, getOffsetY(y) - cellSpacing + 3];
},
drawCell: function(x, y){
x = getOffsetX(x, y) + 1;
y = getOffsetY(y) + 1;
ctx.moveTo(x, y);
ctx.lineTo(x + cellWidth, y);
ctx.lineTo(x + cellWidth, y + cellWidth);
ctx.lineTo(x, y + cellWidth);
ctx.lineTo(x, y - 1);
},
getNeighbors: function(x, y){
var neighbors = 0;
neighbors += isAliveOrBorn(cells[x][y - 1]);
neighbors += isAliveOrBorn(cells[x][y + 1]);
if(cells[x - 1]){
neighbors += isAliveOrBorn(cells[x - 1][y]);
if(y % 4 == 0 || y % 4 == 1 || y % 4 == 3){
neighbors += isAliveOrBorn(cells[x - 1][y - 1]);
}
if(y % 4 == 0 || y % 4 == 1 || y % 4 == 2){
neighbors += isAliveOrBorn(cells[x - 1][y + 1]);
}
}
if(cells[x + 1]){
neighbors += isAliveOrBorn(cells[x + 1][y]);
if(y % 4 == 1 || y % 4 == 2 || y % 4 == 3){
neighbors += isAliveOrBorn(cells[x + 1][y - 1]);
}
if(y % 4 == 0 || y % 4 == 2 || y % 4 == 3){
neighbors += isAliveOrBorn(cells[x + 1][y + 1]);
}
}
return neighbors;
},
selectCell: function(x, y){
var h = Math.floor(y / (cellWidth + cellSpacing)),
w = Math.floor((x - (h % 4 == 2 || h % 4 == 3) * (cellWidth / 2 + cellSpacing / 2)) / (cellWidth + cellSpacing));
if(getOffsetX(w, h) + cellWidth < x || getOffsetY(h) + cellWidth < y){
return true;
}
return [w, h];
}
}
}
function constructModule8(){
var helper = constructHelperSquareTiles();
return {
clearCanvas: helper.clearCanvas,
drawCanvas: helper.drawCanvas,
drawCell: function(x, y){
x = helper.getOffset(x) + 1;
y = helper.getOffset(y) + 1;
ctx.moveTo(x, y);
ctx.lineTo(x + cellWidth, y);
ctx.lineTo(x + cellWidth, y + cellWidth);
ctx.lineTo(x, y + cellWidth);
ctx.lineTo(x, y - 1);
},
getNeighbors: function(x, y){
var neighbors = 0;
neighbors += isAliveOrBorn(cells[x][y - 1]);
neighbors += isAliveOrBorn(cells[x][y + 1]);
if(cells[x - 1]){
neighbors += isAliveOrBorn(cells[x - 1][y - 1]);
neighbors += isAliveOrBorn(cells[x - 1][y]);
neighbors += isAliveOrBorn(cells[x - 1][y + 1]);
}
if(cells[x + 1]){
neighbors += isAliveOrBorn(cells[x + 1][y - 1]);
neighbors += isAliveOrBorn(cells[x + 1][y]);
neighbors += isAliveOrBorn(cells[x + 1][y + 1]);
}
return neighbors;
},
selectCell: function(x, y){
var w = Math.floor(x / (cellWidth + cellSpacing)),
h = Math.floor(y / (cellWidth + cellSpacing));
if(helper.getOffset(w) + cellWidth < x || helper.getOffset(h) + cellWidth < y){
return true;
}
return [w, h];
}
}
}
function isAliveOrBorn(n){
return n == 3 || n == 4;
}
function startInterval(func, s){
speed = s;
stopInterval();
displayPause();
interval = setInterval(func, intervalTime / speed);
}
function stopInterval(){
if(interval != null){
clearInterval(interval);
displayPlay();
interval = null;
}
}
function forward(){
var nextFrame = [],
neighbors, n, x, y;
for(x in cells){
nextFrame[x] = [];
for(y in cells[x]){
neighbors = module.getNeighbors(parseInt(x), parseInt(y));
switch(cells[x][y]){
case 0:
if(rulesBorn[neighbors] == 1){
nextFrame[x][y] = bornCell(x, y);
}
else{
nextFrame[x][y] = 0;
}
break;
case 1:
if(rulesBorn[neighbors] == 1){
nextFrame[x][y] = bornCell(x, y);
}
else{
nextFrame[x][y] = 1;
}
break;
case 2:
if(rulesBorn[neighbors] == 1){
nextFrame[x][y] = bornCell(x, y);
}
else{
nextFrame[x][y] = deadCell(x, y);
}
break;
case 3:
if(rulesAlive[neighbors] == 1){
nextFrame[x][y] = aliveCell(x, y);
}
else{
nextFrame[x][y] = dieingCell(x, y);
}
break;
case 4:
if(rulesAlive[neighbors] == 1){
nextFrame[x][y] = aliveCell(x, y);
}
else{
nextFrame[x][y] = dieingCell(x, y);
}
break;
}
}
}
if(isEqual(cells, nextFrame)){
stopInterval();
return;
}
if(!endless){
for(n in hist){
if(isEqual(hist[hist.length - n - 1], nextFrame)){
stopInterval();
return;
}
}
}
hist.push(cells);
cells = nextFrame;
}
function backward(){
if(hist.length > 0){
var lastFrame = hist.pop();
for(var x in lastFrame){
for(var y in lastFrame){
if(lastFrame[x][y] != cells[x][y]){
switch(lastFrame[x][y]){
case 0:
cells[x][y] = emptyCell(x, y);
break;
case 1:
cells[x][y] = deadCell(x, y);
break;
case 2:
cells[x][y] = dieingCell(x, y);
break;
case 3:
cells[x][y] = aliveCell(x, y);
break;
case 4:
cells[x][y] = bornCell(x, y);
break;
}
}
}
}
}
else{
stopInterval();
}
}
function drawCanvas(){
var dim = module.drawCanvas();
cells = [[]];
hist = [];
canvas.width = dim[0];
canvas.height = dim[1];
ctx.clearRect(1, 1, dim[0], dim[1]);
module.clearCanvas();
canvas.onmousedown = function(e){
var fill;
function fillCell(index, fill){
if(index === true){
return true;
}
fill = fill
? 0
: (fill == false
? 3
: cells[index[0]][index[1]]);
if(index[0] in cells && index[1] in cells[index[0]]){
if(fill == 3 || fill == 4){
cells[index[0]][index[1]] = emptyCell(index[0], index[1]);
return false;
}
else{
cells[index[0]][index[1]] = bornCell(index[0], index[1]);
return true;
}
}
}
if(e.button == 0){
stopInterval();
fill = fillCell(module.selectCell(e.pageX - this.offsetLeft, e.pageY - this.offsetTop));
hist = [];
this.onmousemove = function(e){
fillCell(module.selectCell(e.pageX - this.offsetLeft, e.pageY - this.offsetTop), fill);
};
this.onmouseup = function(){
this.onmousemove = null;
};
}
}
}
function drawCell(x, y, fill){
ctx.beginPath();
module.drawCell(x, y);
ctx.fillStyle = fill;
ctx.strokeStyle = 'white';
ctx.lineWidth = 4;
ctx.stroke();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
ctx.fill();
}
function emptyCell(x, y){
drawCell(x, y, colors[0]);
return 0;
}
function deadCell(x, y){
drawCell(x, y, colors[1]);
return 1;
}
function dieingCell(x, y){
drawCell(x, y, colors[2]);
return 2;
}
function aliveCell(x, y){
drawCell(x, y, colors[3]);
return 3;
}
function bornCell(x, y){
drawCell(x, y, colors[4]);
return 4;
}
return {
fastRewind: function(){
startInterval(backward, 16);
},
rewind: function(){
startInterval(backward, 4);
},
lastFrame: function(){
stopInterval();
backward();
},
play: function(){
startInterval(forward, 1);
},
pause: function(){
stopInterval();
},
nextFrame: function(){
stopInterval();
forward();
},
forward: function(){
startInterval(forward, 4);
},
fastForward: function(){
startInterval(forward, 16);
},
stop: function(){
hist = [];
stopInterval();
module.clearCanvas();
},
setColorful: function(bool){
colors = bool
? colors0
: colors1;
for(var x in cells){
for(var y in cells){
switch(cells[x][y]){
case 0:
emptyCell(x, y);
break;
case 1:
deadCell(x, y);
break;
case 2:
dieingCell(x, y);
break;
case 3:
aliveCell(x, y);
break;
case 4:
bornCell(x, y);
break;
}
}
}
},
setEndless: function(bool){
endless = bool;
},
setFields: function(number){
fields = number;
drawCanvas();
},
setIntervalTime: function setIntervalTime(interval){
intervalTime = interval;
},
setNeighbors: function(i){
stopInterval();
switch(i){
case 3:
module = constructModule3();
break;
case 4:
module = constructModule4();
break;
case 6:
module = constructModule6();
break;
case 7:
module = constructModule7();
break;
case 8:
module = constructModule8();
break;
}
drawCanvas();
},
setRulesAlive: function(i, rule){
rulesAlive[i] = rule;
},
setRulesBorn: function(i, rule){
rulesBorn[i] = rule;
}
}
}
window.onload = function(){
getControl(constructCanvas());
}
}());