AngularJS sample : game of life

by ffischer2309

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 ||
        neighbours(board, row, cell) > 3);
  }

  function newCell(board, row, cell) {
    return !cellAt(board, row, cell) &&
      neighbours(board, row, cell) == 3;
  }

  function neighbours(board, row, cell) {
    var n = 0;
    n += cellAt(board, row - 1, cell - 1) ? 1 : 0;
    n +=...