Search code examples
javascripthtmlconways-game-of-life

Error in Conway's game of life algorithm (a.k.a problems copying two-dimensional arrays)


Spent some time implementing Conways game of life within the HTML canvas using Javascript.

My cells are not acting as expecting. It seems to be picking up more neighboring cells than are actually there, and removing cells which should not be removed. Any help would be appreciated!

//Globals
var currentCells = [
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
[0,0,0,0,0,0,0,0],
];
var currentGrid = currentCells;
var lastGrid = currentCells;

var cellsX = currentCells[1].length;
var cellsY = currentCells.length;

var canvas_id = "gameGrid";
var gridWidth = 100;
var gridHeight = 50;

var c  = document.getElementById("gameGrid");
var ctx = c.getContext("2d");

var cellWidth = gridWidth / cellsX;
var cellHeight = gridHeight / cellsY;
ctx.fillStyle = "yellow";

window.setInterval(step, 1000);



function move(){


for(var a=0;a<cellsX;a++) {

    for(var b=0; b<cellsY;b++) {

        if (a > 0 && b > 0 && a < cellsY - 1 && b < cellsX){ //centre cells only

            var currentNeighbours = neighbourCount(a, b);



            if (lastGrid[a][b] == 0){

                if(currentNeighbours == 3){

                    currentGrid[a][b] = 1;

                }

            }else if (lastGrid[a][b] == 1){

                if(currentNeighbours > 3 || currentNeighbours < 2){

                    currentGrid[a][b] = 0;

                    //console.log("triggered " + currentNeighbours);

                }
            }
        }
    }
}
lastGrid = currentGrid;
}  

function neighbourCount(a, b){

var currentNeighbours = 0;

if(lastGrid[a-1][b] == 1){ //

    currentNeighbours = currentNeighbours + 1;

}
if(lastGrid[a+1][b] == 1){ //

    currentNeighbours = currentNeighbours + 1;

}
if(lastGrid[a][b-1] == 1){ //

    currentNeighbours = currentNeighbours + 1;

}
if(lastGrid[a][b+1] == 1){ //

    currentNeighbours = currentNeighbours + 1;

}
if(lastGrid[a-1][b-1] == 1){ //

    currentNeighbours = currentNeighbours + 1;

}
if(lastGrid[a+1][b+1] == 1){ //

    currentNeighbours = currentNeighbours + 1;

}
if(lastGrid[a-1][b+1] == 1){ //

    currentNeighbours = currentNeighbours + 1;

}
if(lastGrid[a+1][b-1] == 1){ //

    currentNeighbours = currentNeighbours + 1;

}

//console.log(currentNeighbours);

return currentNeighbours;
}


function Draw(){
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
for(var a=0;a<cellsY;a++) {
    var startX = 0;
    for(var b=0; b<cellsX;b++) {

        startX = cellWidth * b;

        if (currentGrid[a][b] == 1){


            ctx.fillRect(startX,(a*cellWidth) ,cellWidth,cellWidth);
        }
        ctx.strokeRect(startX,(a*cellWidth) ,cellWidth,cellWidth);

    }
}
}

function step(){
    move();
    Draw();

}
<canvas id="gameGrid">
</canvas>


Solution

  • The problem here is the lastGrid and currentGrid are both referencing the same array and its sub-arrays. When you coded the following:

    var currentCells = [
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,1,1,1,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    ];
    var currentGrid = currentCells;
    var lastGrid = currentCells;
    

    ...you created only one array with three names referencing it. Therefore, when you're manipulating currentGrid, you're actually also manipulating lastGrid.

    Instead do this:

    var currentCells = [
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,1,1,1,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    [0,0,0,0,0,0,0,0],
    ];
    var currentGrid = currentCells.map(function(arr){return arr.slice()});
    var lastGrid = currentCells.map(function(arr){return arr.slice()});
    

    ...and this as your last line of move():

    lastGrid = currentGrid.map(function(arr){return arr.slice()});
    

    ...because, as described here, the .slice() method creates a new array with the same values and returns the reference to it. The .map() method moves through each of the top-level array items and performs the requested function.

    Normally, to copy a one-dimensional array, you can just use .slice() in this way:

    newArray = oldArray.slice();
    

    But this method won't work on a two-dimensional array because it will just give you a new array full of pointers to the same sub-arrays as those referenced in oldArray.

    Therefore, you have to go through each of the top-level arrays one at a time and .slice() their sub-arrays. This is accomplished by the .map() method.