JSFiddle - React, Tailwind, and code Playground

by zjoxic

HTML

<!-- Кнопки -->
<button data-bind="click:toggleAutoMode,text:autoMode()?'Остановить':'Запустить'"></button>
<button data-bind="visible:!autoMode(), click:nextGeneration">Следующее поколение</button>
<button data-bind="click:reset">Сброс</button>

<!-- Игровое поле -->
<div class="field" data-bind="foreach:visibleCells, style:{fontSize:size()+'px'}">
    <div class="cell" data-bind="click:toggleAlive, css: {'alive':isAlive}, style: { left:x+'em', top:y+'em' }">
    </div>
</div>

CSS

/* Стили */
.field {position: absolute; left:50%; top:50%;}
.cell { width: 1em; height: 1em; position:absolute; box-shadow: inset 0 0 0.2em rgba(0,0,0,0.5); }
.alive {background-color: green;}

JavaScript

/**
 * Это модель игрового поля
 */
var model = {
    
    field: {},
    generation: 0,
    speed: 3,
    size: ko.observable(32),
    maxFieldWidth: 400,
    autoMode: ko.observable(false),
    totalAlive: ko.observable(0),
    visibleCells: ko.observableArray(),
    toToggleCells: {},
    
    //создание новой клетки
    createCell: function(x, y){
        var cell = new CellModel(x, y);
        this.field[x] = this.field[x] || {};
        this.field[x][y] = cell;
        //магия автоуменьшения поля тут:
        var maxCoord = Math.max( Math.abs(x), Math.abs(y) );
        if(maxCoord*this.size() > this.maxFieldWidth/2){
            this.size(this.maxFieldWidth/2/maxCoord);
        }
        this.visibleCells.push(cell);
        return cell;
    },
    //получение старой клетки
    getCell: function(x, y){
        if(x in this.field && y in this.field[x]){
            return this.field[x][y];
        }
    },
    //переход к следующему поколению
    nextGeneration: function(){
        var toToggle = this.toToggleCells;
        this.toToggleCells = {};
        for(var id in toToggle){
            toToggle[id].toggleAlive();
        }
        if(this.autoMode() && this.totalAlive() === 0){
            //если нет живых клеток, ставим на паузу
            this.toggleAutoMode();
        }
        this.generation++;
    },
    //а это зацикливание программы по таймеру
    timerID:null,
    toggleAutoMode:function(){
        this.autoMode(!this.autoMode());
        if(this.autoMode()){
            var that = this;
            this.timerID = setInterval(function(){that.nextGeneration()}, 1000/this.speed);
        }
        else{
            clearInterval(this.timerID);
        }
    },
    reset: function(){
        if(this.autoMode()){
            this.toggleAutoMode();
        }
        this.field = {};
        this.toToggleCells = {};
        this.visibleCells([]);
        this.totalAlive(0);
        this.createCell(0, 0).toggleAlive();
        this.generation = 0;
...