Implement app controller, patterns, controls, and import/export
This commit is contained in:
555
js/app.js
Normal file
555
js/app.js
Normal file
@@ -0,0 +1,555 @@
|
||||
import { LifeEngine } from "./engine.js";
|
||||
import { Renderer } from "./renderer.js";
|
||||
import { getPatternByKey, PATTERNS } from "./patterns.js";
|
||||
import { clamp, debounce, listLiveCells, parsePositiveInt } from "./utils.js";
|
||||
|
||||
class GameOfLifeApp {
|
||||
constructor() {
|
||||
this.elements = this.getElements();
|
||||
|
||||
this.state = {
|
||||
speed: 10,
|
||||
cellSize: 12,
|
||||
running: false,
|
||||
showGrid: true,
|
||||
stampMode: false,
|
||||
wrap: false,
|
||||
drawValue: 1,
|
||||
pointerDown: false,
|
||||
lastPaintedCell: null,
|
||||
simulationTimerId: null,
|
||||
};
|
||||
|
||||
const { rows, cols } = this.computeGridDimensions(this.state.cellSize);
|
||||
this.engine = new LifeEngine(rows, cols, { wrap: this.state.wrap });
|
||||
this.renderer = new Renderer(this.elements.canvas, {
|
||||
cellSize: this.state.cellSize,
|
||||
});
|
||||
this.renderer.resizeCanvas(rows, cols);
|
||||
|
||||
this.applyThemeFromStorage();
|
||||
this.populatePatternSelect();
|
||||
this.bindEvents();
|
||||
this.updateStaticUiState();
|
||||
this.render();
|
||||
}
|
||||
|
||||
getElements() {
|
||||
return {
|
||||
canvas: document.getElementById("life-canvas"),
|
||||
canvasContainer: document.getElementById("canvas-container"),
|
||||
playPauseBtn: document.getElementById("play-pause-btn"),
|
||||
stepBtn: document.getElementById("step-btn"),
|
||||
clearBtn: document.getElementById("clear-btn"),
|
||||
randomizeBtn: document.getElementById("randomize-btn"),
|
||||
speedRange: document.getElementById("speed-range"),
|
||||
speedValue: document.getElementById("speed-value"),
|
||||
cellSizeRange: document.getElementById("cell-size-range"),
|
||||
cellSizeValue: document.getElementById("cell-size-value"),
|
||||
wrapToggle: document.getElementById("wrap-toggle"),
|
||||
gridlineToggle: document.getElementById("gridline-toggle"),
|
||||
generationCount: document.getElementById("generation-count"),
|
||||
liveCount: document.getElementById("live-count"),
|
||||
boardSize: document.getElementById("board-size"),
|
||||
boundaryMode: document.getElementById("boundary-mode"),
|
||||
patternSelect: document.getElementById("pattern-select"),
|
||||
stampBtn: document.getElementById("stamp-btn"),
|
||||
cancelStampBtn: document.getElementById("cancel-stamp-btn"),
|
||||
toolStatus: document.getElementById("tool-status"),
|
||||
ioFormat: document.getElementById("io-format"),
|
||||
stateText: document.getElementById("state-text"),
|
||||
exportBtn: document.getElementById("export-btn"),
|
||||
importBtn: document.getElementById("import-btn"),
|
||||
helpPanel: document.getElementById("help-panel"),
|
||||
themeToggle: document.getElementById("theme-toggle"),
|
||||
};
|
||||
}
|
||||
|
||||
computeGridDimensions(cellSize) {
|
||||
const width = Math.max(320, this.elements.canvasContainer.clientWidth);
|
||||
const maxHeight = Math.max(280, Math.floor(window.innerHeight * 0.58));
|
||||
const cols = Math.max(20, Math.floor(width / cellSize));
|
||||
const rows = Math.max(20, Math.floor(maxHeight / cellSize));
|
||||
return { rows, cols };
|
||||
}
|
||||
|
||||
populatePatternSelect() {
|
||||
const { patternSelect } = this.elements;
|
||||
patternSelect.innerHTML = "";
|
||||
for (const pattern of PATTERNS) {
|
||||
const option = document.createElement("option");
|
||||
option.value = pattern.key;
|
||||
option.textContent = pattern.label;
|
||||
patternSelect.appendChild(option);
|
||||
}
|
||||
}
|
||||
|
||||
bindEvents() {
|
||||
const e = this.elements;
|
||||
|
||||
e.playPauseBtn.addEventListener("click", () => {
|
||||
if (this.state.running) {
|
||||
this.pause();
|
||||
} else {
|
||||
this.play();
|
||||
}
|
||||
});
|
||||
|
||||
e.stepBtn.addEventListener("click", () => {
|
||||
this.step();
|
||||
});
|
||||
|
||||
e.clearBtn.addEventListener("click", () => {
|
||||
this.pause();
|
||||
this.engine.clear();
|
||||
this.render();
|
||||
});
|
||||
|
||||
e.randomizeBtn.addEventListener("click", () => {
|
||||
this.pause();
|
||||
this.engine.randomize();
|
||||
this.render();
|
||||
});
|
||||
|
||||
e.speedRange.addEventListener("input", (event) => {
|
||||
const speed = clamp(parsePositiveInt(event.target.value, 10), 1, 30);
|
||||
this.state.speed = speed;
|
||||
e.speedValue.textContent = String(speed);
|
||||
if (this.state.running) {
|
||||
this.play(true);
|
||||
}
|
||||
});
|
||||
|
||||
e.cellSizeRange.addEventListener("input", (event) => {
|
||||
const nextSize = clamp(parsePositiveInt(event.target.value, 12), 6, 24);
|
||||
this.state.cellSize = nextSize;
|
||||
e.cellSizeValue.textContent = String(nextSize);
|
||||
this.renderer.setCellSize(nextSize);
|
||||
this.resizeBoardToViewport();
|
||||
});
|
||||
|
||||
e.wrapToggle.addEventListener("change", (event) => {
|
||||
this.state.wrap = event.target.checked;
|
||||
this.engine.wrap = this.state.wrap;
|
||||
this.render();
|
||||
});
|
||||
|
||||
e.gridlineToggle.addEventListener("change", (event) => {
|
||||
this.state.showGrid = event.target.checked;
|
||||
this.render();
|
||||
});
|
||||
|
||||
e.stampBtn.addEventListener("click", () => {
|
||||
this.state.stampMode = true;
|
||||
this.updateToolStatus();
|
||||
});
|
||||
|
||||
e.cancelStampBtn.addEventListener("click", () => {
|
||||
this.state.stampMode = false;
|
||||
this.updateToolStatus();
|
||||
});
|
||||
|
||||
e.exportBtn.addEventListener("click", () => {
|
||||
e.stateText.value = this.serializeBoard(e.ioFormat.value);
|
||||
});
|
||||
|
||||
e.importBtn.addEventListener("click", () => {
|
||||
try {
|
||||
this.importBoard(e.stateText.value.trim());
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : "Import failed.";
|
||||
e.stateText.value = `Import error: ${message}`;
|
||||
}
|
||||
});
|
||||
|
||||
e.themeToggle.addEventListener("click", () => {
|
||||
const next = document.documentElement.dataset.theme === "dark" ? "light" : "dark";
|
||||
this.setTheme(next);
|
||||
});
|
||||
|
||||
this.bindCanvasPointerEvents();
|
||||
|
||||
window.addEventListener(
|
||||
"resize",
|
||||
debounce(() => {
|
||||
this.resizeBoardToViewport();
|
||||
}, 180),
|
||||
);
|
||||
|
||||
document.addEventListener("keydown", (event) => this.handleKeyboard(event));
|
||||
}
|
||||
|
||||
bindCanvasPointerEvents() {
|
||||
const { canvas } = this.elements;
|
||||
|
||||
canvas.addEventListener("pointerdown", (event) => {
|
||||
event.preventDefault();
|
||||
const point = this.pointerEventToCell(event);
|
||||
if (!point) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.state.stampMode) {
|
||||
const pattern = getPatternByKey(this.elements.patternSelect.value);
|
||||
if (pattern) {
|
||||
this.engine.pastePattern(pattern.cells, point.row, point.col, "set");
|
||||
this.state.stampMode = false;
|
||||
this.updateToolStatus();
|
||||
this.render();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.state.pointerDown = true;
|
||||
this.state.lastPaintedCell = null;
|
||||
canvas.setPointerCapture(event.pointerId);
|
||||
this.state.drawValue = this.engine.getCell(point.row, point.col) === 1 ? 0 : 1;
|
||||
this.paintCell(point.row, point.col);
|
||||
});
|
||||
|
||||
canvas.addEventListener("pointermove", (event) => {
|
||||
if (!this.state.pointerDown) {
|
||||
return;
|
||||
}
|
||||
const point = this.pointerEventToCell(event);
|
||||
if (!point) {
|
||||
return;
|
||||
}
|
||||
this.paintCell(point.row, point.col);
|
||||
});
|
||||
|
||||
const endPointer = (event) => {
|
||||
this.state.pointerDown = false;
|
||||
this.state.lastPaintedCell = null;
|
||||
if (this.elements.canvas.hasPointerCapture(event.pointerId)) {
|
||||
this.elements.canvas.releasePointerCapture(event.pointerId);
|
||||
}
|
||||
};
|
||||
canvas.addEventListener("pointerup", endPointer);
|
||||
canvas.addEventListener("pointercancel", endPointer);
|
||||
canvas.addEventListener("pointerleave", endPointer);
|
||||
}
|
||||
|
||||
pointerEventToCell(event) {
|
||||
const rect = this.elements.canvas.getBoundingClientRect();
|
||||
if (rect.width === 0 || rect.height === 0) {
|
||||
return null;
|
||||
}
|
||||
const scaleX = this.elements.canvas.width / rect.width;
|
||||
const scaleY = this.elements.canvas.height / rect.height;
|
||||
const x = (event.clientX - rect.left) * scaleX;
|
||||
const y = (event.clientY - rect.top) * scaleY;
|
||||
|
||||
if (x < 0 || y < 0 || x >= this.elements.canvas.width || y >= this.elements.canvas.height) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const col = Math.floor(x / this.state.cellSize);
|
||||
const row = Math.floor(y / this.state.cellSize);
|
||||
if (!this.engine.inBounds(row, col)) {
|
||||
return null;
|
||||
}
|
||||
return { row, col };
|
||||
}
|
||||
|
||||
paintCell(row, col) {
|
||||
const key = `${row},${col}`;
|
||||
if (this.state.lastPaintedCell === key) {
|
||||
return;
|
||||
}
|
||||
this.state.lastPaintedCell = key;
|
||||
this.engine.setCell(row, col, this.state.drawValue);
|
||||
this.render();
|
||||
}
|
||||
|
||||
play(restart = false) {
|
||||
if (this.state.running && !restart) {
|
||||
return;
|
||||
}
|
||||
this.pause();
|
||||
this.state.running = true;
|
||||
const intervalMs = Math.max(16, Math.floor(1000 / this.state.speed));
|
||||
this.state.simulationTimerId = window.setInterval(() => {
|
||||
this.engine.step();
|
||||
this.render();
|
||||
}, intervalMs);
|
||||
this.updateRunButton();
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.state.running = false;
|
||||
if (this.state.simulationTimerId !== null) {
|
||||
window.clearInterval(this.state.simulationTimerId);
|
||||
this.state.simulationTimerId = null;
|
||||
}
|
||||
this.updateRunButton();
|
||||
}
|
||||
|
||||
step() {
|
||||
this.engine.step();
|
||||
this.render();
|
||||
}
|
||||
|
||||
resizeBoardToViewport() {
|
||||
const { rows, cols } = this.computeGridDimensions(this.state.cellSize);
|
||||
this.engine.resize(rows, cols);
|
||||
this.renderer.resizeCanvas(rows, cols);
|
||||
this.render();
|
||||
}
|
||||
|
||||
getThemeColors() {
|
||||
const rootStyle = getComputedStyle(document.documentElement);
|
||||
return {
|
||||
background: rootStyle.getPropertyValue("--canvas-bg").trim(),
|
||||
cell: rootStyle.getPropertyValue("--cell").trim(),
|
||||
grid: rootStyle.getPropertyValue("--border").trim(),
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
this.renderer.render(this.engine, {
|
||||
showGrid: this.state.showGrid,
|
||||
colors: this.getThemeColors(),
|
||||
});
|
||||
this.updateStatus();
|
||||
}
|
||||
|
||||
updateStatus() {
|
||||
const e = this.elements;
|
||||
e.generationCount.textContent = String(this.engine.generation);
|
||||
e.liveCount.textContent = String(this.engine.liveCount);
|
||||
e.boardSize.textContent = `${this.engine.cols} × ${this.engine.rows}`;
|
||||
e.boundaryMode.textContent = this.state.wrap ? "Toroidal" : "Fixed";
|
||||
}
|
||||
|
||||
updateRunButton() {
|
||||
this.elements.playPauseBtn.textContent = this.state.running ? "⏸ Pause" : "▶ Play";
|
||||
}
|
||||
|
||||
updateToolStatus() {
|
||||
this.elements.toolStatus.textContent = this.state.stampMode
|
||||
? "Stamp mode active: click on the board to place the selected pattern."
|
||||
: "Draw mode: click and drag to paint cells.";
|
||||
}
|
||||
|
||||
updateStaticUiState() {
|
||||
const e = this.elements;
|
||||
e.speedRange.value = String(this.state.speed);
|
||||
e.speedValue.textContent = String(this.state.speed);
|
||||
e.cellSizeRange.value = String(this.state.cellSize);
|
||||
e.cellSizeValue.textContent = String(this.state.cellSize);
|
||||
e.gridlineToggle.checked = this.state.showGrid;
|
||||
e.wrapToggle.checked = this.state.wrap;
|
||||
this.updateRunButton();
|
||||
this.updateToolStatus();
|
||||
}
|
||||
|
||||
serializeBoard(format) {
|
||||
const liveCells = listLiveCells(this.engine.current, this.engine.rows, this.engine.cols);
|
||||
if (format === "compact") {
|
||||
const header = `GOL1 ${this.engine.rows} ${this.engine.cols} ${this.engine.generation} ${
|
||||
this.state.wrap ? 1 : 0
|
||||
}`;
|
||||
const body = liveCells.map(([row, col]) => `${row},${col}`).join(";");
|
||||
return `${header}\n${body}`;
|
||||
}
|
||||
return JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
rows: this.engine.rows,
|
||||
cols: this.engine.cols,
|
||||
generation: this.engine.generation,
|
||||
wrap: this.state.wrap,
|
||||
liveCells,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
importBoard(text) {
|
||||
if (!text) {
|
||||
return;
|
||||
}
|
||||
this.pause();
|
||||
|
||||
if (text.startsWith("{")) {
|
||||
this.importJson(text);
|
||||
return;
|
||||
}
|
||||
this.importCompact(text);
|
||||
}
|
||||
|
||||
importJson(text) {
|
||||
const parsed = JSON.parse(text);
|
||||
const rows = clamp(parsePositiveInt(parsed.rows, this.engine.rows), 10, 400);
|
||||
const cols = clamp(parsePositiveInt(parsed.cols, this.engine.cols), 10, 600);
|
||||
this.engine.resize(rows, cols);
|
||||
this.engine.clear();
|
||||
|
||||
if (Array.isArray(parsed.liveCells)) {
|
||||
for (const cell of parsed.liveCells) {
|
||||
if (!Array.isArray(cell) || cell.length < 2) {
|
||||
continue;
|
||||
}
|
||||
const row = Number.parseInt(cell[0], 10);
|
||||
const col = Number.parseInt(cell[1], 10);
|
||||
this.engine.setCell(row, col, 1);
|
||||
}
|
||||
}
|
||||
|
||||
const generation = Number.parseInt(parsed.generation, 10);
|
||||
this.engine.generation = Number.isFinite(generation) && generation >= 0 ? generation : 0;
|
||||
|
||||
this.state.wrap = Boolean(parsed.wrap);
|
||||
this.engine.wrap = this.state.wrap;
|
||||
this.elements.wrapToggle.checked = this.state.wrap;
|
||||
|
||||
this.renderer.resizeCanvas(this.engine.rows, this.engine.cols);
|
||||
this.render();
|
||||
}
|
||||
|
||||
importCompact(text) {
|
||||
const lines = text.split(/\r?\n/);
|
||||
if (lines.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const header = lines[0].trim().split(/\s+/);
|
||||
if (header[0] !== "GOL1" || header.length < 5) {
|
||||
throw new Error("Invalid compact format header.");
|
||||
}
|
||||
|
||||
const rows = clamp(parsePositiveInt(header[1], this.engine.rows), 10, 400);
|
||||
const cols = clamp(parsePositiveInt(header[2], this.engine.cols), 10, 600);
|
||||
const generation = Math.max(0, Number.parseInt(header[3], 10) || 0);
|
||||
const wrap = header[4] === "1";
|
||||
|
||||
this.engine.resize(rows, cols);
|
||||
this.engine.clear();
|
||||
this.engine.generation = generation;
|
||||
this.state.wrap = wrap;
|
||||
this.engine.wrap = wrap;
|
||||
this.elements.wrapToggle.checked = wrap;
|
||||
|
||||
const body = lines.slice(1).join("").trim();
|
||||
if (body.length > 0) {
|
||||
for (const pair of body.split(";")) {
|
||||
if (!pair) {
|
||||
continue;
|
||||
}
|
||||
const [rowRaw, colRaw] = pair.split(",");
|
||||
const row = Number.parseInt(rowRaw, 10);
|
||||
const col = Number.parseInt(colRaw, 10);
|
||||
this.engine.setCell(row, col, 1);
|
||||
}
|
||||
}
|
||||
|
||||
this.renderer.resizeCanvas(this.engine.rows, this.engine.cols);
|
||||
this.render();
|
||||
}
|
||||
|
||||
handleKeyboard(event) {
|
||||
const activeTag = document.activeElement?.tagName?.toLowerCase();
|
||||
if (activeTag === "input" || activeTag === "textarea" || activeTag === "select") {
|
||||
if (event.key !== "Escape") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
switch (event.key) {
|
||||
case " ":
|
||||
event.preventDefault();
|
||||
this.state.running ? this.pause() : this.play();
|
||||
break;
|
||||
case "s":
|
||||
case "S":
|
||||
event.preventDefault();
|
||||
this.step();
|
||||
break;
|
||||
case "c":
|
||||
case "C":
|
||||
event.preventDefault();
|
||||
this.pause();
|
||||
this.engine.clear();
|
||||
this.render();
|
||||
break;
|
||||
case "r":
|
||||
case "R":
|
||||
event.preventDefault();
|
||||
this.pause();
|
||||
this.engine.randomize();
|
||||
this.render();
|
||||
break;
|
||||
case "g":
|
||||
case "G":
|
||||
event.preventDefault();
|
||||
this.state.showGrid = !this.state.showGrid;
|
||||
this.elements.gridlineToggle.checked = this.state.showGrid;
|
||||
this.render();
|
||||
break;
|
||||
case "w":
|
||||
case "W":
|
||||
event.preventDefault();
|
||||
this.state.wrap = !this.state.wrap;
|
||||
this.engine.wrap = this.state.wrap;
|
||||
this.elements.wrapToggle.checked = this.state.wrap;
|
||||
this.render();
|
||||
break;
|
||||
case "+":
|
||||
case "=":
|
||||
event.preventDefault();
|
||||
this.adjustCellSize(1);
|
||||
break;
|
||||
case "-":
|
||||
case "_":
|
||||
event.preventDefault();
|
||||
this.adjustCellSize(-1);
|
||||
break;
|
||||
case "?":
|
||||
event.preventDefault();
|
||||
this.elements.helpPanel.open = !this.elements.helpPanel.open;
|
||||
break;
|
||||
case "Escape":
|
||||
if (this.state.stampMode) {
|
||||
this.state.stampMode = false;
|
||||
this.updateToolStatus();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
adjustCellSize(delta) {
|
||||
const next = clamp(this.state.cellSize + delta, 6, 24);
|
||||
if (next === this.state.cellSize) {
|
||||
return;
|
||||
}
|
||||
this.state.cellSize = next;
|
||||
this.renderer.setCellSize(next);
|
||||
this.elements.cellSizeRange.value = String(next);
|
||||
this.elements.cellSizeValue.textContent = String(next);
|
||||
this.resizeBoardToViewport();
|
||||
}
|
||||
|
||||
applyThemeFromStorage() {
|
||||
const storedTheme = localStorage.getItem("gol-theme");
|
||||
if (storedTheme === "dark" || storedTheme === "light") {
|
||||
this.setTheme(storedTheme, false);
|
||||
return;
|
||||
}
|
||||
this.setTheme("dark", false);
|
||||
}
|
||||
|
||||
setTheme(theme, rerender = true) {
|
||||
document.documentElement.dataset.theme = theme;
|
||||
this.elements.themeToggle.textContent = theme === "dark" ? "☀️ Light mode" : "🌙 Dark mode";
|
||||
localStorage.setItem("gol-theme", theme);
|
||||
if (rerender) {
|
||||
this.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new GameOfLifeApp();
|
||||
157
js/patterns.js
Normal file
157
js/patterns.js
Normal file
@@ -0,0 +1,157 @@
|
||||
const block = [
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
];
|
||||
|
||||
const blinker = [
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
];
|
||||
|
||||
const toad = [
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
[0, 3],
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[1, 2],
|
||||
];
|
||||
|
||||
const beacon = [
|
||||
[0, 0],
|
||||
[0, 1],
|
||||
[1, 0],
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
[2, 3],
|
||||
[3, 2],
|
||||
[3, 3],
|
||||
];
|
||||
|
||||
const glider = [
|
||||
[0, 1],
|
||||
[1, 2],
|
||||
[2, 0],
|
||||
[2, 1],
|
||||
[2, 2],
|
||||
];
|
||||
|
||||
const lightweightSpaceship = [
|
||||
[0, 1],
|
||||
[0, 2],
|
||||
[0, 3],
|
||||
[0, 4],
|
||||
[1, 0],
|
||||
[1, 4],
|
||||
[2, 4],
|
||||
[3, 0],
|
||||
[3, 3],
|
||||
];
|
||||
|
||||
const pulsar = [
|
||||
[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],
|
||||
];
|
||||
|
||||
const gosperGliderGun = [
|
||||
[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],
|
||||
];
|
||||
|
||||
export const PATTERNS = [
|
||||
{ key: "block", label: "Block (still life)", cells: block },
|
||||
{ key: "blinker", label: "Blinker (oscillator)", cells: blinker },
|
||||
{ key: "toad", label: "Toad (oscillator)", cells: toad },
|
||||
{ key: "beacon", label: "Beacon (oscillator)", cells: beacon },
|
||||
{ key: "glider", label: "Glider", cells: glider },
|
||||
{ key: "lwss", label: "Lightweight spaceship", cells: lightweightSpaceship },
|
||||
{ key: "pulsar", label: "Pulsar", cells: pulsar },
|
||||
{ key: "gosper", label: "Gosper glider gun", cells: gosperGliderGun },
|
||||
];
|
||||
|
||||
export function getPatternByKey(key) {
|
||||
return PATTERNS.find((pattern) => pattern.key === key) ?? null;
|
||||
}
|
||||
Reference in New Issue
Block a user