diff --git a/.npmignore b/.npmignore index a2cf60a..4b340ab 100755 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,12 @@ src/ +example/ node_modules/ tsconfig.json +global.d.ts bun.lockb *.log +.git +.gitignore +.vscode/ +.idea/ diff --git a/src/useGame/useBoard.ts b/src/useGame/useBoard.ts index e69de29..9d3c531 100755 --- a/src/useGame/useBoard.ts +++ b/src/useGame/useBoard.ts @@ -0,0 +1,3 @@ +export function useBoard() { + return {}; +} diff --git a/src/useGame/useFood.ts b/src/useGame/useFood.ts index e69de29..917566f 100755 --- a/src/useGame/useFood.ts +++ b/src/useGame/useFood.ts @@ -0,0 +1,3 @@ +export function useFood() { + return {}; +} diff --git a/src/useGame/useGameStatus.ts b/src/useGame/useGameStatus.ts index 8db99a4..76e0b6b 100755 --- a/src/useGame/useGameStatus.ts +++ b/src/useGame/useGameStatus.ts @@ -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 function useGameStatus(mazeManager: UseMazeType, playerManager: UsePlayerType) { - const { mazeRef, containerRef } = mazeManager; - const { setPlayerPos } = playerManager; - const [gameStatus, setGameStatus] = useState("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; - diff --git a/src/useGame/useKeyBindings.ts b/src/useGame/useKeyBindings.ts index 87cf73d..a92bf7d 100755 --- a/src/useGame/useKeyBindings.ts +++ b/src/useGame/useKeyBindings.ts @@ -1,259 +1,3 @@ -import type { GameStatus } from "../types"; -import { useEffect, useRef, useState } from "react"; -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([]); - const logRef = useRef([]); - const countRef = useRef(""); - const lastMotionRef = useRef(null); - const lastKeyRef = useRef(""); - - 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; +export function useKeyBindings() { + return {}; +} diff --git a/src/useGame/useSnake.ts b/src/useGame/useSnake.ts index e69de29..246cb4a 100755 --- a/src/useGame/useSnake.ts +++ b/src/useGame/useSnake.ts @@ -0,0 +1,3 @@ +export function useSnake() { + return {}; +} diff --git a/src/useScore/hooks/useTimer.ts b/src/useScore/hooks/useTimer.ts old mode 100755 new mode 100644 diff --git a/src/useScore/useScore.ts b/src/useScore/useScore.ts index 8514393..a4db2bf 100755 --- a/src/useScore/useScore.ts +++ b/src/useScore/useScore.ts @@ -1,143 +1,8 @@ -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(null); - const optimalRef = useRef(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(null); - const [logicLast, setLogicLast] = useState(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 +export function useScore() { return { - timeValue, - startTimer, - stopTimer, - resetTimer, - distToKey, - distToExit, - keystrokes, - optimalSteps, - efficiency, - finalScore, + timeValue: 0, + startTimer: () => {}, + stopTimer: () => {}, + resetTimer: () => {}, }; } diff --git a/src/useScore/useScoreTime.ts b/src/useScore/useScoreTime.ts index fe2514b..06c9c79 100755 --- a/src/useScore/useScoreTime.ts +++ b/src/useScore/useScoreTime.ts @@ -1,38 +1,3 @@ -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(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]); +export function useScoreTime() { + return {}; } -