AngularJS sample : game of life

by tchatel

HTML

<script src="http://code.angularjs.org/1.0.0rc1/angular-1.0.0rc1.js"></script>
<div ng:app ng-controller="GameOfLifeCntl">
    
  <h1>AngularJS Game of Life</h1>
  Height: <input type="text" ng-model="height"/>
  Width: <input type="text" ng-model="width"/>
  <button ng-click="newGame()">New</button>
  <button ng-click="next()">Next</button>

  <table id="board">
      <tr ng-repeat="row in board">
          <td ng-repeat="cell in row" ng-click="toggle($parent.$index, $index)"
              class="{{cellClass($parent.$index, $index)}}">
             <span ng-show="cell">O</span>
          </td>
      </tr>
  </table>

  <p class="history" ng-show="history.length > 0">Previous steps (click to rewind)</p>
  <table class="history">
      <tr>
          <td ng-repeat="step in history" ng-click="$parent.step($index)">{{$index}}</td>
      </tr>
  </table>
    
</div>

CSS

h1 {
    font-size: 1.3em;
    color: #844;
    font-style: bold;    
}
table#board td {
    width: 1.5em;
    height: 1.5em;
    text-align: center;
    vertical-align: middle;
    border: solid 1px #ddd;
}
table#board td.new {
    background-color: #efe;
}
table#board td.die {
    background-color: #fee;
}

.history {
    color: #777;
}
table.history td {
    width: 1.5em;
    height: 1.5em;
    text-align: center;
    vertical-align: middle;
    border: solid 1px #eee;
}

JavaScript

function GameOfLifeCntl($scope, $http, $resource) {

    $scope.newGame = function () {
        $scope.history = [];
        $scope.board = init($scope.height, $scope.width);
    };
    
    $scope.next = function () {
        $scope.history.push($scope.board);
        $scope.board = computeNext($scope.board);
    };

    $scope.step = function (index) {
        $scope.board = $scope.history[index];
        $scope.history = $scope.history.slice(0, index);
    };
    
    $scope.toggle = function (row, cell) {
        $scope.history = []; // Reset history as it is no longer accurate       
        $scope.board[row][cell] = !$scope.board[row][cell];
    };
    
    $scope.cellClass = function (row, cell) {
        if (willDie($scope.board, row, cell)) {
            return "die";
        }
        if (newCell($scope.board, row, cell)) {
            return "new";
        }
        return "";
    };

    $scope.height = 10;
    $scope.width = 20;
    $scope.newGame();
    
    function init(height, width) {
        var board = [];
        for (var h = 0 ; h < height ; h++) {
            var row = [];
            for (var w = 0 ; w < width ; w++) {
                row.push(false);
            }
            board.push(row); 
        }
        return board;
    }
    
    function computeNext(board) {
        var newBoard = [];
        for (var r = 0 ; r < board.length ; r++) {
            var newRow = [];
            for (var c = 0 ; c < board[r].length ; c++) {
                newRow.push(willLive(board, r, c) || newCell(board, r, c));
            }
            newBoard.push(newRow); 
        }
        return newBoard;
    }
    
    function willLive(board, row, cell) {
        return cellAt(board, row, cell) 
            && neighbours(board, row, cell) >= 2 
            && neighbours(board, row, cell) <= 3;
    }
    function willDie(board, row, cell) {
        return cellAt(board, row, cell) 
        && (neighbours(board, row, cell) < 2 
                ||...