From 5f3e3706fa5068e867a244622e06829684c28fc7 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Wed, 15 Oct 2025 18:03:18 +0100 Subject: [PATCH] working on useScore --- src/types.ts | 3 ++ src/useGame/useGame.ts | 8 ++++ src/useScore/hooks/useTimer.ts | 82 ++++++++++++++++++++++++++++++++++ src/useScore/useScore.ts | 44 ++++++++++++++++++ src/useScore/useScoreTime.ts | 30 +++++++++++++ 5 files changed, 167 insertions(+) create mode 100644 src/useScore/hooks/useTimer.ts create mode 100644 src/useScore/useScore.ts create mode 100644 src/useScore/useScoreTime.ts diff --git a/src/types.ts b/src/types.ts index db4afb4..2370fc1 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,8 @@ import { useGame } from "./useGame"; +import { useScore } from "./useScore"; + export type UseGameType = ReturnType; +export type UseScoreType = ReturnType; export type { Difficulty, diff --git a/src/useGame/useGame.ts b/src/useGame/useGame.ts index 9fb64a3..e962539 100644 --- a/src/useGame/useGame.ts +++ b/src/useGame/useGame.ts @@ -4,6 +4,7 @@ import { useCursor } from "./useCursor"; import { useGameStatus } from "./useGameStatus"; import { useBoardState } from "./useBoardState"; import { useKeyBindings } from "./useKeyBindings"; +import { useScore } from "../useScore"; export function useGame(platformHook?: unknown) { const rendererManager = useRenderer(); @@ -15,6 +16,10 @@ export function useGame(platformHook?: unknown) { const boardManager = useBoardState(cursorManager); const { board, initialBoard, solution, setDigit, erase, reset, solveBoard } = boardManager; + const scoreManager = useScore({ + gameContext: { gameStatus, setGameStatus }, + }); + // --------------------------- // Keybindings // --------------------------- @@ -81,6 +86,9 @@ export function useGame(platformHook?: unknown) { reset, solveBoard, + // scoring + scoreManager, + // keybindings (vim-maze compatible) ...keyBindings, }; diff --git a/src/useScore/hooks/useTimer.ts b/src/useScore/hooks/useTimer.ts new file mode 100644 index 0000000..16e6e10 --- /dev/null +++ b/src/useScore/hooks/useTimer.ts @@ -0,0 +1,82 @@ +import { useCallback, useEffect, useRef, useState } from "react"; + +export type UseTimerReturn = { + timeValue: number; + startTimer: () => void; + stopTimer: () => void; + resetTimer: () => void; +}; + +export type UseTimerOptions = { + interval?: number; +}; + +export function useTimer(options: UseTimerOptions = {}): UseTimerReturn { + const { interval = 100 } = options; + const [timeValue, setTimeValue] = useState(0); + + const intervalIdRef = useRef | null>(null); + const offsetRef = useRef(0); + const startTimeRef = useRef(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, + }; +} diff --git a/src/useScore/useScore.ts b/src/useScore/useScore.ts new file mode 100644 index 0000000..f1917e8 --- /dev/null +++ b/src/useScore/useScore.ts @@ -0,0 +1,44 @@ +import { useEffect, useState } from "react"; +import { useTimer } from "./hooks/useTimer"; +import { useScoreTime } from "./useScoreTime"; +import type { GameStatus } from "../types"; + +type GameScoreContext = { + gameStatus: GameStatus; + setGameStatus: (status: GameStatus) => void; +}; + +type UseScoreParams = { + gameContext: GameScoreContext; +}; + +export function useScore({ gameContext }: UseScoreParams) { + const timer = useTimer(); + const { gameStatus } = gameContext; + const { timeValue, startTimer, stopTimer, resetTimer } = timer; + + const [finalScore, setFinalScore] = useState(null); + + useScoreTime({ gameStatus, timer }); + + useEffect(() => { + if (gameStatus !== "game-won") return; + + const timeInSeconds = timeValue / 1000; + const baseScore = 100000; + const timePenalty = Math.floor(timeInSeconds * 10); + const score = Math.max(0, baseScore - timePenalty); + + setFinalScore(score); + }, [gameStatus, timeValue]); + + return { + timeValue, + startTimer, + stopTimer, + resetTimer, + finalScore, + }; +} + +export type UseScoreType = ReturnType; diff --git a/src/useScore/useScoreTime.ts b/src/useScore/useScoreTime.ts new file mode 100644 index 0000000..4f76b85 --- /dev/null +++ b/src/useScore/useScoreTime.ts @@ -0,0 +1,30 @@ +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(() => { + prevStatusRef.current = gameStatus; + + switch (gameStatus) { + case "started": + resetTimer(); + startTimer(); + break; + + case "game-over": + case "game-won": + case "waiting": + stopTimer(); + break; + } + }, [gameStatus, startTimer, stopTimer, resetTimer]); +}