Files
conways-game-of-life/js/engine.js

161 lines
4.0 KiB
JavaScript

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);
}
}
}
}