Files
js-canvas-playground/life.ts

113 lines
3.4 KiB
TypeScript

import { Point } from "./common.js"
const SCALE = 4;
const times: number[] = [];
let fps: number;
function addCells(p: Point, state: number[], gridWidth: number) {
const x = Math.floor(p.x / SCALE);
const y = Math.floor(p.y / SCALE);
for (let i=-5; i < 5; i++) {
for (let j=-5; j < 5; j++) {
if (/*Math.random() * 2 < 1*/true) {
state[(y + j) * gridWidth + x + i] = 1;
}
}
}
}
function init() {
const canvas = document.getElementById("canvas") as HTMLCanvasElement | null;
if (!canvas) throw new Error("unable to get canvas HTML element");
const ctx = canvas.getContext("2d", { alpha: false }) as CanvasRenderingContext2D | null;
if (!ctx) throw new Error("unable to get canvas 2D context");
ctx.canvas.width = window.innerWidth
ctx.canvas.height = window.innerHeight
const gridWidth = Math.floor(ctx.canvas.width / SCALE)
const gridHeight = Math.floor(ctx.canvas.height / SCALE)
const cells = new Array(gridWidth * gridHeight)
cells.fill(0)
const state = new Array(gridWidth * gridHeight)
state.fill(0)
for (let i=0; i < gridWidth * gridHeight; i++) state[i] = +(Math.random() * 4 < 1)
canvas.addEventListener("click", (evt) => {
addCells({ x: evt.clientX, y: evt.clientY }, state, gridWidth);
});
canvas.ontouchstart = (evt: TouchEvent) => {
for (const e of evt.touches) {
addCells({ x: e.clientX, y: e.clientY }, state, gridWidth);
}
};
window.requestAnimationFrame(t => update(ctx, cells, state));
}
// update loop, called every frame
function update(ctx: CanvasRenderingContext2D, cells: number[], state: number[]) {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
const gridWidth = Math.floor(ctx.canvas.width / SCALE)
const gridHeight = Math.floor(ctx.canvas.height / SCALE)
for (let i=0; i < gridWidth * gridHeight; i++) cells[i] = state[i];
ctx.beginPath(); // batch draw calls, only once per frame
ctx.fillStyle = "white";
for (let y=1; y < gridHeight - 1; y++) {
for (let x=1; x < gridWidth - 1; x++) {
const pos = gridWidth * y + x;
// count neighbors
const n = cells[pos - 1 - gridWidth]
+ cells[pos - gridWidth]
+ cells[pos + 1 - gridWidth]
+ cells[pos - 1]
+ cells[pos + 1]
+ cells[pos - 1 + gridWidth]
+ cells[pos + gridWidth]
+ cells[pos + 1 + gridWidth];
const cell = cells[pos];
if (cell == 1) {
state[pos] = +(n == 2 || n == 3)
} else {
state[pos] = +(n == 3)
}
if (cell == 1) {
ctx.moveTo(x * SCALE, y * SCALE);
ctx.rect(x * SCALE, y * SCALE, SCALE, SCALE);
}
}
}
ctx.fill(); // draw only once per frame
const now = performance.now();
while (times.length > 0 && times[0] <= now - 1000) {
times.shift();
}
times.push(now);
fps = times.length;
ctx.save();
ctx.fillStyle = "red";
ctx.font = "bold 48px sans-serif";
ctx.shadowColor = "white";
ctx.fillText("" + fps, 10, 50);
ctx.restore();
window.requestAnimationFrame(t => update(ctx, cells, state));
}
init();