working on useScore
This commit is contained in:
+48
-3
@@ -1,15 +1,60 @@
|
|||||||
import { useGame } from "../../src/useGame/useGame";
|
import { useGame } from "../../src/useGame/useGame";
|
||||||
import { useKeyBindings } from './useKeyBindings';
|
import { useKeyBindings } from './useKeyBindings';
|
||||||
|
import { useMounted } from './useMounted';
|
||||||
|
import { GameOverlay } from './GameOverlay';
|
||||||
import "../../src/sudoku.css";
|
import "../../src/sudoku.css";
|
||||||
|
|
||||||
|
function formatTime(ms: number): string {
|
||||||
|
const seconds = Math.floor(ms / 1000);
|
||||||
|
const minutes = Math.floor(seconds / 60);
|
||||||
|
const remainingSeconds = seconds % 60;
|
||||||
|
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const { containerRef, initGame, solveBoard } = useGame(useKeyBindings);
|
const mounted = useMounted();
|
||||||
|
const {
|
||||||
|
containerRef,
|
||||||
|
initGame,
|
||||||
|
solveBoard,
|
||||||
|
scoreManager,
|
||||||
|
keyLog,
|
||||||
|
gameStatus,
|
||||||
|
} = useGame(useKeyBindings);
|
||||||
|
|
||||||
|
const { timeValue, finalScore } = scoreManager;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 16 }}>
|
<div style={{ padding: 16 }}>
|
||||||
<h1>VIMazing Sudoku</h1>
|
<h1>VIMazing Sudoku</h1>
|
||||||
<div ref={containerRef} />
|
|
||||||
<div className="mt-20">
|
{/* Scoreboard */}
|
||||||
|
<div style={{
|
||||||
|
display: 'flex',
|
||||||
|
gap: 32,
|
||||||
|
marginBottom: 16,
|
||||||
|
fontFamily: 'monospace',
|
||||||
|
fontSize: 18,
|
||||||
|
}}>
|
||||||
|
<div>
|
||||||
|
<strong>Time:</strong> {formatTime(timeValue)}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Keystrokes:</strong> {keyLog.length}
|
||||||
|
</div>
|
||||||
|
{gameStatus === "game-won" && finalScore !== null && (
|
||||||
|
<div style={{ color: '#82aaff', fontWeight: 'bold' }}>
|
||||||
|
<strong>Score:</strong> {finalScore.toLocaleString()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="relative w-fit mx-auto">
|
||||||
|
<div ref={containerRef} />
|
||||||
|
{mounted && <GameOverlay gameStatus={gameStatus} scoreManager={scoreManager} />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginTop: 20, display: 'flex', gap: 8 }}>
|
||||||
<button onClick={initGame}>New Puzzle</button>
|
<button onClick={initGame}>New Puzzle</button>
|
||||||
<button onClick={solveBoard}>Solve</button>
|
<button onClick={solveBoard}>Solve</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+25
-21
@@ -1,5 +1,4 @@
|
|||||||
import type { GameStatus } from "@vimazing/vim-maze";
|
import type { GameStatus, UseScoreType } from "../../src/types";
|
||||||
import type { UseScoreType } from "@vimazing/vim-maze";
|
|
||||||
import { useIsMobile } from "./useIsMobile";
|
import { useIsMobile } from "./useIsMobile";
|
||||||
import { MobileGameOverlay } from "./MobileGameOverlay";
|
import { MobileGameOverlay } from "./MobileGameOverlay";
|
||||||
|
|
||||||
@@ -12,9 +11,7 @@ export const GameOverlay = ({ gameStatus, scoreManager }: GameOverlayProps) => {
|
|||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
if (isMobile) return <MobileGameOverlay gameStatus={gameStatus} scoreManager={scoreManager} />
|
if (isMobile) return <MobileGameOverlay gameStatus={gameStatus} scoreManager={scoreManager} />
|
||||||
|
|
||||||
// 🧠 Only show overlay when the game has ended
|
if (gameStatus === "started") return null;
|
||||||
if (['started', 'hasKey'].includes(gameStatus)) return null;
|
|
||||||
// console.log('gameStatus:', gameStatus)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GameOverlayContainer>
|
<GameOverlayContainer>
|
||||||
@@ -32,13 +29,20 @@ export const GameWaitingContent = () => {
|
|||||||
style={{ textShadow: "2px 2px 4px rgba(27, 29, 43, 0.9)" }}
|
style={{ textShadow: "2px 2px 4px rgba(27, 29, 43, 0.9)" }}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-10">
|
<div className="flex flex-col gap-10">
|
||||||
<p className="text-[3rem]">Press <strong className="text-[#ffc777]">i</strong> to play...</p>
|
<p className="text-[3rem]">Press <strong className="text-[#82aaff]">New Puzzle</strong> to start</p>
|
||||||
<p><strong className="text-[#ffc777]">h</strong>,<strong className="text-[#ffc777]">j</strong>,<strong className="text-[#ffc777]">k</strong>,<strong className="text-[#ffc777]">l</strong> to move around</p>
|
<div className="text-[1.2em]">
|
||||||
<p>try other vim motions <br /> you're familiar with</p>
|
<p className="mb-4"><strong className="text-[#ffc777]">Navigation Mode</strong> (default):</p>
|
||||||
<p>
|
<p><strong className="text-[#82aaff]">h</strong>,<strong className="text-[#82aaff]">j</strong>,<strong className="text-[#82aaff]">k</strong>,<strong className="text-[#82aaff]">l</strong> to move around</p>
|
||||||
<strong className="text-[#ffc777]">$</strong>,<strong className="text-[#ffc777]">^</strong>,<strong className="text-[#ffc777]">0</strong>,<strong className="text-[#ffc777]">G</strong>,<strong className="text-[#ffc777]">gg</strong> only valid if <br />
|
<p className="mt-2"><strong className="text-[#82aaff]">R</strong> or <strong className="text-[#82aaff]">C</strong> to enter edit mode</p>
|
||||||
the edges are reachable
|
|
||||||
</p>
|
<p className="mt-6 mb-4"><strong className="text-[#ffc777]">Edit Mode</strong>:</p>
|
||||||
|
<p>Type <strong className="text-[#82aaff]">1-9</strong> to fill cell</p>
|
||||||
|
<p className="mt-2"><strong className="text-[#82aaff]">Esc</strong> or <strong className="text-[#82aaff]">Enter</strong> to exit edit mode</p>
|
||||||
|
|
||||||
|
<p className="mt-6 text-[0.8em] text-[#828bb8]">
|
||||||
|
Try vim motions: <strong className="text-[#82aaff]">$</strong>, <strong className="text-[#82aaff]">^</strong>, <strong className="text-[#82aaff]">0</strong>, counted moves
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -52,22 +56,22 @@ export const GameWonContent = ({ scoreManager }: { scoreManager: UseScoreType })
|
|||||||
className="text-[#c8d3f5] text-[2em] grid gap-4 font-mono uppercase text-center select-none"
|
className="text-[#c8d3f5] text-[2em] grid gap-4 font-mono uppercase text-center select-none"
|
||||||
style={{ textShadow: "2px 2px 4px rgba(27, 29, 43, 0.9)" }}
|
style={{ textShadow: "2px 2px 4px rgba(27, 29, 43, 0.9)" }}
|
||||||
>
|
>
|
||||||
<p className="text-[#c3e88d]">Congratulations!</p>
|
<p className="text-[#c3e88d]">🎉 Puzzle Solved!</p>
|
||||||
<p>Final Score:</p>
|
<p>Final Score:</p>
|
||||||
<p className="text-[#82aaff] text-[2.5em]">{finalScore.toLocaleString()}</p>
|
<p className="text-[#82aaff] text-[2.5em]">{finalScore.toLocaleString()}</p>
|
||||||
<p className="text-[#828bb8] text-[0.65em] normal-case">
|
<p className="text-[#828bb8] text-[0.65em] normal-case">
|
||||||
<span className="text-[#c099ff]">min</span>
|
<span className="text-[#c099ff]">max</span>
|
||||||
<span className="text-[#c8d3f5]">(</span>
|
<span className="text-[#c8d3f5]">(</span>
|
||||||
|
<span className="text-[#ff966c]">0</span>
|
||||||
|
<span className="text-[#c8d3f5]">, </span>
|
||||||
<span className="text-[#ff966c]">100000</span>
|
<span className="text-[#ff966c]">100000</span>
|
||||||
<span className="text-[#c8d3f5]">, (</span>
|
<span className="text-[#c8d3f5]"> - </span>
|
||||||
<span className="text-[#82aaff]">optimalTime</span>
|
<span className="text-[#82aaff]">timeInSeconds</span>
|
||||||
<span className="text-[#c8d3f5]"> / </span>
|
<span className="text-[#c8d3f5]"> * </span>
|
||||||
<span className="text-[#82aaff]">actualTime</span>
|
<span className="text-[#ff966c]">10</span>
|
||||||
<span className="text-[#c8d3f5]">) * </span>
|
|
||||||
<span className="text-[#ff966c]">100000</span>
|
|
||||||
<span className="text-[#c8d3f5]">)</span>
|
<span className="text-[#c8d3f5]">)</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[#828bb8]">Press <strong className="text-[#ffc777] normal-case">i</strong> to play again</p>
|
<p className="text-[#828bb8]">Click <strong className="text-[#ffc777] normal-case">New Puzzle</strong> to play again</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import type { GameStatus } from "@vimazing/vim-maze";
|
import type { GameStatus, UseScoreType } from "../../src/types";
|
||||||
import type { UseScoreType } from "@vimazing/vim-maze";
|
|
||||||
|
|
||||||
type MobileGameOverlayProps = {
|
type MobileGameOverlayProps = {
|
||||||
gameStatus: GameStatus;
|
gameStatus: GameStatus;
|
||||||
@@ -7,9 +6,7 @@ type MobileGameOverlayProps = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const MobileGameOverlay = ({ gameStatus, scoreManager }: MobileGameOverlayProps) => {
|
export const MobileGameOverlay = ({ gameStatus, scoreManager }: MobileGameOverlayProps) => {
|
||||||
// 🧠 Only show overlay when the game has ended
|
if (gameStatus === "started") return null;
|
||||||
if (['started', 'hasKey'].includes(gameStatus)) return null;
|
|
||||||
// console.log('gameStatus:', gameStatus)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GameOverlayContainer>
|
<GameOverlayContainer>
|
||||||
@@ -27,8 +24,8 @@ export const GameWaitingContent = () => {
|
|||||||
style={{ textShadow: "2px 2px 4px rgba(27, 29, 43, 0.9)" }}
|
style={{ textShadow: "2px 2px 4px rgba(27, 29, 43, 0.9)" }}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
<p className="text-[1.5rem]">Press here to play</p>
|
<p className="text-[1.5rem]">Tap New Puzzle to start</p>
|
||||||
<p>all the vim motions <br /> available on desktop</p>
|
<p>VIM motions available <br /> via on-screen keyboard</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -42,22 +39,22 @@ export const GameWonContent = ({ scoreManager }: { scoreManager: UseScoreType })
|
|||||||
className="text-[#c8d3f5] text-[1em] grid gap-4 font-mono uppercase text-center select-none"
|
className="text-[#c8d3f5] text-[1em] grid gap-4 font-mono uppercase text-center select-none"
|
||||||
style={{ textShadow: "2px 2px 4px rgba(27, 29, 43, 0.9)" }}
|
style={{ textShadow: "2px 2px 4px rgba(27, 29, 43, 0.9)" }}
|
||||||
>
|
>
|
||||||
<p className="text-[#c3e88d]">Congratulations!</p>
|
<p className="text-[#c3e88d]">🎉 Solved!</p>
|
||||||
<p>Final Score:</p>
|
<p>Final Score:</p>
|
||||||
<p className="text-[#82aaff] text-[2.5em]">{finalScore.toLocaleString()}</p>
|
<p className="text-[#82aaff] text-[2.5em]">{finalScore.toLocaleString()}</p>
|
||||||
<p className="text-[#828bb8] text-[0.65em] normal-case">
|
<p className="text-[#828bb8] text-[0.65em] normal-case">
|
||||||
<span className="text-[#c099ff]">min</span>
|
<span className="text-[#c099ff]">max</span>
|
||||||
<span className="text-[#c8d3f5]">(</span>
|
<span className="text-[#c8d3f5]">(</span>
|
||||||
|
<span className="text-[#ff966c]">0</span>
|
||||||
|
<span className="text-[#c8d3f5]">, </span>
|
||||||
<span className="text-[#ff966c]">100000</span>
|
<span className="text-[#ff966c]">100000</span>
|
||||||
<span className="text-[#c8d3f5]">, (</span>
|
<span className="text-[#c8d3f5]"> - </span>
|
||||||
<span className="text-[#82aaff]">optimalTime</span>
|
<span className="text-[#82aaff]">time</span>
|
||||||
<span className="text-[#c8d3f5]"> / </span>
|
<span className="text-[#c8d3f5]"> * </span>
|
||||||
<span className="text-[#82aaff]">actualTime</span>
|
<span className="text-[#ff966c]">10</span>
|
||||||
<span className="text-[#c8d3f5]">) * </span>
|
|
||||||
<span className="text-[#ff966c]">100000</span>
|
|
||||||
<span className="text-[#c8d3f5]">)</span>
|
<span className="text-[#c8d3f5]">)</span>
|
||||||
</p>
|
</p>
|
||||||
<p className="text-[#828bb8]">Press to play again</p>
|
<p className="text-[#828bb8]">Tap to play again</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,6 +8,14 @@
|
|||||||
--box-separator: 3px;
|
--box-separator: 3px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--cell-size: 96px;
|
||||||
|
--cell-font-size: 48px;
|
||||||
|
--grid-gap: 6px;
|
||||||
|
--box-separator: 9px;
|
||||||
|
}
|
||||||
|
|
||||||
/* =========================================================
|
/* =========================================================
|
||||||
Container & Grid Base
|
Container & Grid Base
|
||||||
========================================================= */
|
========================================================= */
|
||||||
|
|||||||
@@ -16,10 +16,6 @@ export function useGame(platformHook?: unknown) {
|
|||||||
const boardManager = useBoardState(cursorManager);
|
const boardManager = useBoardState(cursorManager);
|
||||||
const { board, initialBoard, solution, setDigit, erase, reset, solveBoard } = boardManager;
|
const { board, initialBoard, solution, setDigit, erase, reset, solveBoard } = boardManager;
|
||||||
|
|
||||||
const scoreManager = useScore({
|
|
||||||
gameContext: { gameStatus, setGameStatus },
|
|
||||||
});
|
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// Keybindings
|
// Keybindings
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
@@ -33,7 +29,15 @@ export function useGame(platformHook?: unknown) {
|
|||||||
initialBoard,
|
initialBoard,
|
||||||
});
|
});
|
||||||
|
|
||||||
const { editMode } = keyBindings;
|
const { editMode, keyLog } = keyBindings;
|
||||||
|
|
||||||
|
// ---------------------------
|
||||||
|
// Scoring (needs keyLog from keyBindings)
|
||||||
|
// ---------------------------
|
||||||
|
const scoreManager = useScore({
|
||||||
|
gameContext: { gameStatus, setGameStatus },
|
||||||
|
keystrokeCount: keyLog.length,
|
||||||
|
});
|
||||||
|
|
||||||
// ---------------------------
|
// ---------------------------
|
||||||
// Rendering
|
// Rendering
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ import { useTimer } from "./hooks/useTimer";
|
|||||||
import { useScoreTime } from "./useScoreTime";
|
import { useScoreTime } from "./useScoreTime";
|
||||||
import type { GameStatus } from "../types";
|
import type { GameStatus } from "../types";
|
||||||
|
|
||||||
|
const TIME_LIMIT_MS = 60000; // 60 seconds
|
||||||
|
const KEYSTROKE_LIMIT = 100;
|
||||||
|
|
||||||
type GameScoreContext = {
|
type GameScoreContext = {
|
||||||
gameStatus: GameStatus;
|
gameStatus: GameStatus;
|
||||||
setGameStatus: (status: GameStatus) => void;
|
setGameStatus: (status: GameStatus) => void;
|
||||||
@@ -10,17 +13,35 @@ type GameScoreContext = {
|
|||||||
|
|
||||||
type UseScoreParams = {
|
type UseScoreParams = {
|
||||||
gameContext: GameScoreContext;
|
gameContext: GameScoreContext;
|
||||||
|
keystrokeCount: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useScore({ gameContext }: UseScoreParams) {
|
export function useScore({ gameContext, keystrokeCount }: UseScoreParams) {
|
||||||
const timer = useTimer();
|
const timer = useTimer();
|
||||||
const { gameStatus } = gameContext;
|
const { gameStatus, setGameStatus } = gameContext;
|
||||||
const { timeValue, startTimer, stopTimer, resetTimer } = timer;
|
const { timeValue, startTimer, stopTimer, resetTimer } = timer;
|
||||||
|
|
||||||
const [finalScore, setFinalScore] = useState<number | null>(null);
|
const [finalScore, setFinalScore] = useState<number | null>(null);
|
||||||
|
|
||||||
useScoreTime({ gameStatus, timer });
|
useScoreTime({ gameStatus, timer });
|
||||||
|
|
||||||
|
// Check for game-over conditions
|
||||||
|
useEffect(() => {
|
||||||
|
if (gameStatus !== "started") return;
|
||||||
|
|
||||||
|
// Time limit exceeded
|
||||||
|
if (timeValue >= TIME_LIMIT_MS) {
|
||||||
|
setGameStatus("game-over");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keystroke limit exceeded
|
||||||
|
if (keystrokeCount >= KEYSTROKE_LIMIT) {
|
||||||
|
setGameStatus("game-over");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}, [gameStatus, timeValue, keystrokeCount, setGameStatus]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (gameStatus !== "game-won") return;
|
if (gameStatus !== "game-won") return;
|
||||||
|
|
||||||
@@ -38,6 +59,8 @@ export function useScore({ gameContext }: UseScoreParams) {
|
|||||||
stopTimer,
|
stopTimer,
|
||||||
resetTimer,
|
resetTimer,
|
||||||
finalScore,
|
finalScore,
|
||||||
|
timeLimit: TIME_LIMIT_MS,
|
||||||
|
keystrokeLimit: KEYSTROKE_LIMIT,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user