feat: prepare vim-sudoku for unified API implementation

- Archive previous implementation attempt (src -> src_failed)
- Create comprehensive IMPLEMENTATION_PLAN.md with phased approach
- Create IMPLEMENTATION.md to track implementation progress
- Update example app to match vim-maze patterns:
  - Simplify UI to desktop-only (no mobile support)
  - Add score metrics display (time, keystrokes, final score)
  - Update instructions for sudoku-specific keys (hjkl, i for edit)
  - Remove unnecessary mobile hooks

Phase 5 complete. Ready to begin Phase 10 (Setup & Core Types).
This commit is contained in:
2025-10-22 18:08:33 +01:00
parent d0a51d7079
commit 9292b83dd0
25 changed files with 368 additions and 85 deletions
+49 -9
View File
@@ -1,17 +1,57 @@
import { useGame } from "../../src/useGame";
import { useKeyBindings } from './useKeyBindings';
import "../../src/game.css";
import { useKeyBindings } from "./useKeyBindings";
import '../../src/game.css';
export default function App() {
const { containerRef } = useGame({}, useKeyBindings);
function App() {
const gameManager = useGame({ difficulty: 'medium' }, useKeyBindings);
const { containerRef, gameStatus, scoreManager } = gameManager;
const { timeValue, keystrokes, finalScore } = scoreManager;
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 style={{ padding: 16 }}>
<h1>VIMazing Sudoku</h1>
<div className="relative w-fit mx-auto">
<div ref={containerRef} />
<div className="flex flex-col items-center justify-around min-h-[100vh]">
<h1 className="text-6xl">VIMazing vim-sudoku example</h1>
<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">
<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>
{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="sudoku_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 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 === "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;
-30
View File
@@ -1,30 +0,0 @@
import * as React from "react";
const MOBILE_BREAKPOINT = 768;
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean>(
typeof window !== "undefined"
? window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`).matches
: false
);
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
const onChange = (event: MediaQueryListEvent) => {
setIsMobile(event.matches);
};
// Set initial value
setIsMobile(mql.matches);
// Add listener
mql.addEventListener("change", onChange);
// Cleanup
return () => mql.removeEventListener("change", onChange);
}, []);
return isMobile;
}