feat: implement hint system (Shift+H) with scoring penalties

- Add getHint() method to BoardManager with two-tier priority:
  1. Find incorrect user-entered cells → flash red
  2. Find empty cells → show correct digit green temporarily
- Add Shift+H key handler in useCursor (normal mode only)
- Visual feedback:
  - Red flash (hint-flash-error): incorrect cells, 1s duration, red bg + border
  - Green flash (hint-flash-correct): correct digit shown, 1s duration, green bg + border
  - Distinct from invalid-move (no background, 500ms)
- Score penalties based on difficulty:
  - Easy: 25 points per hint
  - Medium: 50 points per hint
  - Hard: 100 points per hint
- Track hints used in scoreboard display
- Correct digit appears temporarily (1s) then disappears (player still does the work)
- Update example app to show hints counter and Shift+H instruction
This commit is contained in:
2025-10-22 21:24:06 +01:00
parent cfdf8100fb
commit 97c31ab6cc
9 changed files with 345 additions and 9 deletions
+7 -2
View File
@@ -8,7 +8,7 @@ function App() {
const [difficulty, setDifficulty] = useState<Difficulty>('easy');
const gameManager = useGame({ difficulty }, useKeyBindings);
const { containerRef, gameStatus, scoreManager } = gameManager;
const { timeValue, totalKeystrokes, finalScore } = scoreManager;
const { timeValue, totalKeystrokes, hintsUsed, finalScore } = scoreManager;
const formatTime = (ms: number) => {
const seconds = Math.floor(ms / 1000);
@@ -76,6 +76,11 @@ function App() {
<div className="text-muted-foreground">Keystrokes</div>
<div className="text-lg font-bold">{totalKeystrokes}</div>
</div>
<div className="w-px h-12 bg-muted-foreground/20" />
<div>
<div className="text-muted-foreground">Hints</div>
<div className="text-lg font-bold">{hintsUsed}</div>
</div>
{gameStatus === "game-won" && finalScore !== null && (
<>
<div className="w-px h-12 bg-muted-foreground/20" />
@@ -93,7 +98,7 @@ function App() {
</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 navigate • Press <kbd className="px-2 py-1 bg-muted rounded">i</kbd> to edit • Press <kbd className="px-2 py-1 bg-muted rounded">q</kbd> to quit</p>}
{gameStatus === "started" && <p>Use <kbd className="px-2 py-1 bg-muted rounded">hjkl</kbd> to navigate • Press <kbd className="px-2 py-1 bg-muted rounded">i</kbd> to edit • Press <kbd className="px-2 py-1 bg-muted rounded">Shift+H</kbd> for hint • 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>