Conway's Game of Life

by Retsam19

HTML

<label>
    Min X: <input data-bind="value: xMin, valueUpdate:'input'"></input>
</label>
<label>
    Max X: <input data-bind="value: xMax, valueUpdate:'input'"></input>
</label>
<button data-bind="
    click: toggleRun,
    text: running() ? 'Stop' : 'Run'
"></button>
<br />
<label>
    Min Y: <input data-bind="value: yMin, valueUpdate:'input'"></input>
</label>
<label>
    Max Y: <input data-bind="value: yMax, valueUpdate:'input'"></input>
</label>
<button data-bind="click: step">Step</button>
<div class="table" data-bind="foreach: {as: 'y', data: yRange}">
    <div class="row" data-bind="foreach: {as: 'x', data: $root.xRange}">
        <div class="cell" data-bind="
            css: {
                 alive: $root.generation().isAlive({x: x, y:y}),
            },
            click: $root.toggleCell
        "></div>
    </div>
</div>

CSS

.table {
    display: flex-inline;
    flex-direction: column;
}

.row {
    display: flex;
    flex-shrink:1;
}
.cell {
    border: 1px solid black;
    width: 50px;
    height: 50px;
}
.cell.alive {
    background-color: green
}

JavaScript

var RUN_SPEED = 500; //Lower this to speed up Run mode

var vm = {};
vm.generation = ko.observable(new Generation([{x: 0, y: 0}, {x: 1, y: 0}, {x:2, y: 0},{x:2,y:1},{x: 1,y:2}]));

vm.xMin = ko.observable(-5);
vm.xMax = ko.observable(5);
vm.yMin = ko.observable(-5);
vm.yMax = ko.observable(5);
vm.xRange = ko.computed(function () {
    var range = [];
    for(var x=vm.xMin(); x<=vm.xMax(); x++) {
        range.push(x);
    }
    return range;
});
vm.yRange = ko.computed(function () {
    var range = [];
    for(var y=vm.yMax(); y>=vm.yMin(); y--) {
        range.push(y);
    }
    return range;
});

vm.toggleCell = function(data,e) {
    var ctx = ko.contextFor(e.target);
    vm.generation.valueWillMutate();
    vm.generation().toggleAlive({x: ctx.x, y: ctx.y});
    vm.generation.valueHasMutated();
}
vm.step = function() {
    vm.generation(vm.generation().nextGeneration());
}

vm.running = ko.observable(false);
var runId;
vm.running.subscribe(function (nowRunning) {
    if(nowRunning) {
        vm.step();
        runId = window.setInterval(vm.step, RUN_SPEED);
    } else {
        window.clearInterval(runId);
    }
});

vm.toggleRun = function () {
    vm.running(!vm.running());
}

ko.applyBindings(vm);

function Generation(cellList) {
  var liveCells = {};
  var self = this;

  function init(cellList) {
    cellList.map(self.setAlive);
  }

  this.setAlive = function(cell) {
    var x = cell.x; var y = cell.y;
    liveCells[x] = liveCells[x] || {};
    liveCells[x][y] = true;
    return true;
  };

  this.setDead = function(cell) {
    var x = cell.x; var y = cell.y;
    liveCells[x] = liveCells[x] || {};
    liveCells[x][y] = false;
    return false;
  };

  this.isAlive = function(cell) {
    var x = cell.x; var y = cell.y;
    return liveCells[x] && liveCells[x][y];
  };

  this.toggleAlive = function(cell) {
    return self.isAlive(cell) ? self.setDead(cell) : self.setAlive(cell);
  };

  this.getLiveCells = function() {
    var cells = [];
    for(var x in...