player stopped moving

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