game.height = 400;
game.width = 500;
const BACKGROUND = "black";
const ctx = game.getContext("2d");
const DIFF_A = 1.0;
const DIFF_B = 0.5;
const FEED = 0.055;
const KILL = 0.06;
class PetriDish {
constructor(w, h) {
this.w = w;
this.h = h;
this.grid = [];
this.next = [];
for (let x = 0; x < w; x++) {
this.grid[x] = [];
this.next[x] = [];
for (let y = 0; y < h; y++) {
// Start with full Chemical A (1) and no Chemical B (0)
this.grid[x][y] = { a: 1, b: 0 };
this.next[x][y] = { a: 1, b: 0 };
}
}
}
seed(x, y, radius = 5) {
for (let i = x - radius; i < x + radius; i++) {
for (let j = y - radius; j < y + radius; j++) {
if (i > 0 && i < this.w && j > 0 && j < this.h) {
this.grid[i][j].b = 1;
}
}
}
}
// The Laplacian function: checks 8 neighbors to see how chemicals spread
laplace(x, y, chemical) {
let sum = 0;
sum += this.grid[x][y][chemical] * -1;
sum += this.grid[x - 1][y][chemical] * 0.2;
sum += this.grid[x + 1][y][chemical] * 0.2;
sum += this.grid[x][y - 1][chemical] * 0.2;
sum += this.grid[x][y + 1][chemical] * 0.2;
sum += this.grid[x - 1][y - 1][chemical] * 0.05;
sum += this.grid[x + 1][y - 1][chemical] * 0.05;
sum += this.grid[x - 1][y + 1][chemical] * 0.05;
sum += this.grid[x + 1][y + 1][chemical] * 0.05;
return sum;
}
update() {
for (let x = 1; x < this.w - 1; x++) {
for (let y = 1; y < this.h - 1; y++) {
let a = this.grid[x][y].a;
let b = this.grid[x][y].b;
let reaction = a * b * b;
this.next[x][y].a =
a + DIFF_A * this.laplace(x, y, "a") - reaction + FEED * (1 - a);
this.next[x][y].b =
b + DIFF_B * this.laplace(x, y, "b") + reaction - (KILL + FEED) * b;
// Clamp values between 0 and 1
this.next[x][y].a = Math.max(0, Math.min(1, this.next[x][y].a));
this.next[x][y].b = Math.max(0, Math.min(1, this.next[x][y].b));
}
}
// Swap grids
let temp = this.grid;
this.grid = this.next;
this.next = temp;
}
draw() {
const imgData = ctx.createImageData(this.w, this.h);
for (let x = 0; x < this.w; x++) {
for (let y = 0; y < this.h; y++) {
let pos = (x + y * this.w) * 4;
let val = Math.floor((this.grid[x][y].a - this.grid[x][y].b) * 255);
imgData.data[pos] = val; // Red
imgData.data[pos + 1] = val / 2; // Green (makes it look organic/yellow)
imgData.data[pos + 2] = 255 - val; // Blue
imgData.data[pos + 3] = 255; // Alpha
}
}
ctx.putImageData(imgData, 0, 0);
}
}
const dish = new PetriDish(game.width, game.height);
dish.seed(game.width / 2, game.height / 2, 10);
function loop() {
dish.update();
dish.draw();
requestAnimationFrame(loop);
}
loop();