# Unified API Design ## Overview The VIMazing game engine provides a uniform external API for building grid-based games. The architecture is designed around three core hooks that can be composed to create various game types (maze, puzzle, strategy, etc.). ## Core Philosophy **Single Entry Point**: Platforms consume games by calling `useGame(gameOptions?, platformHook?)` and receive everything needed to run the game. **Composable Architecture**: Three fundamental hooks that work together: - `useBoard` - Manages the game board/level/field - `useCursor` - Manages player/actor/selector movement - `useGame` - Orchestrates everything together ## Primary API ### `useGame(gameOptions?, platformHook?)` The main entry point for consuming any game. ```tsx const gameManager = useGame(gameOptions?, platformHook?) ``` **Parameters:** - `gameOptions?: GameOptions` - Game-specific configuration - `platformHook?: (gameManager: GameManager) => void` - Platform-specific integration **Returns:** `GameManager` - Complete game management interface ## Core Types ### `GameOptions` ```tsx type GameOptions = { rows: number; cols: number; }; ``` ### `GameManager` ```tsx type GameManager = { containerRef: RefObject; renderBoard: () => void; cursor: CursorManager; hero: HeroManager; renderer: HeroRenderer; } & GameStatusManager & GameKeyManager; ``` ### `GameStatus` ```tsx type GameStatus = 'waiting' | 'started' | 'has-key' | 'paused' | 'game-over' | 'game-won' ``` ### `CursorManager` ```tsx type CursorManager = { position: () => Coord; mode: () => CursorMode; move: (dCols: number, dRows: number, count: number) => void; moveLeft: (count?: number) => void; moveRight: (count?: number) => void; moveUp: (count?: number) => void; moveDown: (count?: number) => void; moveToStart: () => void; moveToEnd: () => void; moveToTop: () => void; moveToBottom: () => void; repeatLastMotion: () => void; resetCount: () => void; getCount: () => string; setCount: (digit: string) => void; setLastKey: (key: string) => void; getLastKey: () => string; hero?: UseHeroType; } ``` ### `HeroManager` ```tsx type HeroManager = { position: Coord | null; canMoveTo: (coord: Coord) => boolean; moveTo: (coord: Coord) => void; pickupKey: () => void; reachExit: () => void; reset: () => void; } ``` ### `HeroRenderer` ```tsx type HeroRenderer = { render: () => void; updatePosition: (coord: Coord) => void; showCoordinates: (show: boolean) => void; } ``` ### `UseHeroType` ```tsx type UseHeroType = { heroPos: Coord | null; setHeroPos: (pos: Coord | null) => void; moveHero: (dr: number, dc: number, steps: number, gameStatus: GameStatus, setGameStatus: (s: GameStatus) => void) => void; canMoveTo: (coord: Coord) => boolean; moveTo: (coord: Coord) => void; pickupKey: () => void; reachExit: () => void; reset: () => void; } ``` ### `Cursor` (Legacy) ```tsx type Cursor = CursorManager; // For backward compatibility ``` ### `CursorMode` ```tsx type CursorMode = 'normal' | 'insert' | 'replace' | 'visual' | 'visual-line'; ``` ### `Coord` ```tsx type Coord = { row: number, col: number }; ``` ### `GameStatusManager` ```tsx type GameStatusManager = { gameStatus: GameStatus; setGameStatus: (status: GameStatus) => void; startGame: () => void; togglePause: (pause?: boolean) => void; quitGame: () => void; }; ``` ### `GameKeyManager` ```tsx type GameKeyManager = { keyLog: KeyLogEntry[]; clearKeyLog: () => void; getKeyLog: () => KeyLogEntry[]; }; ``` ### `CursorBindingContext` ```tsx type CursorBindingContext = { moveLeft: (count?: number) => void; moveRight: (count?: number) => void; moveUp: (count?: number) => void; moveDown: (count?: number) => void; moveToStart: () => void; moveToEnd: () => void; moveToTop: () => void; moveToBottom: () => void; repeatLastMotion: () => void; resetCount: () => void; getCount: () => string; setCount: (digit: string) => void; setLastKey: (key: string) => void; getLastKey: () => string; }; ``` ### `UseKeyBindingsParams` ```tsx type UseKeyBindingsParams = { cursor: CursorBindingContext; gameStatus: GameStatus; }; ``` ## Core Hooks Architecture ### `useBoard(cols, rows) -> BoardManager` Manages the game board/level structure. **Returns:** - `containerRef` - DOM container reference - `mazeRef` - Board data reference - `mazeInstanceRef` - Board generator instance - `rendererRef` - Renderer instance - `renderBoard()` - Initialize/render the board ### `useCursor(board: BoardManager, gameStatusManager: GameStatusManager) -> Cursor & { keyManager: GameKeyManager }` Manages player/actor movement and state, now includes integrated key management. **Returns:** - Position tracking - Mode management - Movement methods (with count support) - Directional movement helpers - Integrated `keyManager` for key logging and input handling ### `useGameKeys({ cursor, gameStatusManager }) -> GameKeyManager` Internal hook that handles keyboard input and maps keys to cursor actions. **Parameters:** - `cursor` - Cursor binding context with movement methods - `gameStatusManager` - Game status manager for state checking **Returns:** - `keyLog` - Array of key press entries with timestamps - `clearKeyLog()` - Clear the key log - `getKeyLog()` - Get current key log **Note:** This is now integrated within `useCursor` and not exposed as a separate hook. ### `useGameStatus() -> GameStatusManager` Manages game state and lifecycle. **Returns:** - `gameStatus` - Current game state - `setGameStatus` - State setter - `startGame()` - Start the game - `togglePause(pause?)` - Toggle or set pause state - `quitGame()` - Quit/reset the game ### `useGame(options: GameOptions, platformHook?) -> GameManager` Orchestrates board, cursor, and game status, provides game lifecycle management. **Returns:** - Complete game management interface - Game state control - Platform integration point ## Platform Integration ### Platform Hook Pattern ```tsx const platformHook = (gameManager: GameManager) => { // Platform-specific logic // - Custom key bindings // - Analytics tracking // - Save/load functionality // - Platform UI integration }; const gameManager = useGame(gameOptions, platformHook); ``` ## Game Implementation Pattern ### For New Game Types 1. **Extend `GameOptions`** with game-specific configuration 2. **Extend `GameStatus`** with game-specific states 3. **Implement game logic** in core hooks 4. **Maintain same external API** - `useGame(options, platformHook)` ### Example: Maze Game ```tsx // Platform consumes the game const mazeGame = useGame({ rows: 20, cols: 16 }, platformHook); // Everything needed is provided return (
{/* Game renders here */}
Status: {mazeGame.gameStatus}
); ``` ## Design Principles ### 1. **Uniform Interface** All games expose the same `GameManager` interface regardless of internal complexity. ### 2. **Composable Architecture** Core hooks can be mixed and matched for different game types. ### 3. **Platform Agnostic** Games work across different platforms without modification. ### 4. **Type Safety** Full TypeScript support with generated declarations. ### 5. **Tree-Shakeable** Only used functionality is included in the final bundle. ## Migration Path ### From Old Architecture - **Old**: Multiple specialized managers (`useMaze`, `usePlayer`, `useScore`, etc.) - **New**: Three core hooks with unified interfaces ### Benefits - **Simpler consumption**: Single `useGame` call - **Consistent API**: Same pattern across all games - **Better maintainability**: Clear separation of concerns - **Platform flexibility**: Easy integration points ## Future Extensibility The unified API is designed to accommodate: - Different game types (puzzles, strategy, arcade) - Various input methods (touch, gamepad, voice) - Multiple rendering backends (Canvas, WebGL, DOM) - Cross-platform features (save states, leaderboards, multiplayer) ## Implementation Status - ✅ **Type definitions** - Complete - ✅ **Core architecture** - Defined - ✅ **useGameStatus implementation** - Complete - ✅ **useGameKeys implementation** - Complete (integrated into useCursor) - ✅ **useCursor implementation** - Complete (with integrated key management) - ✅ **useBoard implementation** - Complete - ✅ **useGame implementation** - Complete (with flattened GameManager interface) - ✅ **React 19 optimization** - No useCallback/useMemo usage - ✅ **API refactoring** - Complete (key management moved into cursor module) - ✅ **Architecture refactoring** - Complete (separation of concerns implemented) - ✅ **Specialized hooks** - Complete (useMazeNavigation, useVimMotions, useAnimation) - ✅ **Single source of truth** - Complete (hero position as authoritative) - ✅ **React-first architecture** - Complete (state-driven rendering) - ✅ **Hero movement implementation** - Complete (hjkl, anchors, repeat with counts) - ✅ **Platform integration** - Ready - ⏳ **Advanced features** - Pending (scoring, animations, etc.) ## Recent Architecture Changes ### Separation of Concerns (Phase 1-3 Complete) - **useMazeNavigation**: Pure maze geometry and path validation - **useVimMotions**: Pure Vim-style input parsing - **useAnimation**: Pure movement animation system - **useCursor**: Input coordination and cursor state - **useHero**: Pure game logic and state management - **useHeroRender**: Pure visual presentation layer ### Single Source of Truth - Hero position is now the authoritative game state - Cursor position is derived from hero position - All rendering is driven by state changes ### Communication Patterns - Global events replaced with callback props and return values - Direct DOM manipulation moved to render layer - State-driven rendering instead of imperative updates ## Usage Examples ### Basic Game Integration ```tsx import { useGame } from "@vimazing/vim-maze"; function App() { const gameManager = useGame({ cols: 32, rows: 24 }); const { containerRef, gameStatus, keyManager } = gameManager; return (
); } ``` ### Platform Integration Pattern ```tsx import { useGame } from "@vimazing/vim-maze"; import { useKeyBindings } from "./useKeyBindings"; function App() { const gameManager = useGame({ cols: 32, rows: 24 }, useKeyBindings); const { containerRef, gameStatus, cursor, keyManager } = gameManager; return (
); } ``` ### Platform Hook Implementation ```tsx export const useKeyBindings = (gameManager: GameManager) => { const { gameStatus, startGame, quitGame, cursor, clearKeyLog } = gameManager; useEffect(() => { const handler = (e: KeyboardEvent) => { // Start game with 'i' if (e.key === "i" || e.key === "I") { if (["waiting", "game-over", "game-won"].includes(gameStatus)) { clearKeyLog(); startGame(); return; } } // Movement keys if (gameStatus === "started") { switch (e.key) { case "h": cursor.moveLeft(); break; case "j": cursor.moveDown(); break; case "k": cursor.moveUp(); break; case "l": cursor.moveRight(); break; } } // Quit with 'q' if (e.key === "q" || e.key === "Q") { quitGame(); } }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [gameStatus, startGame, quitGame, cursor, clearKeyLog]); }; ``` ### Internal Key Bindings Integration ```tsx // Inside useGame - simplified integration const { keyManager, ...cursor } = useCursor(board, gameStatusManager); const gameManager = { containerRef, renderBoard, cursor, ...keyManager, ...gameStatusManager, }; ``` ### Responsive Game Sizing ```tsx function App() { const isMobile = useIsMobile(); const cols = isMobile ? 12 : 32; const rows = isMobile ? 8 : 24; const gameManager = useGame({ cols, rows }, useKeyBindings); const { containerRef, keyManager } = gameManager; return (
); } ``` ## Platform Integration Examples ### Key Binding Platform Hook Platforms can provide custom key bindings by passing a platform hook: ```tsx const platformHook = (gameManager: GameManager) => { // Custom VIM-style bindings const { gameStatus, cursor, startGame, quitGame } = gameManager; // Implementation details... }; const gameManager = useGame(gameOptions, platformHook); ``` ### Analytics Platform Hook ```tsx const analyticsHook = (gameManager: GameManager) => { const { gameStatus } = gameManager; useEffect(() => { analytics.track('game_status_change', { status: gameStatus }); }, [gameStatus]); }; ``` ### Save/Load Platform Hook ```tsx const saveLoadHook = (gameManager: GameManager) => { const { cursor, gameStatus } = gameManager; const saveGame = () => { localStorage.setItem('gameState', JSON.stringify({ position: cursor.position(), status: gameStatus })); }; // Auto-save on status changes useEffect(() => { saveGame(); }, [gameStatus]); }; ``` ## Expected Platform Hook Interface Platform hooks receive the complete `GameManager` interface: ```tsx type GameManager = { containerRef: RefObject; renderBoard: () => void; cursor: CursorManager; hero: HeroManager; renderer: HeroRenderer; } & GameStatusManager & GameKeyManager; ``` This allows platforms to: - Add custom input handling - Implement analytics tracking - Add save/load functionality - Integrate with platform-specific UI - Extend game behavior without modifying core logic - Access key logging functionality via `clearKeyLog()` and `getKeyLog()`