From 7ee42a8dc189d44145e42dfab5cca131f5b1bbb3 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Mon, 3 Nov 2025 23:04:26 +0000 Subject: [PATCH] feat: implement complete score tracking system - Track score (starts at 0) - +1 score and +1 correct for each correct character typed - -1 score and +1 missed for each wrong character typed - +1 deaths for each collision with ship - Score resets when game starts - All metrics exposed through ScoreManager - Updated example app UI with colorful scoreboard showing: - Time (gray) - Score (neutral) - Correct (green) - Missed (yellow) - Deaths (red) - Keystrokes (neutral) - Uses grid layout for organized metric display - Score cannot go below 0 (clamped to minimum) --- example/bun.lock | 4 ++-- example/src/App.tsx | 24 ++++++++++++++++++--- package.json | 1 + src/types.ts | 13 ++++++++++-- src/useGameStatus/useGameStatus.ts | 34 ++++++++++++++++++++++++++++++ src/useScore/useScore.ts | 6 +++++- 6 files changed, 74 insertions(+), 8 deletions(-) mode change 100755 => 100644 example/bun.lock diff --git a/example/bun.lock b/example/bun.lock old mode 100755 new mode 100644 index 3d521d2..79fc665 --- a/example/bun.lock +++ b/example/bun.lock @@ -5,7 +5,7 @@ "name": "vimazing-vim-snake-example", "dependencies": { "@tailwindcss/vite": "^4.1.14", - "@vimazing/vim-snake": "file:../", + "@vimazing/typing-chud": "file:../", "react": "^19.1.1", "react-dom": "^19.1.1", }, @@ -211,7 +211,7 @@ "@types/react-dom": ["@types/react-dom@19.2.2", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-9KQPoO6mZCi7jcIStSnlOWn2nEF3mNmyr3rIAsGnAbQKYbRLyqmeSc39EVgtxXVia+LMT8j3knZLAZAh+xLmrw=="], - "@vimazing/vim-snake": ["@vimazing/vim-snake@file:..", { "devDependencies": { "@types/node": "^24.7.2", "@types/react": "^19.2.2", "typescript": "~5.9.3" }, "peerDependencies": { "react": "^19.1.1" } }], + "@vimazing/typing-chud": ["@vimazing/typing-chud@file:..", { "devDependencies": { "@types/node": "^24.7.2", "@types/react": "^19.2.2", "typescript": "~5.9.3" }, "peerDependencies": { "react": "^19.1.1" } }], "@vitejs/plugin-react": ["@vitejs/plugin-react@5.0.4", "", { "dependencies": { "@babel/core": "^7.28.4", "@babel/plugin-transform-react-jsx-self": "^7.27.1", "@babel/plugin-transform-react-jsx-source": "^7.27.1", "@rolldown/pluginutils": "1.0.0-beta.38", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, "peerDependencies": { "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-La0KD0vGkVkSk6K+piWDKRUyg8Rl5iAIKRMH0vMJI0Eg47bq1eOxmoObAaQG37WMW9MSyk7Cs8EIWwJC1PtzKA=="], diff --git a/example/src/App.tsx b/example/src/App.tsx index 72efe07..f1b7839 100755 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -17,12 +17,30 @@ function App() {

Typing Chud

-
+
- Time: {formatTime(scoreManager.timeValue)} +
Time
+
{formatTime(scoreManager.timeValue)}
- Keystrokes: {scoreManager.totalKeystrokes} +
Score
+
{scoreManager.score}
+
+
+
Correct
+
{scoreManager.correct}
+
+
+
Missed
+
{scoreManager.missed}
+
+
+
Deaths
+
{scoreManager.deaths}
+
+
+
Keystrokes
+
{scoreManager.totalKeystrokes}
diff --git a/package.json b/package.json index bb52ee1..ff357a0 100755 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "dist" ], "scripts": { + "dev": "cd example && bun run dev", "build": "tsc --outDir dist --declaration --emitDeclarationOnly false && node -e \"const fs = require('node:fs'); fs.mkdirSync('dist', { recursive: true }); fs.copyFileSync('src/game.css', 'dist/game.css'); fs.copyFileSync('package.json', 'dist/package.json');\"", "prepublishOnly": "npm run build" }, diff --git a/src/types.ts b/src/types.ts index 49c3608..6513c55 100755 --- a/src/types.ts +++ b/src/types.ts @@ -82,6 +82,10 @@ export type CursorManager = { export type ScoreManager = { timeValue: number; totalKeystrokes: number; + score: number; + correct: number; + missed: number; + deaths: number; }; // ============================================================================ @@ -94,13 +98,18 @@ export type GameStatusManager = { startGame: () => void; quitGame: () => void; level: number; - score: number; - togglePause: () => void; shipState: ShipState; shipExplosionTime: number; updatePlayerX: (x: number) => void; handleTypedLetter: (letter: string) => void; setGameLoopCallback: (callback: (deltaTime: number, letters: FallingLetter[]) => void) => void; + addCorrect: () => void; + addMissed: () => void; + addDeath: () => void; + score: number; + correct: number; + missed: number; + deaths: number; }; // ============================================================================ diff --git a/src/useGameStatus/useGameStatus.ts b/src/useGameStatus/useGameStatus.ts index 26ce4e3..bfcf06e 100755 --- a/src/useGameStatus/useGameStatus.ts +++ b/src/useGameStatus/useGameStatus.ts @@ -14,6 +14,10 @@ export function useGameStatus(onGameLoopTick?: (deltaTime: number, letters: Fall const [gameStatus, setGameStatus] = useState("waiting"); const [shipState, setShipState] = useState("normal"); const [shipExplosionTime, setShipExplosionTime] = useState(0); + const [score, setScore] = useState(0); + const [correct, setCorrect] = useState(0); + const [missed, setMissed] = useState(0); + const [deaths, setDeaths] = useState(0); const gameLoopRef = useRef(null); const lastFrameTimeRef = useRef(0); const lettersRef = useRef([]); @@ -61,6 +65,7 @@ export function useGameStatus(onGameLoopTick?: (deltaTime: number, letters: Fall letter.stateStartTime = currentTime; setShipState('exploding'); setShipExplosionTime(performance.now()); + setDeaths(prev => prev + 1); // Clear letters and reset spawn timer after explosion animation setTimeout(() => { @@ -88,6 +93,10 @@ export function useGameStatus(onGameLoopTick?: (deltaTime: number, letters: Fall lastFrameTimeRef.current = performance.now(); lastSpawnRef.current = performance.now(); lettersRef.current = []; + setScore(0); + setCorrect(0); + setMissed(0); + setDeaths(0); setGameStatus("started"); gameLoopRef.current = requestAnimationFrame(gameLoop); }, [gameLoop]); @@ -108,6 +117,8 @@ export function useGameStatus(onGameLoopTick?: (deltaTime: number, letters: Fall if (typedLetter.toUpperCase() === currentLetter.letter) { currentLetter.state = 'exploding'; currentLetter.stateStartTime = performance.now(); + setScore(prev => prev + 1); + setCorrect(prev => prev + 1); setTimeout(() => { lettersRef.current = []; @@ -116,6 +127,8 @@ export function useGameStatus(onGameLoopTick?: (deltaTime: number, letters: Fall } else { currentLetter.state = 'wrong'; currentLetter.stateStartTime = performance.now(); + setScore(prev => Math.max(0, prev - 1)); + setMissed(prev => prev + 1); setTimeout(() => { if (currentLetter.state === 'wrong') { @@ -133,6 +146,20 @@ export function useGameStatus(onGameLoopTick?: (deltaTime: number, letters: Fall onGameLoopTickRef.current = callback; }, []); + const addCorrect = useCallback(() => { + setScore(prev => prev + 1); + setCorrect(prev => prev + 1); + }, []); + + const addMissed = useCallback(() => { + setScore(prev => Math.max(0, prev - 1)); + setMissed(prev => prev + 1); + }, []); + + const addDeath = useCallback(() => { + setDeaths(prev => prev + 1); + }, []); + return { gameStatus, setGameStatus, @@ -143,6 +170,13 @@ export function useGameStatus(onGameLoopTick?: (deltaTime: number, letters: Fall shipExplosionTime, updatePlayerX, setGameLoopCallback, + addCorrect, + addMissed, + addDeath, + score, + correct, + missed, + deaths, }; } diff --git a/src/useScore/useScore.ts b/src/useScore/useScore.ts index d1b71e0..753acdc 100755 --- a/src/useScore/useScore.ts +++ b/src/useScore/useScore.ts @@ -16,7 +16,11 @@ export function useScore(gameStatusManager: any): ScoreManager { return { timeValue, - totalKeystrokes: 0, // TODO: Track keystrokes + totalKeystrokes: 0, // TODO: Track keystrokes from keyboard input + score: gameStatusManager.score, + correct: gameStatusManager.correct, + missed: gameStatusManager.missed, + deaths: gameStatusManager.deaths, }; }