Game of Life p5.js trouble updating cells

Viewed 63

I've implemented Conway's game of life in p5.js. I have a function which counts the number of alive neighbours of a given cell, it appears from the tests I did that it works perfectly. That being said I'm having unexpected behaviour when it comes to simulating : it's not following the correct patterns. I start here with 3 aligned squares (a blinker) and it's supposed to rotate but for some reason they just all die.

Wikipedia shows how a blinker should behave : https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life#Examples_of_patterns You can copy paste the source below into https://editor.p5js.org/ and see for yourself.

I'm suspecting the issue has got something to do with copying of the board (matrice of cells), because an update has to be simultaneous for all cells, hence we keep in memory the previous board and update the current board based on the previous board.

Source :

//size of cell in pixels.
const size = 20;
const canvasWidth = 400;
const canvasHeight = 400;
const n = canvasWidth / size;
const m = canvasHeight / size;

class Cell {
    constructor(status, position) {
        this.status = status;
        this.position = position;
    }
}

let board = [];

function setup() {
    //initialise matrix.
    for(let i=0; i < m; i++) {
        board[i] = new Array(n);
    }
    //fill matrix of cells.
    for(let i=0; i < m; i++ ) {
        for(let j=0; j < n; j++ ) {
            board[i][j] = new Cell(false, [i*size, j*size]);
        }
    }
    console.log("start", floor(m /2), floor(n / 2))
    //[x, y] positions.
    board[floor(m /2)][floor(n / 2)].status = true;
    board[floor(m /2)+1][floor(n / 2)].status = true;
    board[floor(m /2)+2][floor(n / 2)].status = true;

    createCanvas(canvasWidth, canvasHeight);

    frameRate( 1 )
}

//I'm 99.99% sure this function works, all the tests shows it does.
function updateCell(i, j, previousBoard) {
    let count = 0;

    //check neighbourgh cells.
    for(let k=-1; k < 2; k++) {
        for(let p=-1; p < 2; p++) {
            if( !(i + k < 0 || j + p < 0 || j + p >= n || i + k >= m) ) {
                if(k != 0 || p != 0) {         
                    if(previousBoard[i+k][j+p].status === true) {
                        count++;
                    }
                }
            }
        }
    }
    console.log(count)
    if((previousBoard[i][j].status === true) && (count < 2 || count > 3)) {
        //console.log("false", i, j, count)
        board[i][j].status = false;
        
    } else if((count === 3) && (previousBoard[i][j].status === false)) { //if dead but 3 alive neighbours.
        //alive
        console.log("true", i, j, count)
        board[i][j].status = true;
        
    } else if((previousBoard[i][j].status === true) && (count === 2 || count === 3)) {
        console.log("true", i, j, count)
        board[i][j].status = true;
    }
}

function copyMatrix() {
    let newMatrix = []
    for(let i=0; i < m; i++) {
        //console.log(board[i])
        newMatrix[i] = board[i].slice()
    }
    //console.log(newMatrix)
    return newMatrix
} 

function draw() {
    background(220);

    //draw rectangles. 
    for(let i=0; i < m; i++) {
        for(let j=0; j < n; j++) {
            if (board[i][j].status === true) {
                fill('black');
            } else {
                fill('white');
            }
            rect(board[i][j].position[0], board[i][j].position[1], size, size);
        }
    }
    
    //slice copies previous array into a new reference, previousBoard and board are 
    //now independent.
    let previousBoard = copyMatrix();
    
    //updateCell(11, 9, previousBoard) //uncommenting this line creates weird results...
    //console.log(previousBoard)
    //update all cells based on previousBoard. (we'll modify board based on previous)
    for(let i=0; i < m; i++) {
        for(let j=0; j < n; j++) {
            updateCell(i, j, previousBoard);
        }
    }
}
<html>
    <head>
        <Title>Gordon's Game of Life</Title>
        <script src = "p5.js"></script>
        <script src = "sketch.js"></script>
    </head>
    <body>
        <h1>Gordon's Game of Life</h1>
    </body>
</html>
1 Answers

The issue is that copyMatrix needs to do a deep copy.

Change copy matrix to this:

function copyMatrix() {
    let newMatrix = []
    for(let i=0; i < m; i++) {
        newMatrix[i] = [];
        for (let j=0;j< n;j++){
             newMatrix[i][j] = new Cell(board[i][j].status, board[i][j].position);
        }
    }
    return newMatrix
} 

Here is your code with the copyMatrix change and an added glider to help demonstrate behavior.

 //size of cell in pixels.
const size = 20;
const canvasWidth = 400;
const canvasHeight = 400;
const n = canvasWidth / size;
const m = canvasHeight / size;

class Cell {
    constructor(status, position) {
        this.status = status;
        this.position = position;
    }
}

let board = [];

function setup() {
    //initialise matrix.
    for(let i=0; i < m; i++) {
        board[i] = new Array(n);
    }
    //fill matrix of cells.
    for(let i=0; i < m; i++ ) {
        for(let j=0; j < n; j++ ) {
            board[i][j] = new Cell(false, [i*size, j*size]);
        }
    }

    board[floor(m /2)][floor(n / 2)].status = true;
    board[floor(m /2)+1][floor(n / 2)].status = true;
    board[floor(m /2)+2][floor(n / 2)].status = true;
    
    // glider
    board[0][0].status = true;
    board[0][2].status = true;
    board[1][1].status = true;
    board[1][2].status = true;
    board[2][1].status = true;
  

    createCanvas(canvasWidth, canvasHeight);

    frameRate( 1 )
}


function updateCell(i, j, previousBoard) {
    let count = 0;

    //check neighbourgh cells.
    for(let k=-1; k < 2; k++) {
        for(let p=-1; p < 2; p++) {
            if( !(i + k < 0 || j + p < 0 || j + p >= n || i + k >= m) ) {
                if(k != 0 || p != 0) {         
                    if(previousBoard[i+k][j+p].status === true) {
                        count++;
                    }
                }
            }
        }
    }
  
    if((previousBoard[i][j].status) && (count < 2 || count > 3)) {
        board[i][j].status = false;
        
    } else if((count === 3) && (!previousBoard[i][j].status)) { //if dead but 3 alive neighbours.
        board[i][j].status = true;
        
    } else if((previousBoard[i][j].status) && (count === 2 || count === 3)) {
        board[i][j].status = true;
    }
}

function copyMatrix() {
    let newMatrix = []
    for(let i=0; i < m; i++) {
        newMatrix[i] = [];
        for (let j=0;j< n;j++){
        newMatrix[i][j] = new Cell(board[i][j].status, board[i][j].position);
        }
    }
    return newMatrix
} 

function draw() {
    background(220);

    //draw rectangles. 
    for(let i=0; i < m; i++) {
        for(let j=0; j < n; j++) {
            if (board[i][j].status === true) {
                fill('black');
            } else {
                fill('white');
            }
            rect(board[i][j].position[0], board[i][j].position[1], size, size);
        }
    }
    
    let previousBoard = copyMatrix();
    
    //updateCell(11, 9, previousBoard) //uncommenting this line creates weird results...
    //console.log(previousBoard)
    //update all cells based on previousBoard. (we'll modify board based on previous)
    for(let i=0; i < m; i++) {
        for(let j=0; j < n; j++) {
            updateCell(i, j, previousBoard);
        }
    }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js"></script>

With the glider this setup demonstrates all three possible patterns. We start with a moving spaceship glider and an oscillator and when the glider runs into the oscillator we have a few iterations that break both patterns and then it settles down into a 2x2 still life.

Related