550 lines
16 KiB
JavaScript
550 lines
16 KiB
JavaScript
|
|
(function (global) {
|
||
|
|
'use strict';
|
||
|
|
|
||
|
|
const PATTERNS = {
|
||
|
|
glider: {
|
||
|
|
name: 'Glider',
|
||
|
|
cells: [
|
||
|
|
[0, 1],
|
||
|
|
[1, 2],
|
||
|
|
[2, 0],
|
||
|
|
[2, 1],
|
||
|
|
[2, 2],
|
||
|
|
],
|
||
|
|
},
|
||
|
|
pulsar: {
|
||
|
|
name: 'Pulsar',
|
||
|
|
cells: [
|
||
|
|
[0, 2], [0, 3], [0, 4], [0, 8], [0, 9], [0, 10],
|
||
|
|
[2, 0], [2, 5], [2, 7], [2, 12],
|
||
|
|
[3, 0], [3, 5], [3, 7], [3, 12],
|
||
|
|
[4, 0], [4, 5], [4, 7], [4, 12],
|
||
|
|
[5, 2], [5, 3], [5, 4], [5, 8], [5, 9], [5, 10],
|
||
|
|
[7, 2], [7, 3], [7, 4], [7, 8], [7, 9], [7, 10],
|
||
|
|
[8, 0], [8, 5], [8, 7], [8, 12],
|
||
|
|
[9, 0], [9, 5], [9, 7], [9, 12],
|
||
|
|
[10, 0], [10, 5], [10, 7], [10, 12],
|
||
|
|
[12, 2], [12, 3], [12, 4], [12, 8], [12, 9], [12, 10],
|
||
|
|
],
|
||
|
|
},
|
||
|
|
gosperGliderGun: {
|
||
|
|
name: 'Gosper Glider Gun',
|
||
|
|
cells: [
|
||
|
|
[0, 24],
|
||
|
|
[1, 22], [1, 24],
|
||
|
|
[2, 12], [2, 13], [2, 20], [2, 21], [2, 34], [2, 35],
|
||
|
|
[3, 11], [3, 15], [3, 20], [3, 21], [3, 34], [3, 35],
|
||
|
|
[4, 0], [4, 1], [4, 10], [4, 16], [4, 20], [4, 21],
|
||
|
|
[5, 0], [5, 1], [5, 10], [5, 14], [5, 16], [5, 17], [5, 22], [5, 24],
|
||
|
|
[6, 10], [6, 16], [6, 24],
|
||
|
|
[7, 11], [7, 15],
|
||
|
|
[8, 12], [8, 13],
|
||
|
|
],
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
function createEmptyGrid(rows, cols) {
|
||
|
|
return Array.from({ length: rows }, function () {
|
||
|
|
return Array(cols).fill(0);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function cloneGrid(grid) {
|
||
|
|
return grid.map(function (row) {
|
||
|
|
return row.slice();
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function countLiveNeighbors(grid, row, col) {
|
||
|
|
let total = 0;
|
||
|
|
|
||
|
|
for (let rowOffset = -1; rowOffset <= 1; rowOffset += 1) {
|
||
|
|
for (let colOffset = -1; colOffset <= 1; colOffset += 1) {
|
||
|
|
if (rowOffset === 0 && colOffset === 0) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
const nextRow = row + rowOffset;
|
||
|
|
const nextCol = col + colOffset;
|
||
|
|
|
||
|
|
if (
|
||
|
|
nextRow >= 0 &&
|
||
|
|
nextRow < grid.length &&
|
||
|
|
nextCol >= 0 &&
|
||
|
|
nextCol < grid[0].length
|
||
|
|
) {
|
||
|
|
total += grid[nextRow][nextCol];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return total;
|
||
|
|
}
|
||
|
|
|
||
|
|
function stepGrid(grid) {
|
||
|
|
return grid.map(function (cells, rowIndex) {
|
||
|
|
return cells.map(function (cell, colIndex) {
|
||
|
|
const neighbors = countLiveNeighbors(grid, rowIndex, colIndex);
|
||
|
|
|
||
|
|
if (cell === 1) {
|
||
|
|
return neighbors === 2 || neighbors === 3 ? 1 : 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
return neighbors === 3 ? 1 : 0;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function stampPattern(grid, pattern, offsetRow, offsetCol) {
|
||
|
|
pattern.cells.forEach(function (cell) {
|
||
|
|
const row = cell[0] + offsetRow;
|
||
|
|
const col = cell[1] + offsetCol;
|
||
|
|
|
||
|
|
if (row >= 0 && row < grid.length && col >= 0 && col < grid[0].length) {
|
||
|
|
grid[row][col] = 1;
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
return grid;
|
||
|
|
}
|
||
|
|
|
||
|
|
function countLiveCells(grid) {
|
||
|
|
return grid.reduce(function (total, row) {
|
||
|
|
return total + row.reduce(function (rowTotal, cell) {
|
||
|
|
return rowTotal + cell;
|
||
|
|
}, 0);
|
||
|
|
}, 0);
|
||
|
|
}
|
||
|
|
|
||
|
|
function centerPattern(grid, pattern) {
|
||
|
|
const lastRow = pattern.cells.reduce(function (maxRow, cell) {
|
||
|
|
return Math.max(maxRow, cell[0]);
|
||
|
|
}, 0);
|
||
|
|
const lastCol = pattern.cells.reduce(function (maxCol, cell) {
|
||
|
|
return Math.max(maxCol, cell[1]);
|
||
|
|
}, 0);
|
||
|
|
const offsetRow = Math.max(0, Math.floor((grid.length - (lastRow + 1)) / 2));
|
||
|
|
const offsetCol = Math.max(0, Math.floor((grid[0].length - (lastCol + 1)) / 2));
|
||
|
|
|
||
|
|
return stampPattern(grid, pattern, offsetRow, offsetCol);
|
||
|
|
}
|
||
|
|
|
||
|
|
function createInitialState(options) {
|
||
|
|
const rows = options.rows;
|
||
|
|
const cols = options.cols;
|
||
|
|
const defaultPattern = options.defaultPattern || 'pulsar';
|
||
|
|
const grid = createEmptyGrid(rows, cols);
|
||
|
|
|
||
|
|
centerPattern(grid, PATTERNS[defaultPattern]);
|
||
|
|
|
||
|
|
return {
|
||
|
|
cols: cols,
|
||
|
|
defaultPattern: defaultPattern,
|
||
|
|
generation: 0,
|
||
|
|
grid: grid,
|
||
|
|
liveCount: countLiveCells(grid),
|
||
|
|
rows: rows,
|
||
|
|
running: false,
|
||
|
|
selectedPattern: defaultPattern,
|
||
|
|
speed: 1,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function randomizeGrid(grid, probability) {
|
||
|
|
const chance = typeof probability === 'number' ? probability : 0.28;
|
||
|
|
|
||
|
|
return grid.map(function (row) {
|
||
|
|
return row.map(function () {
|
||
|
|
return Math.random() < chance ? 1 : 0;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function toggleCell(grid, row, col, forcedValue) {
|
||
|
|
const next = cloneGrid(grid);
|
||
|
|
|
||
|
|
if (row < 0 || row >= next.length || col < 0 || col >= next[0].length) {
|
||
|
|
return next;
|
||
|
|
}
|
||
|
|
|
||
|
|
next[row][col] = typeof forcedValue === 'number' ? forcedValue : next[row][col] ? 0 : 1;
|
||
|
|
return next;
|
||
|
|
}
|
||
|
|
|
||
|
|
function applyPreset(state, patternName) {
|
||
|
|
const grid = createEmptyGrid(state.rows, state.cols);
|
||
|
|
const nextPattern = PATTERNS[patternName] ? patternName : state.defaultPattern;
|
||
|
|
|
||
|
|
centerPattern(grid, PATTERNS[nextPattern]);
|
||
|
|
|
||
|
|
return {
|
||
|
|
cols: state.cols,
|
||
|
|
defaultPattern: state.defaultPattern,
|
||
|
|
generation: 0,
|
||
|
|
grid: grid,
|
||
|
|
liveCount: countLiveCells(grid),
|
||
|
|
rows: state.rows,
|
||
|
|
running: false,
|
||
|
|
selectedPattern: nextPattern,
|
||
|
|
speed: state.speed,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
|
||
|
|
function advanceState(state) {
|
||
|
|
const nextGrid = stepGrid(state.grid);
|
||
|
|
|
||
|
|
return Object.assign({}, state, {
|
||
|
|
generation: state.generation + 1,
|
||
|
|
grid: nextGrid,
|
||
|
|
liveCount: countLiveCells(nextGrid),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function setSpeed(state, speed) {
|
||
|
|
return Object.assign({}, state, {
|
||
|
|
speed: Math.max(1, Math.min(6, speed)),
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function getSpeedLabel(speed) {
|
||
|
|
if (speed <= 1) {
|
||
|
|
return 'Drift';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (speed <= 3) {
|
||
|
|
return 'Pulse';
|
||
|
|
}
|
||
|
|
|
||
|
|
if (speed <= 5) {
|
||
|
|
return 'Hyper';
|
||
|
|
}
|
||
|
|
|
||
|
|
return 'Lightstorm';
|
||
|
|
}
|
||
|
|
|
||
|
|
function getSpeedDelay(speed) {
|
||
|
|
const delays = {
|
||
|
|
1: 520,
|
||
|
|
2: 340,
|
||
|
|
3: 220,
|
||
|
|
4: 140,
|
||
|
|
5: 90,
|
||
|
|
6: 60,
|
||
|
|
};
|
||
|
|
|
||
|
|
return delays[Math.max(1, Math.min(6, speed))];
|
||
|
|
}
|
||
|
|
|
||
|
|
function getPatternLabel(patternName) {
|
||
|
|
return PATTERNS[patternName] ? PATTERNS[patternName].name : 'Custom';
|
||
|
|
}
|
||
|
|
|
||
|
|
function createEmptyLike(state) {
|
||
|
|
return createEmptyGrid(state.rows, state.cols);
|
||
|
|
}
|
||
|
|
|
||
|
|
function replaceGrid(state, grid, selectedPattern) {
|
||
|
|
return Object.assign({}, state, {
|
||
|
|
generation: 0,
|
||
|
|
grid: grid,
|
||
|
|
liveCount: countLiveCells(grid),
|
||
|
|
running: false,
|
||
|
|
selectedPattern: selectedPattern || 'custom',
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function getCanvasPoint(event, canvas, state) {
|
||
|
|
const rect = canvas.getBoundingClientRect();
|
||
|
|
const col = Math.floor(((event.clientX - rect.left) / rect.width) * state.cols);
|
||
|
|
const row = Math.floor(((event.clientY - rect.top) / rect.height) * state.rows);
|
||
|
|
|
||
|
|
if (row < 0 || row >= state.rows || col < 0 || col >= state.cols) {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
|
||
|
|
return { row: row, col: col };
|
||
|
|
}
|
||
|
|
|
||
|
|
function resizeCanvas(canvas) {
|
||
|
|
const ratio = global.devicePixelRatio || 1;
|
||
|
|
const bounds = canvas.getBoundingClientRect();
|
||
|
|
const width = Math.max(1, Math.floor(bounds.width * ratio));
|
||
|
|
const height = Math.max(1, Math.floor(bounds.height * ratio));
|
||
|
|
|
||
|
|
if (canvas.width !== width || canvas.height !== height) {
|
||
|
|
canvas.width = width;
|
||
|
|
canvas.height = height;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
function drawSimulation(canvas, state) {
|
||
|
|
const context = canvas.getContext('2d');
|
||
|
|
const width = canvas.width;
|
||
|
|
const height = canvas.height;
|
||
|
|
const cellWidth = width / state.cols;
|
||
|
|
const cellHeight = height / state.rows;
|
||
|
|
const inset = Math.max(1.5, Math.min(cellWidth, cellHeight) * 0.12);
|
||
|
|
|
||
|
|
context.clearRect(0, 0, width, height);
|
||
|
|
|
||
|
|
const background = context.createLinearGradient(0, 0, width, height);
|
||
|
|
background.addColorStop(0, '#081321');
|
||
|
|
background.addColorStop(1, '#040a13');
|
||
|
|
context.fillStyle = background;
|
||
|
|
context.fillRect(0, 0, width, height);
|
||
|
|
|
||
|
|
const bloom = context.createRadialGradient(width * 0.18, height * 0.16, 0, width * 0.18, height * 0.16, width * 0.55);
|
||
|
|
bloom.addColorStop(0, 'rgba(115, 240, 221, 0.15)');
|
||
|
|
bloom.addColorStop(1, 'rgba(115, 240, 221, 0)');
|
||
|
|
context.fillStyle = bloom;
|
||
|
|
context.fillRect(0, 0, width, height);
|
||
|
|
|
||
|
|
context.beginPath();
|
||
|
|
context.strokeStyle = 'rgba(126, 192, 212, 0.1)';
|
||
|
|
context.lineWidth = 1;
|
||
|
|
|
||
|
|
for (let row = 0; row <= state.rows; row += 1) {
|
||
|
|
const y = Math.round(row * cellHeight) + 0.5;
|
||
|
|
context.moveTo(0, y);
|
||
|
|
context.lineTo(width, y);
|
||
|
|
}
|
||
|
|
|
||
|
|
for (let col = 0; col <= state.cols; col += 1) {
|
||
|
|
const x = Math.round(col * cellWidth) + 0.5;
|
||
|
|
context.moveTo(x, 0);
|
||
|
|
context.lineTo(x, height);
|
||
|
|
}
|
||
|
|
|
||
|
|
context.stroke();
|
||
|
|
|
||
|
|
for (let rowIndex = 0; rowIndex < state.rows; rowIndex += 1) {
|
||
|
|
for (let colIndex = 0; colIndex < state.cols; colIndex += 1) {
|
||
|
|
if (!state.grid[rowIndex][colIndex]) {
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
const x = colIndex * cellWidth + inset;
|
||
|
|
const y = rowIndex * cellHeight + inset;
|
||
|
|
const drawWidth = Math.max(2, cellWidth - inset * 2);
|
||
|
|
const drawHeight = Math.max(2, cellHeight - inset * 2);
|
||
|
|
const fill = context.createLinearGradient(x, y, x + drawWidth, y + drawHeight);
|
||
|
|
|
||
|
|
fill.addColorStop(0, 'rgba(115, 240, 221, 0.98)');
|
||
|
|
fill.addColorStop(1, 'rgba(190, 252, 125, 0.94)');
|
||
|
|
context.fillStyle = fill;
|
||
|
|
context.shadowColor = 'rgba(115, 240, 221, 0.4)';
|
||
|
|
context.shadowBlur = Math.max(8, Math.min(cellWidth, cellHeight) * 0.66);
|
||
|
|
context.fillRect(x, y, drawWidth, drawHeight);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
context.shadowBlur = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
function renderState(state, refs) {
|
||
|
|
resizeCanvas(refs.canvas);
|
||
|
|
drawSimulation(refs.canvas, state);
|
||
|
|
|
||
|
|
refs.statusText.textContent = state.running ? 'Running' : 'Paused';
|
||
|
|
refs.statusText.classList.toggle('running', state.running);
|
||
|
|
refs.speedLabel.textContent = getSpeedLabel(state.speed);
|
||
|
|
refs.speedReadout.textContent = state.speed + ' / 6';
|
||
|
|
refs.generationValue.textContent = String(state.generation);
|
||
|
|
refs.liveValue.textContent = String(state.liveCount);
|
||
|
|
refs.presetValue.textContent = getPatternLabel(state.selectedPattern);
|
||
|
|
refs.heroGeneration.textContent = String(state.generation);
|
||
|
|
refs.heroLive.textContent = String(state.liveCount);
|
||
|
|
refs.heroPattern.textContent = getPatternLabel(state.selectedPattern);
|
||
|
|
refs.canvasHint.textContent = state.running ? '演化进行中,拖拽会自动暂停' : '点击或拖拽即可绘制细胞';
|
||
|
|
refs.playToggle.textContent = state.running ? '暂停演化' : '开始演化';
|
||
|
|
refs.stepButton.disabled = state.running;
|
||
|
|
refs.speedSlider.value = String(state.speed);
|
||
|
|
|
||
|
|
refs.presetButtons.forEach(function (button) {
|
||
|
|
button.classList.toggle('active', button.dataset.pattern === state.selectedPattern);
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
function initDemo() {
|
||
|
|
const canvas = document.getElementById('life-canvas');
|
||
|
|
|
||
|
|
if (!canvas) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const refs = {
|
||
|
|
canvas: canvas,
|
||
|
|
canvasHint: document.getElementById('canvas-hint'),
|
||
|
|
clearButton: document.getElementById('clear-button'),
|
||
|
|
generationValue: document.getElementById('generation-value'),
|
||
|
|
heroGeneration: document.getElementById('hero-generation'),
|
||
|
|
heroLive: document.getElementById('hero-live'),
|
||
|
|
heroPattern: document.getElementById('hero-pattern'),
|
||
|
|
liveValue: document.getElementById('live-value'),
|
||
|
|
loadDefaultHero: document.getElementById('load-default-hero'),
|
||
|
|
playToggle: document.getElementById('play-toggle'),
|
||
|
|
presetButtons: Array.from(document.querySelectorAll('[data-pattern]')),
|
||
|
|
presetValue: document.getElementById('preset-value'),
|
||
|
|
randomButton: document.getElementById('random-button'),
|
||
|
|
resetButton: document.getElementById('reset-button'),
|
||
|
|
speedLabel: document.getElementById('speed-label'),
|
||
|
|
speedReadout: document.getElementById('speed-readout'),
|
||
|
|
speedSlider: document.getElementById('speed-slider'),
|
||
|
|
statusText: document.getElementById('status-text'),
|
||
|
|
stepButton: document.getElementById('step-button'),
|
||
|
|
};
|
||
|
|
const baseSize = global.innerWidth <= 720 ? 34 : 44;
|
||
|
|
let state = createInitialState({
|
||
|
|
rows: baseSize,
|
||
|
|
cols: baseSize,
|
||
|
|
defaultPattern: 'pulsar',
|
||
|
|
});
|
||
|
|
let lastTick = 0;
|
||
|
|
let drawing = false;
|
||
|
|
let drawValue = 1;
|
||
|
|
let lastCellKey = '';
|
||
|
|
|
||
|
|
function setState(nextState) {
|
||
|
|
state = nextState;
|
||
|
|
renderState(state, refs);
|
||
|
|
}
|
||
|
|
|
||
|
|
function applyCanvasPoint(event) {
|
||
|
|
const point = getCanvasPoint(event, refs.canvas, state);
|
||
|
|
|
||
|
|
if (!point) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
const cellKey = point.row + ':' + point.col;
|
||
|
|
|
||
|
|
if (cellKey === lastCellKey) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
lastCellKey = cellKey;
|
||
|
|
const nextGrid = toggleCell(state.grid, point.row, point.col, drawValue);
|
||
|
|
setState(replaceGrid(state, nextGrid, 'custom'));
|
||
|
|
}
|
||
|
|
|
||
|
|
refs.playToggle.addEventListener('click', function () {
|
||
|
|
setState(Object.assign({}, state, {
|
||
|
|
running: !state.running,
|
||
|
|
}));
|
||
|
|
});
|
||
|
|
|
||
|
|
refs.stepButton.addEventListener('click', function () {
|
||
|
|
if (!state.running) {
|
||
|
|
setState(advanceState(state));
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
refs.clearButton.addEventListener('click', function () {
|
||
|
|
setState(replaceGrid(state, createEmptyLike(state), 'custom'));
|
||
|
|
});
|
||
|
|
|
||
|
|
refs.randomButton.addEventListener('click', function () {
|
||
|
|
setState(replaceGrid(state, randomizeGrid(createEmptyLike(state), 0.22), 'custom'));
|
||
|
|
});
|
||
|
|
|
||
|
|
refs.resetButton.addEventListener('click', function () {
|
||
|
|
const patternName = PATTERNS[state.selectedPattern] ? state.selectedPattern : state.defaultPattern;
|
||
|
|
setState(applyPreset(state, patternName));
|
||
|
|
});
|
||
|
|
|
||
|
|
refs.loadDefaultHero.addEventListener('click', function () {
|
||
|
|
setState(applyPreset(state, state.defaultPattern));
|
||
|
|
document.getElementById('lab').scrollIntoView({ behavior: 'smooth', block: 'start' });
|
||
|
|
});
|
||
|
|
|
||
|
|
refs.speedSlider.addEventListener('input', function (event) {
|
||
|
|
const speed = Number(event.target.value);
|
||
|
|
setState(setSpeed(state, speed));
|
||
|
|
});
|
||
|
|
|
||
|
|
refs.presetButtons.forEach(function (button) {
|
||
|
|
button.addEventListener('click', function () {
|
||
|
|
setState(applyPreset(state, button.dataset.pattern));
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
refs.canvas.addEventListener('pointerdown', function (event) {
|
||
|
|
const point = getCanvasPoint(event, refs.canvas, state);
|
||
|
|
|
||
|
|
if (!point) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
drawing = true;
|
||
|
|
lastCellKey = '';
|
||
|
|
drawValue = state.grid[point.row][point.col] ? 0 : 1;
|
||
|
|
refs.canvas.setPointerCapture(event.pointerId);
|
||
|
|
applyCanvasPoint(event);
|
||
|
|
});
|
||
|
|
|
||
|
|
refs.canvas.addEventListener('pointermove', function (event) {
|
||
|
|
if (!drawing) {
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
applyCanvasPoint(event);
|
||
|
|
});
|
||
|
|
|
||
|
|
function stopDrawing(event) {
|
||
|
|
if (drawing && event && refs.canvas.hasPointerCapture(event.pointerId)) {
|
||
|
|
refs.canvas.releasePointerCapture(event.pointerId);
|
||
|
|
}
|
||
|
|
|
||
|
|
drawing = false;
|
||
|
|
lastCellKey = '';
|
||
|
|
}
|
||
|
|
|
||
|
|
refs.canvas.addEventListener('pointerup', stopDrawing);
|
||
|
|
refs.canvas.addEventListener('pointerleave', stopDrawing);
|
||
|
|
|
||
|
|
global.addEventListener('resize', function () {
|
||
|
|
renderState(state, refs);
|
||
|
|
});
|
||
|
|
|
||
|
|
function animate(timestamp) {
|
||
|
|
if (state.running && timestamp - lastTick >= getSpeedDelay(state.speed)) {
|
||
|
|
lastTick = timestamp;
|
||
|
|
setState(advanceState(state));
|
||
|
|
}
|
||
|
|
|
||
|
|
global.requestAnimationFrame(animate);
|
||
|
|
}
|
||
|
|
|
||
|
|
renderState(state, refs);
|
||
|
|
global.requestAnimationFrame(animate);
|
||
|
|
}
|
||
|
|
|
||
|
|
const api = {
|
||
|
|
advanceState: advanceState,
|
||
|
|
applyPreset: applyPreset,
|
||
|
|
PATTERNS: PATTERNS,
|
||
|
|
cloneGrid: cloneGrid,
|
||
|
|
countLiveNeighbors: countLiveNeighbors,
|
||
|
|
countLiveCells: countLiveCells,
|
||
|
|
createInitialState: createInitialState,
|
||
|
|
createEmptyGrid: createEmptyGrid,
|
||
|
|
getSpeedDelay: getSpeedDelay,
|
||
|
|
getSpeedLabel: getSpeedLabel,
|
||
|
|
randomizeGrid: randomizeGrid,
|
||
|
|
setSpeed: setSpeed,
|
||
|
|
stampPattern: stampPattern,
|
||
|
|
stepGrid: stepGrid,
|
||
|
|
toggleCell: toggleCell,
|
||
|
|
};
|
||
|
|
|
||
|
|
if (typeof module !== 'undefined' && module.exports) {
|
||
|
|
module.exports = api;
|
||
|
|
}
|
||
|
|
|
||
|
|
global.LifeDemo = api;
|
||
|
|
|
||
|
|
if (typeof document !== 'undefined') {
|
||
|
|
document.addEventListener('DOMContentLoaded', initDemo);
|
||
|
|
}
|
||
|
|
})(typeof window !== 'undefined' ? window : globalThis);
|