Source: main-life.js

/**
 * @author Antonio Raúl Guijarro Contreras <alu0101100494@ull.edu.es>
 * @file Script principal que simulará el juego de la vida
 * @copyright Antonio Raúl Guijarro Contreras 2020
 * @since 07.05.2020
 * @module life-main
 */

import Board from './Board.js';
import Box from './Box.js';
import Layers from './Layers.js';
import * as Utility from './CanvasUtility.js';
import Cell from './Cell.js';

 // Atributos HTML
let canvas;
let ctx;
let buttonStartGame;
let buttonEndGame;
let buttonPauseGame;
let buttonNextStepGame;
let sliderSpeed;

//  Atributos lógicos
const BOXES = 33;
let originalTimeSpeed = 1000;
let timeSpeed = originalTimeSpeed;
let timeSpeedScale = 1;
let isRun = true;
let board;
let interval;
let timeOut;
let nCells;

if (document != null) {
  // Asignación
  canvas = document.getElementById('canvas');
  ctx = canvas.getContext('2d');
  buttonStartGame = document.getElementById('start-game');
  buttonEndGame = document.getElementById('end-game');
  buttonPauseGame = document.getElementById('pause-game');
  buttonNextStepGame = document.getElementById('nextSep-game');
  sliderSpeed = document.getElementById('speed');
  nCells = parseInt(document.getElementById('cellsNumber').value);

  // Eventos
  window.addEventListener('load', start);
  buttonStartGame.addEventListener('click', main);
  buttonNextStepGame.addEventListener('click', gameOfLife);
  buttonEndGame.addEventListener('click', ()  => { isRun = false; Utility.clearScreen(ctx, canvas); resetAll()});
}

/**
 * Función que se ejecutará nada más cargar la página
 */
function start() {
  //  Escalamos el ratio de pixeles en función del tamaño dado por el CSS
  Utility.fixDpi(canvas);

  //  Para no detener el doom dejamos que cada X tiempo revise algunos elementos
  interval = setInterval(function() {
    buttonPauseGame.addEventListener('click', () => isRun = false);
    timeSpeedScale = sliderSpeed.value;
    timeSpeed = originalTimeSpeed / timeSpeedScale;
  }, 100);
}

/**
 * Ejecutará la función principal del script: Generar una solucíon de 8 reinas
 */
async function main() {
  if (isRun) resetAll();
  isRun = true;
  while(isRun) {
    await gameOfLife();
    await new Promise(r => timeOut = setTimeout(r, timeSpeed));
  }
}

/**
 * Algoritmo del juego de la vida
 */
async function gameOfLife() {
  // Mostramos por pantalla
  await board.draw(ctx);
  // Recorremos y asignamos los vecinos
  await setNeightbourds(board);
  // Comprobamos el estado
  await checkRules(board);
}

/**
 * Resetea todo el entorno del script
 */
function resetAll() {
  let aux = canvas.width / canvas.height;
  board = new Board(BOXES * Math.round(aux), BOXES);
  board.build(canvas.width, canvas.height, ['black']);
  clearTimeout(timeOut);
  //  Insertamos las células
  nCells = parseInt(document.getElementById('cellsNumber').value);
  resetBoard(board);
  insertCellsRandomdly(board, nCells);
}

/**
 * Resetea el tablero a solo células muertas
 * @param {Board} _board Tablero de la simulación
 */
function resetBoard(_board) {
  for (let i = 0; i < _board.verticalBoxes; i++) {
    for (let j = 0; j < _board.horizontalBoxes; j++) {
      _board.elements[_board.translate(i, j)].element = new Cell(false);
    }
  }
}

/**
 * Asingna a cada célula sus vecinos muertos y vivos
 * @param {Board} _board Ingresa los vecinos correspondientes a cada neurona 
 */
function setNeightbourds(_board) {
  for (let i = 0; i < _board.verticalBoxes; i++) {
    for (let j = 0; j < _board.horizontalBoxes; j++) {
      _board.elements[_board.translate(i, j)].element.neighbors = {alive: [], dead: []};
      // Submatriz 3x3
      if (_board.elements[_board.translate(i, j)].element != null) {
        for (let y = -1; y < 2; y++) {
          for (let x = -1; x < 2; x++) {
            // Controlamos que no sobresalga el rango de la matriz y no es el punto inicial
            if ((i + y >= 0 && i + y < _board.verticalBoxes && j + x >= 0 &&
                j + x < _board.horizontalBoxes) && (y != 0 || x != 0)) {
              _board.elements[_board.translate(i, j)].element.neighbor =
                  _board.elements[_board.translate(i + y, j + x)].element;
            }
          }
        }
      }

    }
  }
}

/**
 * Introduce aleatoriamente un número determinado de células dentro del tablero
 * @param {Board} _board Tablero de la simulación
 * @param {Nmber} _nCells Número de células a introducir
 */
function insertCellsRandomdly(_board, _nCells) {
  let size = _board.horizontalBoxes * _board.verticalBoxes;
  let index = 0;
  for (let i = 0; i < _nCells; i++) {
    index = Math.floor(Math.random() * size); 
    _board.elements[index].element = new Cell();
    _board.elements[index].fillStyle = 'yellow';
  }
}

/**
 * Comprueba las reglas de juego
 * @param {Board} _board Tablero de la simulación
 */
function checkRules(_board) {
  for (let i = 0; i < _board.horizontalBoxes * _board.verticalBoxes; i++) {
    _board.elements[i].element.check();
    if (_board.elements[i].element.isAlive) {
      // Células vivas
      _board.elements[i].fillStyle = 'yellow';
    } else {
      // Células muertas
      _board.elements[i].fillStyle = 'black';
    }
  }
}