"use client"; import type { MouseEvent } from "react"; import { useEffect, useMemo, useRef, useState } from "react"; import { Button } from "@/components/ui/button"; function createGrid(cols: number, rows: number) { return Array.from({ length: rows }, () => Array.from({ length: cols }, () => 0)); } function seedRandom(grid: number[][], density: number) { for (let y = 0; y < grid.length; y += 1) { for (let x = 0; x < grid[0].length; x += 1) { grid[y][x] = Math.random() < density ? 1 : 0; } } } function stepGrid(grid: number[][]) { const rows = grid.length; const cols = grid[0]?.length ?? 0; const next = createGrid(cols, rows); for (let y = 0; y < rows; y += 1) { const yPrev = (y - 1 + rows) % rows; const yNext = (y + 1) % rows; for (let x = 0; x < cols; x += 1) { const xPrev = (x - 1 + cols) % cols; const xNext = (x + 1) % cols; const neighbors = grid[yPrev][xPrev] + grid[yPrev][x] + grid[yPrev][xNext] + grid[y][xPrev] + grid[y][xNext] + grid[yNext][xPrev] + grid[yNext][x] + grid[yNext][xNext]; if (grid[y][x]) { next[y][x] = neighbors === 2 || neighbors === 3 ? 1 : 0; } else { next[y][x] = neighbors === 3 ? 1 : 0; } } } return next; } export function GameOfLifeMini() { const canvasRef = useRef(null); const [running, setRunning] = useState(true); const [speed, setSpeed] = useState(6); const cellSize = 10; const width = 420; const height = 260; const { rows, cols } = useMemo( () => ({ cols: Math.floor(width / cellSize), rows: Math.floor(height / cellSize), }), [], ); const gridRef = useRef(createGrid(cols, rows)); useEffect(() => { gridRef.current = createGrid(cols, rows); seedRandom(gridRef.current, 0.22); }, [cols, rows]); useEffect(() => { const canvas = canvasRef.current; if (!canvas) return; const ctx = canvas.getContext("2d"); if (!ctx) return; let raf = 0; let last = performance.now(); const tickMs = 1000 / speed; const draw = () => { ctx.clearRect(0, 0, width, height); ctx.fillStyle = "#141414"; ctx.fillRect(0, 0, width, height); const grid = gridRef.current; ctx.fillStyle = "#f2f2f2"; for (let y = 0; y < rows; y += 1) { for (let x = 0; x < cols; x += 1) { if (grid[y][x]) { ctx.fillRect( x * cellSize, y * cellSize, cellSize - 1, cellSize - 1, ); } } } }; const loop = (now: number) => { if (running && now - last >= tickMs) { gridRef.current = stepGrid(gridRef.current); last = now; } draw(); raf = requestAnimationFrame(loop); }; raf = requestAnimationFrame(loop); return () => cancelAnimationFrame(raf); }, [running, speed, cols, rows]); const handleToggleCell = (event: MouseEvent) => { const rect = event.currentTarget.getBoundingClientRect(); const x = Math.floor((event.clientX - rect.left) / cellSize); const y = Math.floor((event.clientY - rect.top) / cellSize); if (x < 0 || y < 0 || y >= rows || x >= cols) return; gridRef.current[y][x] = gridRef.current[y][x] ? 0 : 1; }; return (
Game of Life
Click to toggle cells. Space-time improvisation.
Speed
{speed}
); }