refactor: simplify scoring to remove efficiency-based complexity

- Remove efficiency calculation and game-over at 150%
- Remove optimal steps tracking and complex logic timing
- Simplify to time + keystrokes only scoring
- New formula: score = min(1000, (1000 - time_penalty - keystroke_penalty) × size_multiplier)
  - time_penalty = seconds / 10
  - keystroke_penalty = keystrokes / 2
  - size_multiplier = max(1.0, (rows × cols) / 500)
- Maze size-based multiplier rewards larger mazes
- Remove optimalSteps and efficiency from ScoreManager type
- Pass rows and cols to useScore for size calculation
- Align with vim-sudoku's simplified scoring approach
This commit is contained in:
2025-10-22 22:03:44 +01:00
parent 75ff8bc76e
commit 7ccf7cdd55
8 changed files with 16 additions and 2241 deletions
-2
View File
@@ -20,7 +20,5 @@ export type ScoreManager = {
distToKey: number;
distToExit: number;
keystrokes: number;
optimalSteps: number;
efficiency: number;
finalScore: number | null;
};
+15 -86
View File
@@ -1,4 +1,4 @@
import { useEffect, useRef, useState } from "react";
import { useEffect, useState } from "react";
import { useTimer } from "./useTimer";
import { useScoreTime } from "./useScoreTime";
import { useScorePaths } from "./useScorePaths";
@@ -11,11 +11,13 @@ type UseScoreParams = {
hero: UseHeroType;
gameStatusManager: GameStatusManager;
keyManager?: { keyLog: any[] };
rows: number;
cols: number;
};
export function useScore({ board, hero, gameStatusManager, keyManager }: UseScoreParams) {
export function useScore({ board, hero, gameStatusManager, keyManager, rows, cols }: UseScoreParams) {
const timer = useTimer();
const { gameStatus, setGameStatus } = gameStatusManager;
const { gameStatus } = gameStatusManager;
const { timeValue, startTimer, stopTimer, resetTimer } = timer;
const {
@@ -29,96 +31,25 @@ export function useScore({ board, hero, gameStatusManager, keyManager }: UseScor
const distToExit = entranceToExit || heroToExit;
const [finalScore, setFinalScore] = useState<number | null>(null);
const optimalRef = useRef<number>(0);
const keystrokes = keyManager ? keyManager.keyLog.length : 0;
useScoreTime({ gameStatus, timer });
useEffect(() => {
const maze = board.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, board.mazeInstanceRef]);
const optimalSteps = optimalRef.current;
const efficiency =
optimalSteps > 0 ? Math.round((keystrokes / optimalSteps) * 100) : 0;
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);
}, []);
const [, setPlayerSteps] = useState(0);
const prevPosRef = useRef<{ row: number; col: number } | null>(null);
useEffect(() => {
const pos = hero.heroPos;
if (!pos) {
prevPosRef.current = null;
return;
}
if (!["started", "has-key"].includes(gameStatus)) {
prevPosRef.current = pos;
return;
}
const prev = prevPosRef.current;
prevPosRef.current = pos;
if (!prev) return;
const delta = Math.abs(pos.row - prev.row) + Math.abs(pos.col - prev.col);
if (delta > 0) setPlayerSteps((s) => s + delta);
}, [gameStatus, hero.heroPos]);
useEffect(() => {
if (gameStatus === "started" && efficiency > 150) {
setGameStatus("game-over");
}
}, [efficiency, gameStatus, setGameStatus]);
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));
const seconds = timeValue / 1000;
const mazeSize = rows * cols;
const timePenalty = seconds / 10;
const keystrokePenalty = keystrokes / 2;
const sizeMultiplier = Math.max(1.0, mazeSize / 500);
const baseScore = 1000 - timePenalty - keystrokePenalty;
const score = Math.min(1000, Math.max(0, Math.round(baseScore * sizeMultiplier)));
setFinalScore(score);
}, [gameStatus, logicFirst, logicLast, timeValue, optimalSteps]);
}, [gameStatus, timeValue, keystrokes, rows, cols]);
return {
timeValue,
@@ -128,8 +59,6 @@ export function useScore({ board, hero, gameStatusManager, keyManager }: UseScor
distToKey,
distToExit,
keystrokes,
optimalSteps,
efficiency,
finalScore,
};
}