BREAKING CHANGES: - Remove timeLimit from GameOptions (use gameOver function instead) - Remove finalScore from ScoreManager (pure data collection only) - Remove rows/cols parameters from useScore - Remove finalScore calculation logic from useScore hook ADDITIONS: - Create calculateMazeScore() utility function - ScoreManager now only returns raw metrics: timeValue, keystrokes, optimalSteps, efficiency, distToKey, distToExit - Score calculation moved to consumer (calculateMazeScore takes scoreManager + mazeSize) - Example app now uses calculateMazeScore to compute final score on game-won BENEFITS: - ScoreManager is now a pure data collector with no side effects - Scoring algorithm is reusable and testable in isolation - Game-over conditions fully controlled via gameOver option - Clear separation of concerns: collection vs calculation
83 lines
3.8 KiB
TypeScript
83 lines
3.8 KiB
TypeScript
import { useGame, version, calculateMazeScore } from "../../src";
|
|
import { useKeyBindings } from "./useKeyBindings";
|
|
import { useIsMobile } from "./useIsMobile";
|
|
import { useMemo } from "react";
|
|
import '../../src/game.css';
|
|
|
|
function App() {
|
|
const isMobile = useIsMobile();
|
|
const cols = isMobile ? 12 : 32;
|
|
const rows = isMobile ? 8 : 24;
|
|
|
|
const gameManager = useGame({ cols, rows }, useKeyBindings);
|
|
const { containerRef, gameStatus, scoreManager } = gameManager;
|
|
const { timeValue, keystrokes, optimalSteps, efficiency } = scoreManager;
|
|
|
|
const mazeSize = rows * cols;
|
|
const finalScore = useMemo(
|
|
() => gameStatus === "game-won" ? calculateMazeScore(scoreManager, mazeSize) : null,
|
|
[gameStatus, scoreManager, mazeSize]
|
|
);
|
|
|
|
const formatTime = (ms: number) => {
|
|
const seconds = Math.floor(ms / 1000);
|
|
const minutes = Math.floor(seconds / 60);
|
|
const secs = seconds % 60;
|
|
return `${minutes}:${secs.toString().padStart(2, '0')}`;
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col items-center justify-around min-h-[100vh]">
|
|
<h1 className="text-6xl">VIMazing vim-maze <small>v{version}</small> example</h1>
|
|
<div className="mx-auto my-4 w-fit bg-muted rounded-lg p-4">
|
|
{(gameStatus === "started" || gameStatus === "has-key" || gameStatus === "game-won") && (
|
|
<div className="flex gap-8 text-sm font-mono items-center">
|
|
<div>
|
|
<div className="text-muted-foreground">Time</div>
|
|
<div className="text-lg font-bold">{formatTime(timeValue)}</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-muted-foreground">Keystrokes</div>
|
|
<div className="text-lg font-bold">{keystrokes}</div>
|
|
</div>
|
|
{optimalSteps > 0 && (
|
|
<>
|
|
<div className="w-px h-12 bg-muted-foreground/20" />
|
|
<div>
|
|
<div className="text-muted-foreground">Optimal Steps</div>
|
|
<div className="text-lg font-bold">{optimalSteps}</div>
|
|
</div>
|
|
<div>
|
|
<div className="text-muted-foreground">Efficiency</div>
|
|
<div className="text-lg font-bold">{efficiency}%</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
{gameStatus === "game-won" && finalScore && (
|
|
<>
|
|
<div className="w-px h-12 bg-muted-foreground/20" />
|
|
<div className="text-center">
|
|
<div className="text-muted-foreground">Final Score</div>
|
|
<div className="text-3xl font-bold bg-gradient-to-r from-green-400 to-emerald-500 bg-clip-text text-transparent">{finalScore}</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
)}
|
|
</div>
|
|
<div className="relative mx-auto my-4 w-fit" id="maze_container">
|
|
<div ref={containerRef} className="relative" />
|
|
</div>
|
|
<div className="text-center text-2xl text-muted-foreground">
|
|
{gameStatus === "waiting" && <p>Press <kbd className="px-2 py-1 bg-muted rounded">space</kbd> to start</p>}
|
|
{gameStatus === "started" && <p>Use <kbd className="px-2 py-1 bg-muted rounded">hjkl</kbd> to change direction • Press <kbd className="px-2 py-1 bg-muted rounded">q</kbd> to quit</p>}
|
|
{gameStatus === "has-key" && <p>Use <kbd className="px-2 py-1 bg-muted rounded">hjkl</kbd> to change direction • Press <kbd className="px-2 py-1 bg-muted rounded">q</kbd> to quit</p>}
|
|
{gameStatus === "game-over" && <p>Game Over! Press <kbd className="px-2 py-1 bg-muted rounded">space</kbd> to restart</p>}
|
|
{gameStatus === "game-won" && <p>You Won! Press <kbd className="px-2 py-1 bg-muted rounded">space</kbd> to play again</p>}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default App;
|