working on refactor

This commit is contained in:
2025-10-17 03:07:07 +01:00
parent acd4ec2053
commit ee58ab25fb
27 changed files with 688 additions and 55 deletions
-85
View File
@@ -1,85 +0,0 @@
import { useCallback, useEffect, useRef, useState } from "react";
export type UseTimerReturn = {
timeValue: number;
startTimer: () => void;
stopTimer: () => void;
resetTimer: () => void;
};
export type UseTimerOptions = {
/**
* Interval (in ms) between updates. Defaults to 100ms.
*/
interval?: number;
};
export function useTimer(options: UseTimerOptions = {}): UseTimerReturn {
const { interval = 100 } = options;
const [timeValue, setTimeValue] = useState(0);
const intervalIdRef = useRef<ReturnType<typeof setInterval> | null>(null);
const offsetRef = useRef(0);
const startTimeRef = useRef<number | null>(null);
const update = useCallback(() => {
if (startTimeRef.current === null) {
return;
}
const now = performance.now();
setTimeValue(offsetRef.current + (now - startTimeRef.current));
}, []);
const startTimer = useCallback(() => {
if (intervalIdRef.current !== null) {
return;
}
if (startTimeRef.current === null) {
startTimeRef.current = performance.now();
}
update();
intervalIdRef.current = setInterval(update, Math.max(16, interval));
}, [interval, update]);
const stopTimer = useCallback(() => {
if (intervalIdRef.current !== null) {
clearInterval(intervalIdRef.current);
intervalIdRef.current = null;
}
if (startTimeRef.current !== null) {
update();
offsetRef.current += performance.now() - startTimeRef.current;
startTimeRef.current = null;
}
}, [update]);
const resetTimer = useCallback(() => {
if (intervalIdRef.current !== null) {
clearInterval(intervalIdRef.current);
intervalIdRef.current = null;
}
startTimeRef.current = null;
offsetRef.current = 0;
setTimeValue(0);
}, []);
useEffect(() => {
return () => {
if (intervalIdRef.current !== null) {
clearInterval(intervalIdRef.current);
}
};
}, []);
return {
timeValue,
startTimer,
stopTimer,
resetTimer,
};
}
-1
View File
@@ -1 +0,0 @@
export * from './useScore';
-143
View File
@@ -1,143 +0,0 @@
import { useEffect, useRef, useState } from "react";
import { useTimer } from "../hooks/useTimer";
import { useScoreTime } from "./useScoreTime";
import { useScorePaths } from "./useScorePaths";
import type { KeyLogProvider, GameScoreContext } from "../types";
type UseScoreParams = {
gameContext: GameScoreContext;
keyManager?: KeyLogProvider;
};
export function useScore({ gameContext, keyManager }: UseScoreParams) {
const timer = useTimer();
const { gameStatus, setGameStatus } = gameContext;
const { timeValue, startTimer, stopTimer, resetTimer } = timer;
// ✅ useScorePaths always returns distances under these names
const {
entranceToKey = 0,
entranceToExit = 0,
heroToKey = 0,
heroToExit = 0,
} = gameContext.mazeManager
? useScorePaths({ gameManager: gameContext as any })
: {};
// ✅ use unified naming for internal logic
const distToKey = entranceToKey || heroToKey;
const distToExit = entranceToExit || heroToExit;
const [finalScore, setFinalScore] = useState<number | null>(null);
const optimalRef = useRef<number>(0);
const keystrokes = keyManager ? keyManager.keyLog.length : 0;
// 1️⃣ Manage visible wall-clock timer
useScoreTime({ gameStatus, timer });
// 2️⃣ Compute static optimal path when maze (re)starts
useEffect(() => {
const maze = gameContext.mazeManager?.mazeInstanceRef?.current;
if (!maze) return;
const k = maze.getDistance("entrance", "key");
const e = maze.getDistance("key", "exit");
const total = k + e;
if (Number.isFinite(total) && total > 0) {
optimalRef.current = total;
}
}, [gameStatus, gameContext.mazeManager?.mazeInstanceRef]);
const optimalSteps = optimalRef.current;
// 3️⃣ Efficiency (keystrokes vs optimal steps)
const efficiency =
optimalSteps > 0 ? Math.round((keystrokes / optimalSteps) * 100) : 0;
// 4️⃣ Logic time (internal motion time)
const [logicFirst, setLogicFirst] = useState<number | null>(null);
const [logicLast, setLogicLast] = useState<number | null>(null);
useEffect(() => {
if (gameStatus === "started") {
setLogicFirst(null);
setLogicLast(null);
setPlayerSteps(0);
prevPosRef.current = null;
}
}, [gameStatus]);
useEffect(() => {
const onTick = (e: Event) => {
const t =
(e as CustomEvent<{ t?: number }>).detail?.t ?? performance.now();
setLogicFirst((prev) => prev ?? t);
setLogicLast(t);
};
window.addEventListener("maze-logic-tick", onTick as EventListener);
return () =>
window.removeEventListener("maze-logic-tick", onTick as EventListener);
}, []);
// 5️⃣ Actual player steps
const [playerSteps, setPlayerSteps] = useState(0);
const prevPosRef = useRef<{ r: number; c: number } | null>(null);
useEffect(() => {
const pos = gameContext.playerManager?.playerPos;
if (!pos) {
prevPosRef.current = null;
return;
}
if (!["started", "hasKey"].includes(gameStatus)) {
prevPosRef.current = pos;
return;
}
const prev = prevPosRef.current;
prevPosRef.current = pos;
if (!prev) return;
const delta = Math.abs(pos.r - prev.r) + Math.abs(pos.c - prev.c);
if (delta > 0) setPlayerSteps((s) => s + delta);
}, [gameStatus, gameContext.playerManager?.playerPos]);
// 6️⃣ Auto game-over on inefficiency
useEffect(() => {
if (gameStatus === "started" && efficiency > 150) {
setGameStatus("game-over");
}
}, [efficiency, gameStatus, setGameStatus]);
// 7️⃣ Final score on win
useEffect(() => {
if (gameStatus !== "game-won") return;
const logicMs =
logicFirst != null && logicLast != null
? Math.max(1, logicLast - logicFirst)
: timeValue;
const optimalMs = optimalSteps * 200;
const ratio = Math.max(0.1, optimalMs / logicMs);
const score = Math.min(100000, Math.round(ratio * 100000));
setFinalScore(score);
}, [gameStatus, logicFirst, logicLast, timeValue, optimalSteps, playerSteps]);
// ✅ Return clean, typed object
return {
timeValue,
startTimer,
stopTimer,
resetTimer,
distToKey,
distToExit,
keystrokes,
optimalSteps,
efficiency,
finalScore,
};
}
-58
View File
@@ -1,58 +0,0 @@
import { useEffect, useState } from "react";
import type { GameStatus } from "../types";
// explicit so as not to break with circular deps, but should be UseGameType
type UseScorePathsParams = {
gameManager: {
gameStatus: GameStatus;
mazeManager: {
mazeInstanceRef: { current: any };
};
playerManager: {
playerPos: { r: number; c: number } | null;
};
};
};
export function useScorePaths({ gameManager }: UseScorePathsParams) {
const { gameStatus } = gameManager;
const { mazeInstanceRef } = gameManager.mazeManager;
const { playerPos } = gameManager.playerManager;
const [distances, setDistances] = useState({
entranceToKey: Infinity,
entranceToExit: Infinity,
heroToKey: Infinity,
heroToExit: Infinity,
});
// 🧭 1️⃣ Calculate static distances (entrance-based) when game starts
useEffect(() => {
if (gameStatus !== "started") return;
const maze = mazeInstanceRef.current;
if (!maze) return;
setDistances((prev) => ({
...prev,
entranceToKey: maze.getDistance("entrance", "key"),
entranceToExit: maze.getDistance("entrance", "exit"),
}));
}, [gameStatus, mazeInstanceRef]);
// 🚶 2️⃣ Update dynamic distances (hero-based) on every move
useEffect(() => {
const maze = mazeInstanceRef.current;
if (!maze || !playerPos) return;
setDistances((prev) => ({
...prev,
heroToKey: maze.getDistance("hero", "key", [playerPos.r, playerPos.c]),
heroToExit: maze.getDistance("hero", "exit", [playerPos.r, playerPos.c]),
}));
}, [playerPos, mazeInstanceRef]);
return distances;
}
export type UseScorePathsReturn = ReturnType<typeof useScorePaths>;
-38
View File
@@ -1,38 +0,0 @@
import { useEffect, useRef } from "react";
import type { UseTimerReturn } from "./hooks/useTimer";
import type { GameStatus } from "../types";
type UseScoreTimeParams = {
gameStatus: GameStatus;
timer: UseTimerReturn;
};
export function useScoreTime({ gameStatus, timer }: UseScoreTimeParams) {
const prevStatusRef = useRef<GameStatus | null>(null);
const { startTimer, stopTimer, resetTimer } = timer;
useEffect(() => {
// const prevStatus = prevStatusRef.current;
prevStatusRef.current = gameStatus;
switch (gameStatus) {
case "started":
// Always reset and start a fresh timer when a new game begins
resetTimer();
startTimer();
break;
case "hasKey":
// Keep running
break;
case "game-over":
case "game-won":
case "waiting":
// Stop timer when game finishes or is exited
stopTimer();
break;
}
}, [gameStatus, startTimer, stopTimer, resetTimer]);
}