Conway's Life Question (Solution)

by Nick Iaconis

HTML

<div id="root">
  <div>
    <label>
      <span>Grid size:</span>
      <input id="size" name="size" type="number" min=2 />
    </label>
    <label>
      <span>Init Life state:</span>
      <input id="init" name="init" placeholder="2 dimension JSON array" />
    </label>
    <button id="generate">
      Reset Grid
    </button>
  </div>
  <div>
    <input id="speed" type="range" min=32 max=512 step=8 value=128 />
    <button id="simulate">
      Start
    </button>
  </div>
  <div class="grid">
  </div>
</div>

CSS

.grid {
  margin-top: .5em;
  width: 100%;
  padding-top: 100%;
  display: flex;
  flex-direction: column;
  background-color: #eee;
}

.grid > :first-child {
  margin-top: -100%;
}

.grid > * {
  flex-grow: 1;
  width: 100%;
  display: flex;
}

.grid > * > * {
  display: block;
  flex-grow: 1;
}

.grid > :nth-child(odd) > :nth-child(even),
.grid > :nth-child(even) > :nth-child(odd) {
  background-color: #ddd;
}

.grid > :nth-child(n) > .alive {
  background-color: black;
}

JavaScript

let hasValidGrid = false;
let simulating = false;
let lastTick = 0;
let lifeState;
const root = document.getElementById('root');
const size = document.getElementById('size');
const init = document.getElementById('init');
const speed = document.getElementById('speed');
const simulate = document.getElementById('simulate');

root.addEventListener('click', event => {
  switch (event.target.id) {
    case "generate": {
      // check grid size validity
      const dimension = parseInt(size.value, 10);
      if (isNaN(dimension) || dimension < 2) {
        size.setCustomValidity('must be positive & ≥ 2');
        size.reportValidity();
        return;
      }
      size.setCustomValidity('');
      // check Life init validity & populate initial state
      let lifeInit = [];
      if (init.value.length) {
        try {
          lifeInit = JSON.parse(init.value);
        } catch (e) {
          init.setCustomValidity(e.message);
          init.reportValidity();
        }
      }
      init.setCustomValidity('');
      simulating = false;
      updateSimulateButton();
      // clear grid & life state
      const oldGrid = root.getElementsByClassName('grid')[0];
      const grid = oldGrid.cloneNode(false);
      oldGrid.parentNode.replaceChild(grid, oldGrid);
      lifeState = [];
      // populate DOM elements & life state
      for (let i = 0; i < dimension; i++) {
        const row = document.createElement('div');
        const lifeRow = [];
        lifeState.push(lifeRow);
        for (let j = 0; j < dimension; j++) {
          const cell = document.createElement('div');
          cell.setAttribute('data-row', i);
          cell.setAttribute('data-cell', j);
          row.appendChild(cell);
          lifeRow.push(queryLife(lifeInit, i, j));
        }
        grid.appendChild(row);
      }
      hasValidGrid = true;
      applyState(lifeState);
      return;
    }
    case "simulate": {
      if (!hasValidGrid) {
        size.setCustomValidity('a grid is required for...