working on useScore

This commit is contained in:
2025-10-15 18:03:18 +01:00
parent 5b9b44aef6
commit 5f3e3706fa
5 changed files with 167 additions and 0 deletions
+3
View File
@@ -1,5 +1,8 @@
import { useGame } from "./useGame";
import { useScore } from "./useScore";
export type UseGameType = ReturnType<typeof useGame>;
export type UseScoreType = ReturnType<typeof useScore>;
export type {
Difficulty,
+8
View File
@@ -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,
};
+82
View File
@@ -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<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,
};
}
+44
View File
@@ -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<number | null>(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<typeof useScore>;
+30
View File
@@ -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<GameStatus | null>(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]);
}