Files
004_comission/chris20202020/_ref/game-of-life-js/canvas.js
louiscklaw d8e1289123 update,
2025-01-31 19:30:10 +08:00

136 lines
3.5 KiB
JavaScript

console.log('Loading Game of Life');
const {
newBoard,
neighborCoordinatesForBoard,
boardAsNumberOfNeighbors,
isLive,
isUnderPopulated,
isOverPopulated,
willContinue,
canReproduce,
SIZE
} = life;
const playPause = document.querySelector('[data-play-pause]');
const reload = document.querySelector('[data-reload]');
const startClass = 'fa-play';
const pauseClass = 'fa-pause';
const canvas = document.querySelector('#life');
const ctx = canvas.getContext('2d');
const {height, width} = canvas;
const UPDATE_FREQUENCY = 30;
const GRID_COUNT = SIZE;
const CELL_SIZE = width/GRID_COUNT;
const COLORS = {
// live: 'rgba(200, 0, 0, 1)',
live: 'rgba(40, 169, 255, 0.75)',
dead: 'rgba(255, 255, 255, 0.5)'
};
// Uses function declaration for binding `this`
// to the canvas context.
function _renderBox (isLive, xGrid, yGrid) {
this.lineWidth = 0.8;
this.strokeStyle = isLive ? COLORS.live : COLORS.dead;
this.strokeRect(xGrid*CELL_SIZE,
yGrid*CELL_SIZE,
CELL_SIZE,
CELL_SIZE);
}
//
const liveAt = _renderBox.bind(ctx, true);
const deadAt = _renderBox.bind(ctx, false);
const numberToIsLive = (number, cell) => {
if (isLive(cell)) {
if (isUnderPopulated(number)) {
// return DEAD;
return false;
} else if (isOverPopulated(number)) {
// return DEAD;
return false
} else if (willContinue(number)) {
// return LIVE;
return true;
}
} else if (canReproduce(number)){
// return LIVE;
return true;
} else {
// return DEAD;
return false;
}
};
// Given rows or boards of cells and neighbor counts, calculate next states.
const numberRowAsLiveDeadCells = (rowOfNumbers, rowOfCells) => rowOfNumbers.map((n, i) => numberToIsLive(n, rowOfCells[i]));
const numberBoardAsLiveDeadCells = (boardOfNumbers, boardOfCells) => boardOfNumbers.map((r, i) => numberRowAsLiveDeadCells(r, boardOfCells[i]));
const renderRow = (r, y) => r.map((c, i) => (c ? liveAt(i, y) : deadAt(i, y)) && c);
const renderBoard = b => b.map(renderRow);
let rafID;
let board = newBoard(true);
const coords = neighborCoordinatesForBoard(board);
let neighbors; // The game board as number of live neighbors per cell.
let isRunning = false;
const main = () => {
if (isRunning) {
// Given a board, calculate all the valid neighbor coordinates.
neighbors = boardAsNumberOfNeighbors(board, coords); // Calculate live neighbor counts.
board = numberBoardAsLiveDeadCells(neighbors, board); // Calculate next state of board.
renderBoard(board);
setTimeout(() => {
rafID = requestAnimationFrame(main);
}, UPDATE_FREQUENCY);
}
}
const togglePlaying = () => {
if (isRunning) {
cancelAnimationFrame(rafID);
playPause.querySelector('i').classList.add(startClass);
playPause.querySelector('i').classList.remove(pauseClass);
} else {
rafID = requestAnimationFrame(main);
playPause.querySelector('i').classList.remove(startClass);
playPause.querySelector('i').classList.add(pauseClass);
}
isRunning = !isRunning;
};
const reloadBoard = () => board = newBoard(true);
playPause.addEventListener('click', togglePlaying);
reload.addEventListener('click', reloadBoard);
document.addEventListener('keydown', (e) => {
console.log(e.keyCode);
if (e.getModifierState('Control')) {
return;
}
switch (e.keyCode) {
case 32:
case 13:
e.preventDefault();
togglePlaying()
break;
case 82:
e.preventDefault();
reloadBoard();
break;
}
})