import { cellCoordsToIndex } from "./utils.js"; export class LifeEngine { constructor(rows, cols, { wrap = false } = {}) { this.rows = rows; this.cols = cols; this.wrap = wrap; this.generation = 0; this.current = new Uint8Array(rows * cols); this.next = new Uint8Array(rows * cols); this.liveCount = 0; } index(row, col) { return cellCoordsToIndex(row, col, this.cols); } inBounds(row, col) { return row >= 0 && row < this.rows && col >= 0 && col < this.cols; } getCell(row, col) { if (!this.inBounds(row, col)) { return 0; } return this.current[this.index(row, col)]; } setCell(row, col, value) { if (!this.inBounds(row, col)) { return; } const idx = this.index(row, col); const normalized = value ? 1 : 0; if (this.current[idx] === normalized) { return; } this.liveCount += normalized === 1 ? 1 : -1; this.current[idx] = normalized; } toggleCell(row, col) { if (!this.inBounds(row, col)) { return; } const idx = this.index(row, col); const nextValue = this.current[idx] ? 0 : 1; this.current[idx] = nextValue; this.liveCount += nextValue ? 1 : -1; } clear() { this.current.fill(0); this.next.fill(0); this.generation = 0; this.liveCount = 0; } randomize(probability = 0.28) { this.generation = 0; let live = 0; for (let i = 0; i < this.current.length; i += 1) { const alive = Math.random() < probability ? 1 : 0; this.current[i] = alive; live += alive; } this.next.fill(0); this.liveCount = live; } countNeighbors(row, col) { let count = 0; for (let dr = -1; dr <= 1; dr += 1) { for (let dc = -1; dc <= 1; dc += 1) { if (dr === 0 && dc === 0) { continue; } let nr = row + dr; let nc = col + dc; if (this.wrap) { nr = (nr + this.rows) % this.rows; nc = (nc + this.cols) % this.cols; count += this.current[this.index(nr, nc)]; continue; } if (this.inBounds(nr, nc)) { count += this.current[this.index(nr, nc)]; } } } return count; } step() { const { rows, cols } = this; let live = 0; for (let row = 0; row < rows; row += 1) { for (let col = 0; col < cols; col += 1) { const idx = this.index(row, col); const alive = this.current[idx] === 1; const neighbors = this.countNeighbors(row, col); const nextAlive = neighbors === 3 || (alive && neighbors === 2); this.next[idx] = nextAlive ? 1 : 0; live += this.next[idx]; } } const temp = this.current; this.current = this.next; this.next = temp; this.generation += 1; this.liveCount = live; } resize(newRows, newCols) { const oldRows = this.rows; const oldCols = this.cols; const oldCurrent = this.current; this.rows = newRows; this.cols = newCols; this.current = new Uint8Array(newRows * newCols); this.next = new Uint8Array(newRows * newCols); const copyRows = Math.min(oldRows, newRows); const copyCols = Math.min(oldCols, newCols); let live = 0; for (let row = 0; row < copyRows; row += 1) { for (let col = 0; col < copyCols; col += 1) { const oldIdx = row * oldCols + col; const newIdx = row * newCols + col; const value = oldCurrent[oldIdx]; this.current[newIdx] = value; live += value; } } this.liveCount = live; } pastePattern(patternCells, anchorRow, anchorCol, mode = "set") { for (const [dr, dc] of patternCells) { let row = anchorRow + dr; let col = anchorCol + dc; if (this.wrap) { row = (row + this.rows) % this.rows; col = (col + this.cols) % this.cols; } if (!this.inBounds(row, col)) { continue; } if (mode === "toggle") { this.toggleCell(row, col); } else { this.setCell(row, col, 1); } } } }