From 81489557c3ccc8296f2b49ef0773043bba16f56b Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Sun, 19 Oct 2025 03:39:37 +0100 Subject: [PATCH] player stopped moving --- AGENTS.md | 2 + src/useCursor/useHero.ts | 253 +++++++++++++-------------- src/useKeyBindings/useKeyBindings.ts | 2 +- 3 files changed, 128 insertions(+), 129 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 97b14d7..29ce1f6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -89,6 +89,7 @@ import { MazeRenderer } from './MazeRenderer'; - **Destructuring**: Use object destructuring for parameters and returns - **Arrow functions**: Prefer arrow functions for consistency - **No useCallback/useMemo**: React 19 optimization makes these hooks unnecessary +- **useEffect dependencies**: Only use values, never functions as dependencies ### Error Handling - **Early returns**: Guard clauses at function start @@ -178,6 +179,7 @@ export function useGame(options: GameOptions, platformHook?: unknown): GameManag - Write overly complex implementations - Add unnecessary features - Use `useCallback` or `useMemo` anywhere in the codebase +- Use functions as useEffect dependencies ### Always DO - Follow existing code patterns exactly diff --git a/src/useCursor/useHero.ts b/src/useCursor/useHero.ts index d9a1f15..79f1ab3 100644 --- a/src/useCursor/useHero.ts +++ b/src/useCursor/useHero.ts @@ -1,4 +1,4 @@ -import { useRef, useState, useCallback, useEffect } from "react"; +import { useRef, useState, useEffect } from "react"; import type { BoardManager, GameStatus, Coord } from "../types"; import { useHeroRender } from "./useHeroRender"; @@ -8,35 +8,35 @@ export function useHero(board: BoardManager, gameStatus: GameStatus) { const animatingRef = useRef(false); - const getCellEl = useCallback((r: number, c: number): HTMLElement | null => { + function getCellEl(r: number, c: number): HTMLElement | null { const container = containerRef.current; if (!container) return null; const mazeDiv = container.querySelector("#maze"); if (!mazeDiv) return null; const selector = `.maze-row > div[data-r="${r}"][data-c="${c}"]`; return mazeDiv.querySelector(selector) as HTMLElement | null; - }, [containerRef]); + } - const leaveTrail = useCallback((r: number, c: number) => { + function leaveTrail(r: number, c: number) { const el = getCellEl(r, c); if (!el) return; el.classList.add("trail"); setTimeout(() => el.classList.remove("trail"), 220); - }, [getCellEl]); + } - const flashInvalid = useCallback(() => { + function flashInvalid() { if (!heroPos) return; const el = getCellEl(heroPos.row, heroPos.col); if (!el) return; el.classList.add("invalid-move"); setTimeout(() => el.classList.remove("invalid-move"), 180); - }, [heroPos, getCellEl]); + } useEffect(() => { const onInvalid = () => flashInvalid(); window.addEventListener("maze-invalid", onInvalid); return () => window.removeEventListener("maze-invalid", onInvalid); - }, [flashInvalid]); + }, []); useEffect(() => { if (gameStatus === 'started' && !heroPos) { @@ -61,142 +61,139 @@ export function useHero(board: BoardManager, gameStatus: GameStatus) { } }, [gameStatus, containerRef]); - const moveHero = useCallback( - ( - dr: number, - dc: number, - steps: number | undefined, - gameStatus: GameStatus, - setGameStatus: (s: GameStatus) => void - ) => { - const m = mazeRef.current; - if (!heroPos || !m.length) return; - if (gameStatus !== "started" && gameStatus !== "hasKey") return; - if (animatingRef.current) return; + function moveHero( + dr: number, + dc: number, + steps: number | undefined, + gameStatus: GameStatus, + setGameStatus: (s: GameStatus) => void + ) { + const m = mazeRef.current; + if (!heroPos || !m.length) return; + if (gameStatus !== "started" && gameStatus !== "hasKey") return; + if (animatingRef.current) return; - const n = Math.max(1, steps ?? 1); + const n = Math.max(1, steps ?? 1); - const r0 = heroPos.row; - const c0 = heroPos.col; - let r = r0; - let c = c0; - let valid = true; + const r0 = heroPos.row; + const c0 = heroPos.col; + let r = r0; + let c = c0; + let valid = true; - for (let i = 1; i <= n; i++) { - r += dr; - c += dc; - if (!m[r] || !m[r][c] || m[r][c].includes("wall")) { - valid = false; - break; - } - if (m[r][c].includes("exit") && gameStatus !== "hasKey") { - valid = false; - break; - } + for (let i = 1; i <= n; i++) { + r += dr; + c += dc; + if (!m[r] || !m[r][c] || m[r][c].includes("wall")) { + valid = false; + break; } - if (!valid) { - flashInvalid(); - return; + if (m[r][c].includes("exit") && gameStatus !== "hasKey") { + valid = false; + break; } + } + if (!valid) { + flashInvalid(); + return; + } - if (n > 1) { - animatingRef.current = true; + if (n > 1) { + animatingRef.current = true; - let i = 0; - let rNow = r0; - let cNow = c0; - let cancelled = false; - let rafId: number | null = null; + let i = 0; + let rNow = r0; + let cNow = c0; + let cancelled = false; + let rafId: number | null = null; - const TOTAL_MS = 64; - const framesTarget = Math.max(1, Math.round(TOTAL_MS / 16)); - const stepsPerFrame = Math.max(1, Math.ceil(n / framesTarget)); + const TOTAL_MS = 64; + const framesTarget = Math.max(1, Math.round(TOTAL_MS / 16)); + const stepsPerFrame = Math.max(1, Math.ceil(n / framesTarget)); - const cancel = () => { - cancelled = true; - if (rafId !== null) cancelAnimationFrame(rafId); + const cancel = () => { + cancelled = true; + if (rafId !== null) cancelAnimationFrame(rafId); + window.removeEventListener("keydown", onEsc); + animatingRef.current = false; + }; + const onEsc = (ev: KeyboardEvent) => { + if (ev.key === "Escape") cancel(); + }; + window.addEventListener("keydown", onEsc); + + const tick = () => { + if (cancelled) return; + + let processed = 0; + while (processed < stepsPerFrame && i < n) { + i += 1; + rNow += dr; + cNow += dc; + leaveTrail(rNow, cNow); + processed += 1; + } + + setHeroPos({ row: rNow, col: cNow }); + window.dispatchEvent(new CustomEvent("maze-logic-tick", { detail: { t: performance.now() } })); + + if (i < n) { + rafId = requestAnimationFrame(tick); + } else { + if (m[rNow][cNow].includes("key")) { + setGameStatus("hasKey"); + m[rNow][cNow] = []; + const container = containerRef.current; + const mazeDiv = container?.querySelector("#maze"); + const keyCell = mazeDiv?.querySelector( + `.maze-row > div[data-r="${rNow}"][data-c="${cNow}"]` + ); + keyCell?.classList.remove("key"); + } + if (m[rNow][cNow].includes("exit")) { + setGameStatus("game-won"); + } window.removeEventListener("keydown", onEsc); animatingRef.current = false; - }; - const onEsc = (ev: KeyboardEvent) => { - if (ev.key === "Escape") cancel(); - }; - window.addEventListener("keydown", onEsc); + } + }; - const tick = () => { - if (cancelled) return; + rafId = requestAnimationFrame(tick); + return; + } - let processed = 0; - while (processed < stepsPerFrame && i < n) { - i += 1; - rNow += dr; - cNow += dc; - leaveTrail(rNow, cNow); - processed += 1; - } + const newR = r0 + dr; + const newC = c0 + dc; - setHeroPos({ row: rNow, col: cNow }); - window.dispatchEvent(new CustomEvent("maze-logic-tick", { detail: { t: performance.now() } })); + if (!m[newR] || !m[newR][newC]) { + flashInvalid(); + return; + } + if (m[newR][newC].includes("wall")) { + flashInvalid(); + return; + } + if (m[newR][newC].includes("exit") && gameStatus !== "hasKey") { + flashInvalid(); + return; + } - if (i < n) { - rafId = requestAnimationFrame(tick); - } else { - if (m[rNow][cNow].includes("key")) { - setGameStatus("hasKey"); - m[rNow][cNow] = []; - const container = containerRef.current; - const mazeDiv = container?.querySelector("#maze"); - const keyCell = mazeDiv?.querySelector( - `.maze-row > div[data-r="${rNow}"][data-c="${cNow}"]` - ); - keyCell?.classList.remove("key"); - } - if (m[rNow][cNow].includes("exit")) { - setGameStatus("game-won"); - } - window.removeEventListener("keydown", onEsc); - animatingRef.current = false; - } - }; + if (m[newR][newC].includes("key")) { + setGameStatus("hasKey"); + m[newR][newC] = []; + const container = containerRef.current; + const mazeDiv = container?.querySelector("#maze"); + const cell = mazeDiv?.querySelector( + `.maze-row > div[data-r="${newR}"][data-c="${newC}"]` + ); + cell?.classList.remove("key"); + } + if (m[newR][newC].includes("exit")) { + setGameStatus("game-won"); + } - rafId = requestAnimationFrame(tick); - return; - } - - const newR = r0 + dr; - const newC = c0 + dc; - - if (!m[newR] || !m[newR][newC]) { - flashInvalid(); - return; - } - if (m[newR][newC].includes("wall")) { - flashInvalid(); - return; - } - if (m[newR][newC].includes("exit") && gameStatus !== "hasKey") { - flashInvalid(); - return; - } - - if (m[newR][newC].includes("key")) { - setGameStatus("hasKey"); - m[newR][newC] = []; - const container = containerRef.current; - const mazeDiv = container?.querySelector("#maze"); - const cell = mazeDiv?.querySelector( - `.maze-row > div[data-r="${newR}"][data-c="${newC}"]` - ); - cell?.classList.remove("key"); - } - if (m[newR][newC].includes("exit")) { - setGameStatus("game-won"); - } - - setHeroPos({ row: newR, col: newC }); - }, - [heroPos, mazeRef, containerRef, flashInvalid, leaveTrail] - ); + setHeroPos({ row: newR, col: newC }); + } const hero = { heroPos, diff --git a/src/useKeyBindings/useKeyBindings.ts b/src/useKeyBindings/useKeyBindings.ts index dd46641..dbea190 100644 --- a/src/useKeyBindings/useKeyBindings.ts +++ b/src/useKeyBindings/useKeyBindings.ts @@ -112,7 +112,7 @@ export const useKeyBindings = ({ cursor, gameStatus, setGameStatus }: UseKeyBind window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); - }, [cursor, gameStatus]); + }, [gameStatus]); return { keyLog,