prepared for publish 0.1.0
This commit is contained in:
@@ -1,6 +1,12 @@
|
|||||||
src/
|
src/
|
||||||
|
example/
|
||||||
node_modules/
|
node_modules/
|
||||||
tsconfig.json
|
tsconfig.json
|
||||||
|
global.d.ts
|
||||||
bun.lockb
|
bun.lockb
|
||||||
*.log
|
*.log
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.vscode/
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function useBoard() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function useFood() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,55 +1 @@
|
|||||||
import { useCallback, useState, useMemo } from "react";
|
|
||||||
import type { UseMazeType } from "./useMaze";
|
|
||||||
import type { UsePlayerType } from "./usePlayer";
|
|
||||||
import type { GameStatus } from "../types";
|
|
||||||
import { getGamePhase, isPlaying } from "../types";
|
|
||||||
|
|
||||||
export type { GameStatus } from "../types";
|
export type { GameStatus } from "../types";
|
||||||
|
|
||||||
export function useGameStatus(mazeManager: UseMazeType, playerManager: UsePlayerType) {
|
|
||||||
const { mazeRef, containerRef } = mazeManager;
|
|
||||||
const { setPlayerPos } = playerManager;
|
|
||||||
const [gameStatus, setGameStatus] = useState<GameStatus>("waiting");
|
|
||||||
|
|
||||||
const gamePhase = useMemo(() => getGamePhase(gameStatus), [gameStatus]);
|
|
||||||
const playStatus = useMemo(
|
|
||||||
() => (isPlaying(gameStatus) ? gameStatus : null),
|
|
||||||
[gameStatus]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 🕹 Start the game (place hero)
|
|
||||||
const startGame = useCallback(() => {
|
|
||||||
const m = mazeRef.current;
|
|
||||||
if (!m.length) return;
|
|
||||||
|
|
||||||
for (let c = 0; c < m[0].length; c++) {
|
|
||||||
if (m[m.length - 1][c].includes("entrance")) {
|
|
||||||
setPlayerPos({ r: m.length - 1, c });
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setGameStatus("started");
|
|
||||||
}, [mazeRef, setPlayerPos]);
|
|
||||||
|
|
||||||
// 🧍 Stop / exit game
|
|
||||||
const stopGame = useCallback(() => {
|
|
||||||
setPlayerPos(null);
|
|
||||||
setGameStatus("waiting");
|
|
||||||
const container = containerRef.current;
|
|
||||||
container?.querySelector("#maze")?.classList.remove("finished");
|
|
||||||
container?.querySelectorAll(".hero").forEach((el) => el.classList.remove("hero"));
|
|
||||||
}, [setPlayerPos, containerRef]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
gameStatus,
|
|
||||||
gamePhase,
|
|
||||||
playStatus,
|
|
||||||
setGameStatus,
|
|
||||||
startGame,
|
|
||||||
stopGame,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type UseGameStatusType = ReturnType<typeof useGameStatus>;
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,259 +1,3 @@
|
|||||||
import type { GameStatus } from "../types";
|
export function useKeyBindings() {
|
||||||
import { useEffect, useRef, useState } from "react";
|
return {};
|
||||||
import type { KeyLogEntry } from "../types";
|
}
|
||||||
|
|
||||||
export type GameBindingContext = {
|
|
||||||
movePlayer: (dr: number, dc: number, count?: number) => void;
|
|
||||||
gameStatus: GameStatus;
|
|
||||||
mazeManager: any;
|
|
||||||
playerManager: any;
|
|
||||||
};
|
|
||||||
|
|
||||||
type UseMazeKeyBindingsParams = {
|
|
||||||
gameManager: GameBindingContext;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Motion = { dr: number; dc: number; steps: number };
|
|
||||||
|
|
||||||
const RELEVANT_KEYS = new Set([
|
|
||||||
"h", "H", "j", "J", "k", "K", "l", "L", ".", "^", "$", "g", "G", "0",
|
|
||||||
"1", "2", "3", "4", "5", "6", "7", "8", "9"
|
|
||||||
]);
|
|
||||||
|
|
||||||
export const useKeyBindings = ({ gameManager }: UseMazeKeyBindingsParams) => {
|
|
||||||
const { movePlayer, gameStatus } = gameManager;
|
|
||||||
|
|
||||||
const [keyLog, setKeyLog] = useState<KeyLogEntry[]>([]);
|
|
||||||
const logRef = useRef<KeyLogEntry[]>([]);
|
|
||||||
const countRef = useRef<string>("");
|
|
||||||
const lastMotionRef = useRef<Motion | null>(null);
|
|
||||||
const lastKeyRef = useRef<string>("");
|
|
||||||
|
|
||||||
const relevantKeys = RELEVANT_KEYS;
|
|
||||||
|
|
||||||
const clearLog = () => {
|
|
||||||
logRef.current = [];
|
|
||||||
setKeyLog([]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const recordKey = (key: string) => {
|
|
||||||
const entry = { key, timestamp: performance.now() };
|
|
||||||
logRef.current.push(entry);
|
|
||||||
setKeyLog([...logRef.current]);
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetCount = () => { countRef.current = ""; };
|
|
||||||
|
|
||||||
const findAnchorTarget = (dir: "left" | "right") => {
|
|
||||||
const m = gameManager.mazeManager.mazeRef.current;
|
|
||||||
const pos = gameManager.playerManager.playerPos;
|
|
||||||
if (!m.length || !pos) return null;
|
|
||||||
|
|
||||||
const colsCount = m[0]?.length ?? 0;
|
|
||||||
const r = pos.r;
|
|
||||||
const c = pos.c;
|
|
||||||
|
|
||||||
if (!m[r] || !m[r][c] || m[r][c].includes("wall")) return null;
|
|
||||||
|
|
||||||
const step = dir === "left" ? -1 : 1;
|
|
||||||
let cc = c;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const next = cc + step;
|
|
||||||
if (next < 0 || next >= colsCount) return null;
|
|
||||||
if (m[r][next].includes("wall")) {
|
|
||||||
const wallIdx = next;
|
|
||||||
const expectedWallIdx = dir === "left" ? 0 : colsCount - 1;
|
|
||||||
const expectedCellIdx = dir === "left" ? 1 : colsCount - 2;
|
|
||||||
|
|
||||||
if (wallIdx !== expectedWallIdx) return null;
|
|
||||||
if (cc !== expectedCellIdx) return null;
|
|
||||||
if (cc === c) return null;
|
|
||||||
|
|
||||||
return { r, cTarget: cc };
|
|
||||||
}
|
|
||||||
cc = next;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const findColumnAnchorTarget = (dir: "top" | "bottom") => {
|
|
||||||
const m = gameManager.mazeManager.mazeRef.current;
|
|
||||||
const pos = gameManager.playerManager.playerPos;
|
|
||||||
if (!m.length || !pos) return null;
|
|
||||||
|
|
||||||
const rowsCount = m.length;
|
|
||||||
const r = pos.r;
|
|
||||||
const c = pos.c;
|
|
||||||
|
|
||||||
if (!m[r] || !m[r][c] || m[r][c].includes("wall")) return null;
|
|
||||||
|
|
||||||
const step = dir === "top" ? -1 : 1;
|
|
||||||
let rr = r;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
const next = rr + step;
|
|
||||||
if (next < 0 || next >= rowsCount) return null;
|
|
||||||
if (m[next][c].includes("wall")) {
|
|
||||||
const wallIdx = next;
|
|
||||||
const expectedWallIdx = dir === "top" ? 0 : rowsCount - 1;
|
|
||||||
const expectedCellIdx = dir === "top" ? 1 : rowsCount - 2;
|
|
||||||
|
|
||||||
if (wallIdx !== expectedWallIdx) return null;
|
|
||||||
if (rr !== expectedCellIdx) return null;
|
|
||||||
if (rr === r) return null;
|
|
||||||
|
|
||||||
return { rTarget: rr, c };
|
|
||||||
}
|
|
||||||
rr = next;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const handler = (e: KeyboardEvent) => {
|
|
||||||
if (["waiting", "game-over", "game-won"].includes(gameStatus)) return;
|
|
||||||
|
|
||||||
// "0" behaves like Vim: beginning of line
|
|
||||||
if (e.key === "0" && countRef.current === "") {
|
|
||||||
recordKey("0");
|
|
||||||
resetCount();
|
|
||||||
|
|
||||||
const anchor = findAnchorTarget("left");
|
|
||||||
if (!anchor) {
|
|
||||||
window.dispatchEvent(new Event("maze-invalid"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pos = gameManager.playerManager.playerPos!;
|
|
||||||
const dr = 0;
|
|
||||||
const dc = anchor.cTarget > pos.c ? 1 : -1;
|
|
||||||
const steps = Math.abs(anchor.cTarget - pos.c);
|
|
||||||
|
|
||||||
movePlayer(dr, dc, steps);
|
|
||||||
lastMotionRef.current = { dr, dc, steps };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDigit = e.key >= "0" && e.key <= "9";
|
|
||||||
if (isDigit) {
|
|
||||||
if (e.key === "0" && countRef.current === "") return;
|
|
||||||
recordKey(e.key);
|
|
||||||
countRef.current += e.key;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === ".") {
|
|
||||||
recordKey(".");
|
|
||||||
const last = lastMotionRef.current;
|
|
||||||
if (!last) return;
|
|
||||||
const steps = Math.max(1, parseInt(countRef.current || String(last.steps), 10));
|
|
||||||
resetCount();
|
|
||||||
movePlayer(last.dr, last.dc, steps);
|
|
||||||
lastMotionRef.current = { dr: last.dr, dc: last.dc, steps };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === "^" || e.key === "$") {
|
|
||||||
recordKey(e.key);
|
|
||||||
resetCount();
|
|
||||||
|
|
||||||
const dir = e.key === "^" ? "left" : "right";
|
|
||||||
const anchor = findAnchorTarget(dir);
|
|
||||||
if (!anchor) {
|
|
||||||
window.dispatchEvent(new Event("maze-invalid"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pos = gameManager.playerManager.playerPos!;
|
|
||||||
const dr = 0;
|
|
||||||
const dc = anchor.cTarget > pos.c ? 1 : -1;
|
|
||||||
const steps = Math.abs(anchor.cTarget - pos.c);
|
|
||||||
|
|
||||||
movePlayer(dr, dc, steps);
|
|
||||||
lastMotionRef.current = { dr, dc, steps };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.key === "g" || e.key === "G") {
|
|
||||||
if (e.key === "g") {
|
|
||||||
if (lastKeyRef.current === "g") {
|
|
||||||
recordKey(e.key);
|
|
||||||
resetCount();
|
|
||||||
lastKeyRef.current = "";
|
|
||||||
|
|
||||||
const anchor = findColumnAnchorTarget("top");
|
|
||||||
if (!anchor) {
|
|
||||||
window.dispatchEvent(new Event("maze-invalid"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pos = gameManager.playerManager.playerPos!;
|
|
||||||
const dr = anchor.rTarget > pos.r ? 1 : -1;
|
|
||||||
const dc = 0;
|
|
||||||
const steps = Math.abs(anchor.rTarget - pos.r);
|
|
||||||
|
|
||||||
movePlayer(dr, dc, steps);
|
|
||||||
lastMotionRef.current = { dr, dc, steps };
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
recordKey(e.key);
|
|
||||||
lastKeyRef.current = "g";
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
recordKey(e.key);
|
|
||||||
resetCount();
|
|
||||||
lastKeyRef.current = "";
|
|
||||||
|
|
||||||
const anchor = findColumnAnchorTarget("bottom");
|
|
||||||
if (!anchor) {
|
|
||||||
window.dispatchEvent(new Event("maze-invalid"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const pos = gameManager.playerManager.playerPos!;
|
|
||||||
const dr = anchor.rTarget > pos.r ? 1 : -1;
|
|
||||||
const dc = 0;
|
|
||||||
const steps = Math.abs(anchor.rTarget - pos.r);
|
|
||||||
|
|
||||||
movePlayer(dr, dc, steps);
|
|
||||||
lastMotionRef.current = { dr, dc, steps };
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!relevantKeys.has(e.key)) return;
|
|
||||||
|
|
||||||
recordKey(e.key);
|
|
||||||
|
|
||||||
let dr = 0, dc = 0;
|
|
||||||
switch (e.key) {
|
|
||||||
case "h":
|
|
||||||
case "H": dr = 0; dc = -1; break;
|
|
||||||
case "j":
|
|
||||||
case "J": dr = 1; dc = 0; break;
|
|
||||||
case "k":
|
|
||||||
case "K": dr = -1; dc = 0; break;
|
|
||||||
case "l":
|
|
||||||
case "L": dr = 0; dc = 1; break;
|
|
||||||
default: return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const steps = Math.max(1, parseInt(countRef.current || "1", 10));
|
|
||||||
resetCount();
|
|
||||||
movePlayer(dr, dc, steps);
|
|
||||||
lastMotionRef.current = { dr, dc, steps };
|
|
||||||
};
|
|
||||||
|
|
||||||
window.addEventListener("keydown", handler);
|
|
||||||
return () => window.removeEventListener("keydown", handler);
|
|
||||||
}, [movePlayer, gameStatus, gameManager]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
keyLog,
|
|
||||||
clearLog,
|
|
||||||
resetCount,
|
|
||||||
getLog: () => logRef.current,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UseKeyBindingsType = ReturnType<typeof useKeyBindings>;
|
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export function useSnake() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|||||||
Executable → Regular
+5
-140
@@ -1,143 +1,8 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
export function useScore() {
|
||||||
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 {
|
return {
|
||||||
timeValue,
|
timeValue: 0,
|
||||||
startTimer,
|
startTimer: () => {},
|
||||||
stopTimer,
|
stopTimer: () => {},
|
||||||
resetTimer,
|
resetTimer: () => {},
|
||||||
distToKey,
|
|
||||||
distToExit,
|
|
||||||
keystrokes,
|
|
||||||
optimalSteps,
|
|
||||||
efficiency,
|
|
||||||
finalScore,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +1,3 @@
|
|||||||
import { useEffect, useRef } from "react";
|
export function useScoreTime() {
|
||||||
import type { UseTimerReturn } from "./hooks/useTimer";
|
return {};
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user