diff --git a/components/Visualizer.tsx b/components/Visualizer.tsx index 88b7400..fc144a7 100644 --- a/components/Visualizer.tsx +++ b/components/Visualizer.tsx @@ -1,50 +1,12 @@ import React from "react"; import { Button } from "@/components/ui/button"; -import { - type Entity, - EntityType, - Position, - ZombieSurvival, -} from "@/simulators/zombie-survival"; -import { - type VisualizerContextImages, - useVisualizer, -} from "./VisualizerProvider"; +import { Renderer } from "@/renderer"; +import { ZombieSurvival } from "@/simulators/zombie-survival"; +import { useVisualizer } from "@/components/VisualizerProvider"; const AUTO_REPLAY_SPEED = 1_500; const REPLAY_SPEED = 600; -function getEntityImage( - entity: Entity, - images: VisualizerContextImages, -): HTMLImageElement { - switch (entity.getType()) { - case EntityType.Box: { - return images.box; - } - case EntityType.Player: { - return images.player; - } - case EntityType.Rock: { - return images.rock; - } - case EntityType.Zombie: { - return images.zombie; - } - } -} - -function getImageOffset(entity: Entity): Position { - if (entity.getType() === EntityType.Zombie) { - return { x: 16, y: 0 }; - } - return { x: 0, y: 0 }; -} - -function cloneMap(map: string[][]): string[][] { - return JSON.parse(JSON.stringify(map)); -} - export function Visualizer({ autoReplay = false, autoStart = false, @@ -62,36 +24,27 @@ export function Visualizer({ }) { const visualizer = useVisualizer(); const simulator = React.useRef(new ZombieSurvival(map)); + const renderer = React.useRef(null); const interval = React.useRef | null>(null); const timeout = React.useRef | null>(null); const canvas = React.useRef(null); - const running = React.useRef(false); - const cellSizeNum = Number.parseInt(cellSize, 10); - const h = ZombieSurvival.boardHeight(map) * cellSizeNum; - const w = ZombieSurvival.boardWidth(map) * cellSizeNum; + const [running, setRunning] = React.useState(false); React.useEffect(() => { - if (canvas.current !== null && visualizer.ready) { - setupCanvas(canvas.current); - } - }, [visualizer.ready]); - - function setupCanvas(canvas: HTMLCanvasElement) { - canvas.setAttribute("height", `${h * window.devicePixelRatio}`); - canvas.setAttribute("width", `${w * window.devicePixelRatio}`); - canvas.style.height = `${h}px`; - canvas.style.width = `${w}px`; - - const ctx = canvas.getContext("2d"); - - if (ctx !== null) { - setupContext(ctx); + if ( + canvas.current !== null && + visualizer.ready && + renderer.current === null + ) { + renderer.current = new Renderer( + visualizer.getAssets(), + ZombieSurvival.boardHeight(map), + ZombieSurvival.boardWidth(map), + canvas.current, + Number.parseInt(cellSize, 10), + ); } - } - - function setupContext(ctx: CanvasRenderingContext2D) { - ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - } + }, [canvas, visualizer.ready]); React.useEffect(() => { if (autoStart) { @@ -101,103 +54,41 @@ export function Visualizer({ function startSimulation() { simulator.current = new ZombieSurvival(map); - running.current = true; - interval.current = setInterval(stepSimulation, REPLAY_SPEED); - } - - function stepSimulation() { - if (simulator.current === null && running) { - return; - } - - if (!simulator.current.finished()) { - simulator.current.step(); - render(); - return; - } + setRunning(true); - clearInterval(interval.current!); - interval.current = null; + interval.current = setInterval(() => { + // if (!running) { + // return; + // } - if (autoReplay) { - timeout.current = setTimeout(() => { - timeout.current = null; - startSimulation(); - }, AUTO_REPLAY_SPEED); + if (!simulator.current.finished()) { + simulator.current.step(); - return; - } + if (renderer.current !== null) { + renderer.current.render(simulator.current.getAllAliveEntities()); + } - running.current = false; - - if (onSimulationEnd) { - onSimulationEnd(!simulator.current.getPlayer().dead()); - } - } - - function render() { - if (canvas.current !== null) { - const ctx = canvas.current.getContext("2d"); - - if (ctx !== null) { - renderCtx(ctx); + return; } - } - } - - function renderCtx(ctx: CanvasRenderingContext2D) { - renderCtxBg(ctx); - - const entities = simulator.current.getAllAliveEntities(); - const images = visualizer.getImages(); - for (const entity of entities) { - const entityImage = getEntityImage(entity, images); - const entityPosition = entity.getPosition(); + clearInterval(interval.current!); + interval.current = null; - ctx.globalAlpha = - entity.getType() === EntityType.Zombie && entity.getHealth() === 1 - ? 0.5 - : 1; + if (autoReplay) { + timeout.current = setTimeout(() => { + timeout.current = null; + startSimulation(); + }, AUTO_REPLAY_SPEED); - const offset = getImageOffset(entity); - - ctx.drawImage( - entityImage, - entityPosition.x * cellSizeNum + offset.x, - entityPosition.y * cellSizeNum + offset.y, - cellSizeNum, - cellSizeNum, - ); - } - - ctx.globalAlpha = 1.0; - } - - function renderCtxBg(ctx: CanvasRenderingContext2D) { - ctx.clearRect(0, 0, w, h); - - const canvasRatio = w / h; - const images = visualizer.getImages(); - const bgRatio = images.bg.width / images.bg.height; - - let drawWidth, drawHeight, offsetX, offsetY; + return; + } - if (bgRatio > canvasRatio) { - drawWidth = h * bgRatio; - drawHeight = h; - offsetX = (w - drawWidth) / 2; - offsetY = 0; - } else { - drawWidth = w; - drawHeight = w / bgRatio; - offsetX = 0; - offsetY = (h - drawHeight) / 2; - } + setRunning(false); - ctx.globalAlpha = 0.5; - ctx.drawImage(images.bg, offsetX, offsetY, drawWidth, drawHeight); - ctx.globalAlpha = 1.0; + if (onSimulationEnd) { + onSimulationEnd(!simulator.current.getPlayer().dead()); + } + }, REPLAY_SPEED); } React.useEffect(() => { @@ -205,7 +96,10 @@ export function Visualizer({ return; } - const observer = new IntersectionObserver(handleObserving); + const observer = new IntersectionObserver(handleObserving, { + threshold: 0, + }); + observer.observe(canvas.current); return () => { @@ -216,7 +110,7 @@ export function Visualizer({ }, [canvas]); function handleObserving([entry]: IntersectionObserverEntry[]) { - running.current = !entry.isIntersecting; + // running.current = !entry.isIntersecting; } React.useEffect(() => { @@ -236,14 +130,14 @@ export function Visualizer({
{controls && (
-