6
\$\begingroup\$

I'm a beginner at coding and the past days I tried to write the famous "Conway's Game of Life" in Angular.js. Right now I'm done with the code and the game works. But if the size of the playground grid gets too big (e.g. 100x100 cells), my program is kind of slow. I wanted to ask you where I could improve my code to make it run more quickly. It would be great for me to see what I have could done in a better way to learn from it.

The following code creates the grid for the playing field and gives the cells their coordinates and alive status:

$scope.createGrid = function(width,height){ // Spielfeld erzeugen
    $scope.playingField = {
        width:width,
        height:height,
        data:[]
    };
    for( var r = 0 ; r < width; r++){
        $scope.playingField.data.push([]);
        for( var c = 0 ; c < height; c++){
            $scope.playingField.data[r].push({ x:c, y:r, alive:false });
        }
    }
    $(".grid").width("600px");
    $(".grid").height("600px");
};

Changes color and live status of a cell when clicked on it:

$scope.clickCell = function(cell){ // Zellen Status ändern. tot / leben
    cell.alive = !cell.alive;
};
$scope.style = function(value) { // Einen Style zuweisen
    return { "background-color": value };
};

This is the part where I think the program gets slowed down. It checks all the cell neighbours and copies a new grid after it; I would be very curious about other solutions to this problem:

$scope.startRound = function(){ // Runde durchlaufen
    console.time("Benchmark für das kopieren");
    // TODO: Kopieren ist zu langsam, muss anders erfolgen!
    var newData = angular.copy($scope.playingField.data);
    console.timeEnd("Benchmark für das kopieren");
    angular.forEach($scope.playingField.data, function(cells,r){
        angular.forEach(cells, function(cell,c){
            var alive = 0;
            //Nachbarn überprüfen
            for(var i = r-1 ; i<=r+1;++i){
                for(var j = c-1; j<= c+1 ; ++j){
                    if($scope.getCell(j,i).alive){
                        alive++;
                    }
                }
            }

            if(cell.alive){
                alive -=1;
            }

            // Regeln überprüfen
            if(cell.alive == true && (alive < 2 || alive > 3)){
                newData[r][c].alive = false;
            }
            if(cell.alive == true && (alive == 2 || alive == 3)){
                newData[r][c].alive = true;
            }
            if(cell.alive == false && alive == 3){
                newData[r][c].alive = true;
            }
        });
    });

    $scope.playingField.data = newData;
};

Checks if an alive cell is at the edge of the playing field. If yes, then a new neighbour will be set.

 $scope.getCell = function(x,y){ // Nachbarn überprüfen
    // Wenn das Spielfeld verlassen wird, wird der Nachbar neu bestimmt (auf der anderen Seite)
    var h = $scope.playingField.height-1;
    var w = $scope.playingField.width-1;
    return $scope.playingField.data[(y+h) % h][(x+w) %w];
};

Functions to start the game, stop the game and to refresh the grid.

$scope.startGame = function(){ // Spiel starten
    console.log("Game started");
    $scope.stopInterval = $interval($scope.startRound, 10);
};
$scope.stopGame = function(){ // Spiel beenden
    console.log("Game stopped");
    $interval.cancel($scope.stopInterval);
};
$scope.refreshGameField = function(){ //Sspielfeld zurücksetzen
    console.log("Game refreshed");
    $scope.createGrid($scope.playingField.width, $scope.playingField.height);
};
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

The problem is that angular.copy is a really slow function (discussion here: https://github.com/angular/angular.js/issues/11099), in order to copy the array without references you could also do something like this:

var newData = JSON.parse(JSON.stringify($scope.playingField.data));

This should be a big improvement in speed.

\$\endgroup\$
4
  • \$\begingroup\$ Hey thanks for the fast respond. I'll try that out and let you know. \$\endgroup\$
    – Chreasy
    Commented Jan 28, 2016 at 11:38
  • \$\begingroup\$ indeed. with a 70x70 grid and the normal way i had a benchmark for the copy of ~190ms. With your method to copy i got around 15ms now. Thanks for the good hint \$\endgroup\$
    – Chreasy
    Commented Jan 28, 2016 at 11:47
  • \$\begingroup\$ Better yet - don't copy the array, use one array to represent the current state, one for the future state, and swap them around on each iteration (pass them as parameters, don't move the data). \$\endgroup\$
    – symcbean
    Commented Jan 28, 2016 at 12:24
  • \$\begingroup\$ Ye that sounds like it could be a good solution. But because of my lack of programming skills, i don't know how to set this up. Could you try to show me roughly how this would look like or how i could implement this? I would be very grateful. \$\endgroup\$
    – Chreasy
    Commented Jan 28, 2016 at 12:58
2
\$\begingroup\$

As mentioned by Martijn Welker, your problem is that you duplicate your playfield on each step. Rather than finding a faster way to duplicate, just get rid of it completely.

Right now you're doing it so newly born cells won't interfere with .alive checks. Instead of making copying entire field, just introduce a new property cell .state which would be either "alive", "born" or absent for dead cells.

When you create new cells, just make their .state = "born". After you're done with processing step data, either iterate over field to change "born" to "alive" or introduce an array that would hold cells born to make things even faster.

\$\endgroup\$

Your Answer

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