feat: add difficulty selector to example app

Add interactive difficulty selection when game is idle:

Example App Changes:
- example/src/App.tsx: Add difficulty state and radio group
  - Import useState and Difficulty type
  - Radio buttons for Easy/Medium/Hard
  - Only visible when gameStatus === 'waiting'
  - Updates difficulty state on selection
  - Passes selected difficulty to useGame

Core Changes:
- src/useGame.ts: Respect difficulty changes
  - Add difficulty to useEffect dependencies
  - Regenerates board when difficulty changes

UX Flow:
1. User sees difficulty selector on idle screen
2. Selects difficulty (easy/medium/hard)
3. Presses Space to start
4. Puzzle generated with selected difficulty
5. On quit, returns to selector with last selection

Cell Counts:
- Easy: 40 empty cells (~41 givens)
- Medium: 50 empty cells (~31 givens)
- Hard: 60 empty cells (~21 givens)
This commit is contained in:
2025-10-22 18:42:35 +01:00
parent 07f07376be
commit 6f4a1accc4
2 changed files with 49 additions and 2 deletions
+48 -1
View File
@@ -1,9 +1,12 @@
import { useState } from "react";
import { useGame } from "../../src/useGame";
import { useKeyBindings } from "./useKeyBindings";
import type { Difficulty } from "../../src/types";
import '../../src/game.css';
function App() {
const gameManager = useGame({ difficulty: 'medium' }, useKeyBindings);
const [difficulty, setDifficulty] = useState<Difficulty>('medium');
const gameManager = useGame({ difficulty }, useKeyBindings);
const { containerRef, gameStatus, scoreManager } = gameManager;
const { timeValue, keystrokes, finalScore } = scoreManager;
@@ -17,6 +20,50 @@ function App() {
return (
<div className="flex flex-col items-center justify-around min-h-[100vh]">
<h1 className="text-6xl">VIMazing vim-sudoku example</h1>
{gameStatus === "waiting" && (
<div className="mx-auto my-4 w-fit bg-muted rounded-lg p-4">
<div className="text-center mb-4">
<div className="text-muted-foreground mb-2">Select Difficulty</div>
<div className="flex gap-4 justify-center">
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="difficulty"
value="easy"
checked={difficulty === 'easy'}
onChange={(e) => setDifficulty(e.target.value as Difficulty)}
className="cursor-pointer"
/>
<span>Easy</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="difficulty"
value="medium"
checked={difficulty === 'medium'}
onChange={(e) => setDifficulty(e.target.value as Difficulty)}
className="cursor-pointer"
/>
<span>Medium</span>
</label>
<label className="flex items-center gap-2 cursor-pointer">
<input
type="radio"
name="difficulty"
value="hard"
checked={difficulty === 'hard'}
onChange={(e) => setDifficulty(e.target.value as Difficulty)}
className="cursor-pointer"
/>
<span>Hard</span>
</label>
</div>
</div>
</div>
)}
<div className="mx-auto my-4 w-fit bg-muted rounded-lg p-4">
{(gameStatus === "started" || gameStatus === "game-won") && (
<div className="flex gap-8 text-sm font-mono items-center">