3
\$\begingroup\$

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());
    }
}());
\$\endgroup\$
3
  • \$\begingroup\$ For a start, I'd get rid of the magic number. \$\endgroup\$
    – SylvainD
    Jan 7, 2013 at 21:31
  • \$\begingroup\$ That is correct, but some are obvious to me and some other are just unimportant (wont change, reused, eye candy...). But I guess my problem is that I have a hard time finding variable names for them. Anything else you would suggest? \$\endgroup\$
    – becon999
    Jan 8, 2013 at 13:11
  • 1
    \$\begingroup\$ For starters I would run it through JSHint and abide by some coding standards. It's hard to give any real advice because there's just so much code. I genuinely think you'd learn a lot from re-writing this with an emphasis on reducing the amount of code. \$\endgroup\$ Feb 19, 2013 at 21:48

1 Answer 1

2
\$\begingroup\$

There is a ton here, these are my 2 cents:

  • Read about Model View Controller, and implement it
  • More specifically, knowing the state of a cell by checking the class name is bad form
  • Read about the cool Array functions that exist ( filter, forEach etc. ), they could make a huge difference in your logic code
  • Read about DRY ( Dont Repeat Yourself ), your code should be much DRYer
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.