From 7ccf7cdd55bddc620b6dea2b1edfbd1a7e559713 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Wed, 22 Oct 2025 22:03:44 +0100 Subject: [PATCH] refactor: simplify scoring to remove efficiency-based complexity MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove efficiency calculation and game-over at 150% - Remove optimal steps tracking and complex logic timing - Simplify to time + keystrokes only scoring - New formula: score = min(1000, (1000 - time_penalty - keystroke_penalty) × size_multiplier) - time_penalty = seconds / 10 - keystroke_penalty = keystrokes / 2 - size_multiplier = max(1.0, (rows × cols) / 500) - Maze size-based multiplier rewards larger mazes - Remove optimalSteps and efficiency from ScoreManager type - Pass rows and cols to useScore for size calculation - Align with vim-sudoku's simplified scoring approach --- AGENTS.md | 191 ------ IMPLEMENTATION_INSTRUCTIONS.md | 134 ----- REFACTORING_PLAN.md | 292 --------- UNIFIED_API.md | 1016 -------------------------------- UNIFIED_API_OLD.md | 519 ---------------- src/useGame.ts | 2 +- src/useScore/types.ts | 2 - src/useScore/useScore.ts | 101 +--- 8 files changed, 16 insertions(+), 2241 deletions(-) delete mode 100644 AGENTS.md delete mode 100644 IMPLEMENTATION_INSTRUCTIONS.md delete mode 100644 REFACTORING_PLAN.md delete mode 100644 UNIFIED_API.md delete mode 100644 UNIFIED_API_OLD.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 29ce1f6..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,191 +0,0 @@ -# Development Guidelines & Agent Instructions - -## Documentation Management - -### Automatic Documentation Updates -- **UNIFIED_API.md**: Automatically update after any implementation changes -- **Track**: Implementation status, API changes, new types, completed features -- **No prompting required**: Keep docs in sync with codebase proactively - -## Code Conventions - -### File Structure & Organization -``` -src/ -├── useBoard/ # Board/level management -│ ├── index.ts # Public exports -│ ├── useBoard.ts # Main hook implementation -│ ├── MazeGenerator.ts -│ └── MazeRenderer.ts -├── useCursor/ # Player/actor management -│ ├── index.ts # Public exports -│ └── useCursor.ts # Main hook implementation -├── types.ts # All shared TypeScript types -├── useGame.ts # Main orchestrator hook -└── index.ts # Main package exports -``` - -### TypeScript Patterns -- **Type-first approach**: Define types before implementation -- **Explicit imports**: Always import types with `import type` -- **Shared types**: Keep all types in `types.ts` for consistency -- **Return type annotations**: Use `ReturnType` for hook return types - -### Hook Structure -```tsx -// Standard hook pattern -export function useHookName(param: Type): ReturnType { - // 1. Refs for persistent state - const ref = useRef(initialValue); - - // 2. State for reactive values - const [state, setState] = useState(initialValue); - - // 3. Internal functions (private) - function internalFunction() { - // Implementation - } - - // 4. Public API functions - function publicMethod() { - // Implementation - } - - // 5. Return object with explicit interface - return { - // Public API only - }; -} - -// Export type for consumers -export type HookManager = ReturnType; -``` - -### Naming Conventions -- **Hooks**: `use[FeatureName]` (e.g., `useBoard`, `useCursor`, `useGame`) -- **Types**: `PascalCase` with descriptive names (e.g., `GameManager`, `BoardManager`) -- **Functions**: `camelCase` with clear verbs (e.g., `renderBoard`, `movePlayer`) -- **Refs**: `[concept]Ref` pattern (e.g., `containerRef`, `mazeRef`) -- **Constants**: `UPPER_SNAKE_CASE` - -### Import Organization -```tsx -// 1. React imports -import { useRef, useState } from 'react'; -import type { RefObject } from 'react'; - -// 2. Internal type imports -import type { Cursor, BoardManager } from '../types'; - -// 3. Internal implementation imports -import { MazeGenerator } from './MazeGenerator'; -import { MazeRenderer } from './MazeRenderer'; -``` - -### Code Style -- **No comments**: Avoid adding comments unless explicitly requested -- **Minimal whitespace**: Clean, concise formatting -- **Explicit returns**: Always return objects from hooks -- **Destructuring**: Use object destructuring for parameters and returns -- **Arrow functions**: Prefer arrow functions for consistency -- **No useCallback/useMemo**: React 19 optimization makes these hooks unnecessary -- **useEffect dependencies**: Only use values, never functions as dependencies - -### Error Handling -- **Early returns**: Guard clauses at function start -- **Null checks**: Always validate refs and state before use -- **Graceful failures**: Return early if conditions aren't met - -## Architecture Principles - -### Unified API Design -- **Single entry point**: `useGame(options?, platformHook?)` for all games -- **Composable hooks**: Three core hooks (`useBoard`, `useCursor`, `useGame`) -- **Platform agnostic**: Games work across platforms without modification -- **Type safety**: Full TypeScript support throughout - -### Hook Composition Pattern -```tsx -// Standard composition in useGame -export function useGame(options: GameOptions, platformHook?: unknown): GameManager { - const boardManager = useBoard(cols, rows); - const cursor = useCursor(boardManager); - - // Orchestrate and return unified interface - return { - // Expose necessary functionality - }; -} -``` - -### Platform Integration -- **Platform hook pattern**: Optional function parameter for platform-specific logic -- **Non-intrusive**: Core game logic works without platform integration -- **Extensible**: Platform can add analytics, custom bindings, etc. - -## Development Workflow - -### Implementation Order -1. **Types first**: Define interfaces in `types.ts` -2. **Core hooks**: Implement `useBoard`, `useCursor` -3. **Orchestrator**: Implement `useGame` -4. **Integration**: Add platform hook support -5. **Features**: Add advanced features (scoring, animations, etc.) - -### Testing & Validation -- **Type checking**: Run TypeScript compiler after changes -- **Build verification**: Ensure build process works -- **Example testing**: Test with example application - -### File Management -- **Prefer editing**: Modify existing files over creating new ones -- **Never create docs**: Don't create README files unless explicitly requested -- **Consistent exports**: Use `index.ts` files for clean public APIs - -## Agent Behavior Rules - -### Proactive Documentation -- Update `UNIFIED_API.md` after every meaningful change -- Track implementation status changes -- Document new types and interfaces -- Note API modifications and architectural decisions - -### Code Generation -- Follow existing patterns exactly -- Mimic code style from surrounding files -- Use established naming conventions -- Maintain type safety throughout - -### Communication -- **Concise responses**: Keep explanations brief and direct -- **Focus on code**: Prioritize implementation over explanation -- **No elaboration**: Avoid unnecessary preamble or summary -- **Direct answers**: Respond to specific requests without extra context - -### Quality Assurance -- **Type safety first**: Ensure all TypeScript types are correct -- **Import consistency**: Follow established import patterns -- **Code style matching**: Mirror existing code style exactly -- **Error prevention**: Write defensive code with proper validation - -## Forbidden Patterns - -### Do NOT -- Add comments to code (unless requested) -- Create documentation files (except UNIFIED_API.md updates) -- Use generic variable names (`data`, `item`, etc.) -- Mix import styles (some type imports, some runtime) -- Break existing patterns without reason -- Write overly complex implementations -- Add unnecessary features -- Use `useCallback` or `useMemo` anywhere in the codebase -- Use functions as useEffect dependencies - -### Always DO -- Follow existing code patterns exactly -- Use descriptive, meaningful names -- Maintain type safety -- Keep implementations simple and focused -- Update documentation proactively -- Test builds after changes -- Use plain functions instead of `useCallback`/`useMemo` (React 19 optimization) \ No newline at end of file diff --git a/IMPLEMENTATION_INSTRUCTIONS.md b/IMPLEMENTATION_INSTRUCTIONS.md deleted file mode 100644 index f0d4621..0000000 --- a/IMPLEMENTATION_INSTRUCTIONS.md +++ /dev/null @@ -1,134 +0,0 @@ -# Implementation Instructions for New Games - -## Goal -Create a comprehensive `UNIFIED_API.md` that documents the current architecture in `src/` (not `src_old/`), replacing the old `UNIFIED_API_OLD.md`. This will serve as the blueprint for porting existing games and creating new games using the new unified architecture. - -## Files to Reference - -### Read These First -1. **AGENTS.md** - Contains all development guidelines, code conventions, naming patterns, hook structure, and architectural principles -2. **UNIFIED_API_OLD.md** - The old API documentation (for reference on what we're replacing) - -### Reference the New Codebase -- **src/useGame.ts** - Main orchestrator hook, entry point -- **src/useBoard/** - Board/level management - - useBoard.ts - - MazeGenerator.ts - - MazeRenderer.ts - - useMazeNavigation.ts -- **src/useCursor/** - Player/actor management - - useCursor.ts - - useHero.ts - - useHeroRender.ts - - useVimMotions.ts - - useGameKeys.ts - - useAnimation.ts -- **src/useScore/** - Scoring system - - useScore.ts - - useScorePaths.ts - - useScoreTime.ts - - useTimer.ts -- **src/useGameStatus.ts** - Game state management -- **src/types.ts** - All shared TypeScript types - -### Example Implementation -- **example/src/App.tsx** - Shows how to use the new unified API - -## What the New UNIFIED_API.md Should Cover - -### 1. Architecture Overview -- Single entry point: `useGame(options?, platformHook?)` -- Three core hooks composition pattern -- Scoring system integration -- Type-safe throughout - -### 2. Core Hooks Reference -For each hook, document: -- **Purpose** - What it does -- **Parameters** - What it accepts with types -- **Return Type** - What it returns with type definition -- **Dependencies** - What other hooks it depends on -- **Example Usage** - Real code example - -Core hooks to document: -- `useGame()` - Main orchestrator -- `useBoard()` - Board/maze management -- `useCursor()` - Player movement and controls -- `useScore()` - Scoring system -- `useGameStatus()` - Game state -- Supporting hooks in each module - -### 3. Type System Documentation -- Document all major types from `src/types.ts`: - - `GameManager` - - `BoardManager` - - `CursorManager` - - `ScoreManager` - - `GameStatusManager` - - `Coord`, `MazeCell`, etc. -- Show relationships between types -- Include examples of how types flow through the system - -### 4. File Structure & Organization -- Show the complete `src/` directory structure -- Explain the purpose of each module -- Show public exports from `index.ts` files - -### 5. Naming Conventions & Patterns -- Hook naming: `use[FeatureName]()` -- Type naming: `PascalCase` with descriptive names -- Function naming: `camelCase` with clear verbs -- Pattern examples from actual code - -### 6. Key Implementation Details -- How board generation and rendering works -- How player movement and vim motions are handled -- How scoring is calculated -- How game status transitions work -- Animation system for multi-step movements -- Keyboard input handling and key binding system - -### 7. Integration Points for New Games -- What you need to implement for a new game -- How to customize board generation -- How to customize player movement rules -- How to customize scoring logic -- Platform hook integration (if applicable) - -### 8. Code Examples -- Minimal game setup example -- Complete feature examples (movement, scoring, rendering) -- Type usage examples -- Common patterns and best practices - -### 9. Migration Guide (Optional) -- How to port games from `src_old/` structure to new structure -- Common gotchas and solutions -- Before/after code comparisons - -## Output Requirements - -The new `UNIFIED_API.md` should: -- Be comprehensive but readable -- Use clear sections and subsections -- Include code blocks for all examples -- Show actual types and function signatures from the codebase -- Be focused on developers porting games or creating new ones -- Be maintainable - designed to stay in sync with actual code in `src/` - -## Success Criteria - -When done, a developer should be able to: -1. Understand the complete architecture without reading source code -2. Know exactly what types and functions are available -3. Understand how hooks compose together -4. Have templates/examples for common tasks -5. Easily port a game from `src_old/` to new structure -6. Create a completely new game following this pattern - -## Notes - -- AGENTS.md already contains code conventions and patterns - reference it heavily -- Focus the new doc on WHAT each part does and HOW to use it, not just code style -- Keep the doc well-organized so it's easy to find specific information -- Show how everything connects together (data flow, composition) diff --git a/REFACTORING_PLAN.md b/REFACTORING_PLAN.md deleted file mode 100644 index e9dd258..0000000 --- a/REFACTORING_PLAN.md +++ /dev/null @@ -1,292 +0,0 @@ -# Vim-Maze Architecture Refactoring Plan - -## Current Problems Identified - -### 1. Dual Position System -- `useCursor` maintains logical cursor position -- `useHero` maintains actual hero position -- They sync but can diverge during animations -- Creates confusion about single source of truth - -### 2. Mixed Responsibilities -- **useCursor**: Input abstraction + maze geometry + event dispatching -- **useHero**: Game logic + DOM manipulation + animation + visual feedback -- **useHeroRender**: Hero rendering + coordinate system + DOM querying - -### 3. Anti-Patterns -- Global events for component communication (`maze-invalid`) -- Direct DOM manipulation breaking React abstraction -- Hard-coded CSS selectors and maze structure assumptions -- Debug code left in production (`console.log`) - -## Target Architecture - -### Clear Separation of Concerns - -``` -useCursor → Pure input abstraction (Vim motions → coordinates) -useHero → Pure game logic (position validation, state transitions) -useHeroRender → Pure visual layer (CSS classes, coordinate display) -``` - -### Single Source of Truth -- Hero position = game state (authoritative) -- Cursor position = input intent (derived from hero) -- Render position = derived from hero position - -### React-First Architecture -- State-driven rendering instead of DOM manipulation -- Callback props instead of global events -- Ref-based access instead of direct queries - -## Detailed Refactoring Steps - -### Phase 1: Extract Specialized Hooks - -#### 1.1 Create `useMazeNavigation` -**Location:** `src/useBoard/useMazeNavigation.ts` -**Responsibilities:** -- Wall detection and boundary finding -- Path validation for movements -- Anchor target detection (line start/end, top/bottom) -- Pure functions, no side effects - -**Interface:** -```tsx -type MazeNavigator = { - isValidMove: (from: Coord, to: Coord) => boolean; - findAnchorTarget: (from: Coord, direction: 'left'|'right'|'top'|'bottom') => Coord | null; - validatePath: (from: Coord, direction: Coord, steps: number) => boolean; -} -``` - -#### 1.2 Create `useVimMotions` -**Location:** `src/useCursor/useVimMotions.ts` -**Responsibilities:** -- Parse Vim-style input (hjkl, counts, gg, G, ^, $, .) -- Convert motions to coordinate changes -- Manage count accumulation and last motion tracking -- Pure input parsing, no game logic - -**Interface:** -```tsx -type VimMotionSystem = { - processKey: (key: string) => { type: 'move'|'anchor'|'repeat', data: any } | null; - resetCount: () => void; - getCount: () => number; - setLastMotion: (motion: Motion) => void; - repeatLastMotion: () => Motion | null; -} -``` - -#### 1.3 Create `useAnimation` -**Location:** `src/useCursor/useAnimation.ts` -**Responsibilities:** -- Smooth interpolated movement for multi-step actions -- Animation timing and frame management -- Cancellation handling (Escape key) -- Trail effects during animation - -**Interface:** -```tsx -type AnimationSystem = { - animateMovement: (from: Coord, to: Coord, steps: number, onComplete: () => void) => void; - cancelAnimation: () => void; - isAnimating: () => boolean; -} -``` - -### Phase 2: Refactor Core Hooks - -#### 2.1 Simplify `useCursor` -**New Responsibilities:** -- Coordinate input from Vim motions -- Delegate navigation to `useMazeNavigation` -- Delegate animation to `useAnimation` -- Maintain cursor state (synced with hero) - -**New Interface:** -```tsx -type CursorManager = { - position: () => Coord; - mode: () => CursorMode; - move: (direction: Coord, count?: number) => boolean; - moveToAnchor: (target: 'start'|'end'|'top'|'bottom') => boolean; - repeatLastMotion: () => boolean; -} -``` - -#### 2.2 Refactor `useHero` -**New Responsibilities:** -- Pure game logic and state management -- Position validation and game rule enforcement -- Game state transitions (key pickup, victory) -- No DOM manipulation or visual effects - -**New Interface:** -```tsx -type HeroManager = { - position: Coord | null; - canMoveTo: (coord: Coord) => boolean; - moveTo: (coord: Coord) => void; - pickupKey: () => void; - reachExit: () => void; - reset: () => void; -} -``` - -#### 2.3 Refactor `useHeroRender` -**New Responsibilities:** -- Pure visual presentation -- CSS class management for hero position -- Coordinate system display -- No game logic or state management - -**New Interface:** -```tsx -type HeroRenderer = { - render: () => void; - updatePosition: (coord: Coord) => void; - showCoordinates: (show: boolean) => void; -} -``` - -### Phase 3: Fix Communication Patterns - -#### 3.1 Replace Global Events -**Current:** `window.dispatchEvent(new Event("maze-invalid"))` -**New:** Callback props and return values - -```tsx -// Instead of events, use return values -const canMove = cursor.move(direction, count); -if (!canMove) { - // Handle invalid move in caller - flashInvalid(); -} -``` - -#### 3.2 Remove Direct DOM Manipulation -**Current:** `getCellEl().classList.add("hero")` -**New:** State-driven rendering - -```tsx -// In render component -
-``` - -#### 3.3 Replace Direct Queries -**Current:** `container.querySelector("#maze")` -**New:** Ref-based access - -```tsx -const mazeRef = useRef(null); -// Use mazeRef.current instead of queries -``` - -### Phase 4: Update Integration Points - -#### 4.1 Fix `useGame` Integration -```tsx -export function useGame(options: GameOptions, platformHook?: unknown): GameManager { - const board = useBoard(cols, rows); - const gameStatusManager = useGameStatus(); - - // New clean composition - const hero = useHero(board, gameStatusManager); - const cursor = useCursor(board, hero, gameStatusManager); - const renderer = useHeroRender(board, hero); - const keyManager = useGameKeys({ cursor, gameStatusManager }); - - const gameManager = { - containerRef: board.containerRef, - renderBoard: board.renderBoard, - cursor, - hero, - renderer, - ...keyManager, - ...gameStatusManager, - }; - - if (typeof platformHook === 'function') { - platformHook(gameManager); - } - - return gameManager; -} -``` - -#### 4.2 Update Platform Hook Interface -```tsx -type GameManager = { - containerRef: RefObject; - renderBoard: () => void; - cursor: CursorManager; - hero: HeroManager; - renderer: HeroRenderer; -} & GameStatusManager & GameKeyManager; -``` - -### Phase 5: Clean Up and Optimize - -#### 5.1 Remove Debug Code -- Remove `console.log('gameStatus', gameStatus)` from `useHeroRender` -- Remove any other debug statements - -#### 5.2 Update Type Definitions -- Update all interfaces in `types.ts` -- Ensure proper export of new types -- Update `UNIFIED_API.md` documentation - -#### 5.3 Update Tests and Examples -- Fix any broken tests -- Update example usage -- Ensure platform hooks still work - -## Implementation Order - -1. **Create new specialized hooks** (Phase 1) -2. **Refactor useCursor** (Phase 2.1) -3. **Refactor useHero** (Phase 2.2) -4. **Refactor useHeroRender** (Phase 2.3) -5. **Fix communication patterns** (Phase 3) -6. **Update integration points** (Phase 4) -7. **Clean up and optimize** (Phase 5) - -## Success Criteria - -- ✅ Single source of truth for position (hero position) -- ✅ Clear separation of concerns between hooks -- ✅ No global events or direct DOM manipulation -- ✅ All functionality preserved (Vim motions, animation, etc.) -- ✅ Type safety maintained throughout -- ✅ Platform hooks continue to work -- ✅ Documentation updated to reflect new architecture - -## Files to Modify - -### New Files -- `src/useBoard/useMazeNavigation.ts` -- `src/useCursor/useVimMotions.ts` -- `src/useCursor/useAnimation.ts` - -### Modified Files -- `src/types.ts` -- `src/useCursor/useCursor.ts` -- `src/useCursor/useHero.ts` -- `src/useCursor/useHeroRender.ts` -- `src/useGame.ts` -- `UNIFIED_API.md` - -### Files to Remove -- None (all existing functionality preserved) - -## Risk Mitigation - -- **Incremental changes**: Implement one phase at a time -- **Backward compatibility**: Ensure platform hooks still work -- **Type safety**: Use TypeScript to catch issues early -- **Testing**: Verify functionality after each phase -- **Documentation**: Keep docs in sync with changes - -This refactoring will result in a cleaner, more maintainable architecture while preserving all existing functionality. \ No newline at end of file diff --git a/UNIFIED_API.md b/UNIFIED_API.md deleted file mode 100644 index ead3e5b..0000000 --- a/UNIFIED_API.md +++ /dev/null @@ -1,1016 +0,0 @@ -# Unified API Documentation - -Complete reference for the vim-maze game engine architecture. This document describes the current implementation in `src/` and serves as a blueprint for porting games and creating new games. - -## Table of Contents - -1. [Architecture Overview](#architecture-overview) -2. [File Structure](#file-structure) -3. [Type System](#type-system) -4. [Core Hooks](#core-hooks) -5. [Supporting Hooks](#supporting-hooks) -6. [Implementation Examples](#implementation-examples) -7. [Integration Points](#integration-points) - ---- - -## Architecture Overview - -The vim-maze engine follows a composable hook architecture with a single entry point: `useGame(options?, platformHook?)`. - -### Design Principles - -- **Single Entry Point**: All games use `useGame()` to initialize -- **Composable Hooks**: Three core hooks compose to create the complete system -- **Type-Safe**: Full TypeScript support throughout -- **Platform Agnostic**: Works across any platform without modification -- **Platform Optional**: Optional platform hook for analytics and custom bindings - -### Core Composition - -```tsx -// Inside useGame: -const boardManager = useBoard(cols, rows); -const { cursor, keyManager } = useCursor(boardManager, gameStatusManager); -const scoreManager = useScore({ board, hero, gameStatusManager, keyManager }); -const heroRenderer = useHeroRender({ gameStatus, board, hero }); - -// Returns unified GameManager interface -return { - containerRef, - renderBoard, - cursor, - hero, - renderer, - scoreManager, - gameStatus, - startGame, - togglePause, - quitGame, - keyLog, - ... -}; -``` - ---- - -## File Structure - -``` -src/ -├── index.ts # Main package exports -├── types.ts # Re-exports all types from modules, useGame-specific types -├── useGame.ts # Main orchestrator hook -├── useGameStatus/ -│ ├── index.ts # Public exports -│ ├── types.ts # GameStatus, GameStatusManager -│ └── useGameStatus.ts # Game state management -├── useBoard/ -│ ├── index.ts # Public exports -│ ├── types.ts # Coord, MazeCell, MazeData, BoardManager, MazeNavigator -│ ├── useBoard.ts # Board/maze management hook -│ ├── MazeGenerator.ts # Maze generation algorithm -│ ├── MazeRenderer.ts # DOM rendering for maze -│ └── useMazeNavigation.ts # Pathfinding and validation -├── useCursor/ -│ ├── index.ts # Public exports -│ ├── types.ts # CursorMode, CursorManager, HeroManager, Motion, AnimationSystem, VimMotionSystem -│ ├── useCursor.ts # Main cursor/input hook -│ ├── useHero.ts # Player position and movement -│ ├── useHeroRender.ts # Hero rendering on DOM -│ ├── useVimMotions.ts # Vim keybinding system -│ ├── useGameKeys.ts # Game-specific key handling -│ └── useAnimation.ts # Multi-step movement animation -└── useScore/ - ├── index.ts # Public exports - ├── types.ts # TimerManager, ScorePathsResult, ScoreManager - ├── useScore.ts # Main scoring hook - ├── useScorePaths.ts # Distance calculations - ├── useScoreTime.ts # Timer integration - └── useTimer.ts # Timer state management -``` - ---- - -## Type System - -Types are organized by module for clarity and maintainability: - -- **`src/useBoard/types.ts`**: Board, maze, and navigation types -- **`src/useCursor/types.ts`**: Cursor, hero, vim motions, and animation types -- **`src/useGameStatus/types.ts`**: Game status and lifecycle types -- **`src/useScore/types.ts`**: Scoring and timer types -- **`src/types.ts`**: Re-exports all module types + useGame-specific types (GameOptions, GameKeyManager, GameManager) - -This modular organization keeps types colocated with their related hooks while maintaining a unified `src/types.ts` entry point for convenience. - -### Main Types - -#### GameManager -The primary interface returned by `useGame()`. - -```typescript -type GameManager = { - // DOM reference for rendering - containerRef: RefObject; - - // Rendering - renderBoard: () => void; - - // Core managers - cursor: CursorManager; - hero: HeroManager; - renderer: HeroRenderer; - scoreManager: ScoreManager; - - // Game status - gameStatus: GameStatus; - setGameStatus: (status: GameStatus) => void; - startGame: () => void; - togglePause: (pause?: boolean) => void; - quitGame: () => void; - - // Input tracking - keyLog: KeyLogEntry[]; - clearKeyLog: () => void; - getKeyLog: () => KeyLogEntry[]; -}; -``` - -#### GameStatus -State machine for game lifecycle. - -```typescript -type GameStatus = - | 'waiting' // Initial state, awaiting start - | 'started' // Game in progress, before key pickup - | 'has-key' // Key obtained, heading to exit - | 'paused' // Game paused - | 'game-over' // Game lost (efficiency > 150%) - | 'game-won'; // Game completed -``` - -#### BoardManager -Manages maze data and rendering. - -```typescript -type BoardManager = { - containerRef: RefObject; - mazeRef: RefObject; - mazeInstanceRef: RefObject; - rendererRef: RefObject; - renderBoard: () => void; -}; -``` - -#### CursorManager -Handles player input and vim motion system. - -```typescript -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; // Move to row start (^) - moveToEnd: () => void; // Move to row end ($) - moveToTop: () => void; // Move to maze top (g) - moveToBottom: () => void; // Move to maze bottom (G) - repeatLastMotion: () => void; // Repeat last motion (.) - resetCount: () => void; - getCount: () => string; - hasCount: () => boolean; - setCount: (digit: string) => void; - setLastKey: (key: string) => void; - getLastKey: () => string; - hero?: UseHeroType; -}; -``` - -#### HeroManager -Player position and interaction. - -```typescript -type HeroManager = { - position: Coord | null; - canMoveTo: (coord: Coord) => boolean; - moveTo: (coord: Coord) => void; - pickupKey: () => void; - reachExit: () => void; - reset: () => void; -}; -``` - -#### HeroRenderer -DOM rendering for hero. - -```typescript -type HeroRenderer = { - render: () => void; - updatePosition: (coord: Coord) => void; - showCoordinates: (show: boolean) => void; -}; -``` - -#### ScoreManager -Scoring system return type. - -```typescript -type ScoreManager = { - timeValue: number; - startTimer: () => void; - stopTimer: () => void; - resetTimer: () => void; - distToKey: number; // Distance from hero to key - distToExit: number; // Distance from hero to exit - keystrokes: number; // Total keystrokes made - optimalSteps: number; // Optimal path length - efficiency: number; // Percentage (keystrokes / optimal * 100) - finalScore: number | null; // Only set when game-won -}; -``` - -#### Coordinate & Maze Types -Basic data structures. - -```typescript -type Coord = { row: number; col: number }; - -type CellTag = 'wall' | 'door' | 'entrance' | 'exit' | 'key' | 'hero'; - -type MazeCell = CellTag[]; // Array of tags for a single cell - -type MazeData = { - maze: MazeCell[][]; - width: number; - height: number; - cols: number; - rows: number; - totalSteps: number; -}; - -type GameOptions = { - rows: number; - cols: number; -}; -``` - ---- - -## Core Hooks - -### useGame() - -Main orchestrator hook. Single entry point for all games. - -**Purpose**: Composes all sub-hooks into a unified interface and optionally integrates platform-specific functionality. - -**Parameters**: -```typescript -useGame(options: GameOptions, platformHook?: unknown): GameManager - -// GameOptions -{ - rows: number; // Maze height in cells - cols: number; // Maze width in cells -} - -// platformHook (optional) -// A function that receives the GameManager for platform integration -// function(gameManager: GameManager): void -``` - -**Return Type**: `GameManager` - Complete game interface - -**Dependencies**: -- `useBoard()` - Maze generation and rendering -- `useCursor()` - Input and movement -- `useGameStatus()` - Game lifecycle -- `useScore()` - Scoring system -- `useHeroRender()` - Visual rendering - -**Example**: -```tsx -const gameManager = useGame( - { cols: 32, rows: 24 }, - platformHook // Optional platform integration -); - -const { containerRef, gameStatus, startGame } = gameManager; -``` - ---- - -### useBoard() - -Board and maze management. - -**Purpose**: Generates maze, manages its data structure, and handles DOM rendering. - -**Parameters**: -```typescript -useBoard(cols: number, rows: number): BoardManager -``` - -**Return Type**: `BoardManager` -```typescript -{ - containerRef: RefObject; - mazeRef: RefObject; - mazeInstanceRef: RefObject; - rendererRef: RefObject; - renderBoard: () => void; -} -``` - -**Dependencies**: -- `MazeGenerator` - Generates random maze -- `MazeRenderer` - Renders maze to DOM -- `useMazeNavigation` - Pathfinding utilities - -**Key Features**: -- Generates randomized maze on mount -- Places key randomly in maze -- Manages maze reference for navigation -- Handles board re-rendering - -**Example**: -```tsx -const board = useBoard(32, 24); - -// Render maze to DOM -useEffect(() => { - if (board.containerRef.current) { - board.renderBoard(); - } -}, [board]); - -// Access maze data -const maze = board.mazeRef.current; // MazeCell[][] -``` - ---- - -### useCursor() - -Input handling and movement system with vim bindings. - -**Purpose**: Manages player cursor movement, vim key processing, and coordinate tracking. - -**Parameters**: -```typescript -useCursor( - board: BoardManager, - gameStatusManager: GameStatusManager -): CursorManager & { keyManager: GameKeyManager } -``` - -**Return Type**: -```typescript -CursorManager & { - keyManager: { - keyLog: KeyLogEntry[]; - clearKeyLog: () => void; - getKeyLog: () => KeyLogEntry[]; - } -} -``` - -**Vim Motion Support**: -- `h`, `j`, `k`, `l` - Move left, down, up, right -- `^` - Move to row start -- `$` - Move to row end -- `g` - Move to maze top -- `G` - Move to maze bottom -- `.` - Repeat last motion -- Number prefixes for counts (e.g., `5j` = move down 5 cells) - -**Dependencies**: -- `useHero()` - Hero position management -- `useMazeNavigation()` - Path validation -- `useVimMotions()` - Vim key processing -- `useGameKeys()` - Game-specific key handling - -**Example**: -```tsx -const { cursor, keyManager } = useCursor(board, gameStatusManager); - -// Direct movement -cursor.moveLeft(5); -cursor.moveDown(); - -// Query state -const pos = cursor.position(); // { row, col } -const count = cursor.getCount(); - -// Vim motions are processed by useGameKeys -keyManager.keyLog; // Track all keystrokes -``` - ---- - -### useGameStatus() - -Game lifecycle state management. - -**Purpose**: Manages game status transitions (waiting → started → has-key → game-won). - -**Parameters**: -```typescript -useGameStatus(): GameStatusManager -``` - -**Return Type**: -```typescript -{ - gameStatus: GameStatus; - setGameStatus: (status: GameStatus) => void; - startGame: () => void; - togglePause: (pause?: boolean) => void; - quitGame: () => void; -} -``` - -**State Transitions**: -``` -waiting ──start──> started ──pickup key──> has-key - ▲ ▲ ▲ - │ │ (pause) │ - │ └─────────────────────────┘ - │ paused - │ - └──quit─────────────────────────────────────┘ - -started/has-key ──efficiency > 150%──> game-over -started/has-key ──reach exit + has-key──> game-won -``` - -**Example**: -```tsx -const gameStatus = useGameStatus(); - -gameStatus.startGame(); -gameStatus.togglePause(); -gameStatus.quitGame(); -``` - ---- - -## Supporting Hooks - -### useScore() - -Scoring and performance metrics. - -**Purpose**: Calculates efficiency, tracks time and keystrokes, computes final score. - -**Parameters**: -```typescript -useScore({ - board: BoardManager; - hero: UseHeroType; - gameStatusManager: GameStatusManager; - keyManager?: { keyLog: any[] }; -}): ScoreManager -``` - -**Return Type**: `ScoreManager` - -**Scoring Logic**: -- **Optimal Steps**: Calculated as distance(entrance → key) + distance(key → exit) -- **Efficiency**: `Math.round((keystrokes / optimalSteps) * 100)` -- **Game Over Trigger**: `efficiency > 150%` -- **Final Score**: `Math.round((optimalMs / logicMs) * 100000)` when game-won -- **Max Score**: 100,000 points - -**Tracking**: -- Real-time keystroke count -- Elapsed time -- Distance to key and exit -- Player movement steps - -**Example**: -```tsx -const score = useScore({ board, hero, gameStatusManager, keyManager }); - -if (score.efficiency > 100) { - // Inefficient movement -} - -// Final score available only after game-won -if (gameStatus === 'game-won') { - console.log(`Final Score: ${score.finalScore}`); -} -``` - ---- - -### useHero() - -Player position and movement logic. - -**Purpose**: Manages hero position, handles cell interactions (key pickup, exit), animates movement. - -**Parameters**: -```typescript -useHero( - board: BoardManager, - gameStatusManager: GameStatusManager -): UseHeroType -``` - -**Return Type**: `UseHeroType` - -**Features**: -- Tracks player position (row, col) -- Validates moves through maze navigation -- Handles key pickup event -- Handles exit reached event -- Animates multi-step movements -- Prevents movement without key to exit - -**Cell Interactions**: -- **Key**: Transitions to "has-key" state, removes key from maze -- **Exit**: - - Without key: Invalid move - - With key: Transitions to "game-won" -- **Wall**: Blocked by path validation - -**Example**: -```tsx -const hero = useHero(board, gameStatusManager); - -// Query position -const pos = hero.heroPos; // { row, col } | null - -// Direct movement -hero.moveTo({ row: 10, col: 15 }); - -// Check validity -if (hero.canMoveTo(coord)) { - hero.moveTo(coord); -} - -// Reset on game end -hero.reset(); -``` - ---- - -### useVimMotions() - -Vim keybinding and motion system. - -**Purpose**: Processes vim keys, manages motion count prefix, tracks last motion for repeat. - -**Parameters**: None (hook state managed internally) - -**Return Type**: `VimMotionSystem` - -**API**: -```typescript -{ - processKey: (key: string) => { type: 'move'|'anchor'|'repeat'|'count', data: any } | null; - resetCount: () => void; - getCount: () => number; // Returns 1 if no count - hasCount: () => boolean; // True if user entered count - setLastMotion: (motion: Motion) => void; - repeatLastMotion: () => Motion | null; -} -``` - -**Supported Keys**: -``` -Move: h (left), j (down), k (up), l (right) -Anchor: ^ (start), $ (end), g (top), G (bottom) -Repeat: . (repeat last motion) -Count: 0-9 (prefix for count) -``` - -**Count Behavior**: -- `5j` = move down 5 times -- `0` alone = anchor to start (special case) -- `50j` = move down 50 times -- Motion repeats use stored count - -**Example**: -```tsx -const motions = useVimMotions(); - -const result = motions.processKey('5'); // { type: 'count', data: '5' } -const result = motions.processKey('j'); // { type: 'move', data: { dr: 1, ... } } - -motions.setLastMotion({ dr: 0, dc: 1, steps: 5 }); -const last = motions.repeatLastMotion(); // Re-executes the motion -``` - ---- - -### useGameKeys() - -Game-specific key input handling. - -**Purpose**: Bridges vim motion system with game cursor movement. - -**Parameters**: -```typescript -useGameKeys({ - cursor: CursorManager; - gameStatusManager: GameStatusManager; -}): GameKeyManager -``` - -**Return Type**: `GameKeyManager` - -**Responsibilities**: -- Listens for keyboard input -- Routes keys through vim motion processor -- Executes corresponding cursor movements -- Tracks keystroke history -- Handles game-specific keys (space, q, p) - -**Example**: -```tsx -const keyManager = useGameKeys({ cursor, gameStatusManager }); - -// Keystroke history available -keyManager.keyLog; // { key: string, timestamp: number }[] -keyManager.getKeyLog(); -``` - ---- - -### useAnimation() - -Multi-step movement animation system. - -**Purpose**: Animates hero movement over multiple cells smoothly. - -**Parameters**: None - -**Return Type**: -```typescript -{ - isAnimating: () => boolean; - animateMovement: ( - from: Coord, - to: Coord, - steps: number, - onStep: (coord: Coord) => void, - onComplete: () => void - ) => void; - cancelAnimation: () => void; -} -``` - -**Behavior**: -- Animates from `from` to `to` coordinate over `steps` cells -- Calls `onStep` for each intermediate position (enables smooth animation) -- Calls `onComplete` when finished -- Prevents overlapping animations - -**Example**: -```tsx -const animation = useAnimation(); - -animation.animateMovement( - { row: 5, col: 5 }, - { row: 5, col: 10 }, - 5, - (coord) => { - // Called 5 times for visualization - updateHeroDisplay(coord); - }, - () => { - // Called when complete - handleCellInteraction(); - } -); -``` - ---- - -### useHeroRender() - -DOM rendering for hero character. - -**Purpose**: Renders hero position on the game board DOM. - -**Parameters**: -```typescript -useHeroRender({ - gameStatus: GameStatus; - board: BoardManager; - hero: UseHeroType; -}): HeroRenderer -``` - -**Return Type**: `HeroRenderer` - -**Features**: -- Renders hero at current position -- Updates hero position on DOM -- Shows/hides coordinate display -- Syncs with board rendering - ---- - -### useTimer() - -Timer state management. - -**Purpose**: Tracks elapsed game time. - -**Return Type**: -```typescript -{ - timeValue: number; // Milliseconds elapsed - startTimer: () => void; - stopTimer: () => void; - resetTimer: () => void; -} -``` - ---- - -### useScorePaths() - -Pathfinding for distance calculations. - -**Purpose**: Calculates distances for scoring. - -**Parameters**: -```typescript -useScorePaths({ - board: BoardManager; - heroPos: Coord | null; -}): { entranceToKey, entranceToExit, heroToKey, heroToExit } -``` - -**Returns**: Path distances or 0 if not calculable - ---- - -### useMazeNavigation() - -Navigation and validation utilities. - -**Purpose**: Validates moves, finds paths, checks cell accessibility. - -**Methods**: -```typescript -{ - validatePath: (from: Coord, direction: { row, col }, steps: number) => boolean; - isValidMove: (from: Coord, to: Coord) => boolean; - findAnchorTarget: (from: Coord, direction: 'top'|'bottom'|'left'|'right') => Coord | null; -} -``` - ---- - -## Implementation Examples - -### Minimal Game Setup - -```tsx -import { useGame } from '@vim-maze/src'; - -function MyGame() { - const gameManager = useGame({ cols: 32, rows: 24 }); - const { containerRef, gameStatus, startGame } = gameManager; - - return ( -
-
- -

Status: {gameStatus}

-
- ); -} -``` - -### Complete Feature Example with Scoring - -```tsx -import { useGame } from '@vim-maze/src'; - -function GameWithScoring() { - const gameManager = useGame( - { cols: 32, rows: 24 }, - platformHook // Optional - ); - - const { - containerRef, - gameStatus, - startGame, - cursor, - hero, - scoreManager, - keyLog, - } = gameManager; - - const { - timeValue, - efficiency, - optimalSteps, - keystrokes, - finalScore, - } = scoreManager; - - return ( -
-
- - {gameStatus === 'waiting' && ( - - )} - - {(gameStatus === 'started' || gameStatus === 'has-key') && ( -
-

Time: {formatTime(timeValue)}

-

Efficiency: {efficiency}% {efficiency > 150 && '(Game Over!)'}

-

Keystrokes: {keystrokes} / {optimalSteps}

-
- )} - - {gameStatus === 'game-won' && ( -
-

You Won!

-

Final Score: {finalScore}

-
- )} -
- ); -} -``` - -### Custom Platform Hook Integration - -```tsx -function customPlatformHook(gameManager: GameManager) { - // Called after game initialization - - // Track analytics - console.log('Game initialized:', { - cols: gameManager.cursor.position().col, - rows: gameManager.cursor.position().row, - }); - - // Add custom key handlers - window.addEventListener('keydown', (e) => { - if (e.key === 'p') { - gameManager.togglePause(); - } - }); - - // Monitor game events - const interval = setInterval(() => { - if (gameManager.gameStatus === 'game-won') { - console.log('Victory:', gameManager.scoreManager.finalScore); - clearInterval(interval); - } - }, 100); -} - -const gameManager = useGame({ cols: 32, rows: 24 }, customPlatformHook); -``` - ---- - -## Integration Points - -### For New Games - -1. **Initialize**: Call `useGame(options, platformHook?)` -2. **Render Container**: Attach `gameManager.containerRef` to DOM -3. **Handle Status**: React to `gameManager.gameStatus` changes -4. **Access Managers**: - - Movement: `gameManager.cursor` - - Scoring: `gameManager.scoreManager` - - Input: `gameManager.keyLog` - - Hero: `gameManager.hero` - -### For Platform Integration - -Implement a platform hook function that receives the complete `GameManager`: - -```typescript -type PlatformHook = (gameManager: GameManager) => void; -``` - -The hook can: -- Add custom key bindings -- Track analytics -- Synchronize with platform APIs -- Monitor game state -- Control game lifecycle - -### Customization Points - -The current implementation supports: - -1. **Board Size**: Adjust `cols` and `rows` in `GameOptions` -2. **Key Bindings**: Via `useGameKeys` in custom implementations -3. **Rendering**: Override via `useHeroRender` for custom visuals -4. **Scoring**: Modify `useScore` for different scoring formulas -5. **Maze Generation**: Extend `MazeGenerator` for different algorithms - -### Data Flow - -``` -Game Initialization - ↓ -useGame() composes hooks - ├→ useBoard() generates maze - ├→ useCursor() initializes input - ├→ useGameStatus() initializes state - └→ useScore() initializes scoring - -User Input - ↓ -useGameKeys() processes keystroke - ↓ -useVimMotions() interprets vim command - ↓ -useCursor() executes movement - ↓ -useHero() validates and animates - ↓ -useScore() updates metrics - ↓ -Rendering updates via refs -``` - ---- - -## Type Safety Patterns - -### Hook Return Types - -All hook return types are explicitly defined and organized by module: - -```typescript -// Import from main types.ts (re-exports all modules) -import type { GameManager, CursorManager, ScoreManager } from './types'; -import type { TimerManager, HeroRenderManager, AnimationSystem } from './types'; -import type { BoardManager, Coord } from './types'; -import type { GameStatus, GameStatusManager } from './types'; - -// Or import directly from module types for clarity -import type { CursorManager } from './useCursor/types'; -import type { BoardManager } from './useBoard/types'; -import type { ScoreManager } from './useScore/types'; -``` - -### Type Organization - -- **Module-local types**: Defined in each hook's `types.ts` file -- **Cross-module types**: Imported as needed within modules -- **Public API types**: Re-exported through `src/types.ts` -- **useGame-specific types**: Defined in main `src/types.ts` (GameOptions, GameKeyManager, GameManager) - -### Type Imports - -Always import types with `import type`: - -```typescript -import type { GameManager, CursorManager, Coord } from './types'; -import { useGame } from './useGame'; -``` - -### Coordinate Operations - -Common patterns for working with coordinates: - -```typescript -// Validate before moving -if (hero.canMoveTo(targetCoord)) { - hero.moveTo(targetCoord); -} - -// Calculate distance -const distance = Math.abs(pos1.row - pos2.row) + - Math.abs(pos1.col - pos2.col); - -// Check if equal -const same = pos1.row === pos2.row && pos1.col === pos2.col; -``` - ---- - -## Summary - -The vim-maze unified API provides a composable, type-safe game engine: - -- **Single Entry Point**: `useGame()` for all initialization -- **Modular Design**: Independent, reusable hooks with colocated types -- **Type Safe**: Comprehensive TypeScript support with explicit type definitions -- **Organized Types**: Types organized by module with main `types.ts` re-exporting all -- **Extensible**: Platform hook system for customization -- **Well Structured**: Clear separation of concerns with intuitive file structure - -For detailed implementation, refer to source files in `src/`. Each module's `types.ts` provides the interface definitions for that domain. diff --git a/UNIFIED_API_OLD.md b/UNIFIED_API_OLD.md deleted file mode 100644 index e25b13e..0000000 --- a/UNIFIED_API_OLD.md +++ /dev/null @@ -1,519 +0,0 @@ -# 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()` \ No newline at end of file diff --git a/src/useGame.ts b/src/useGame.ts index 7fbdf16..9f3f547 100644 --- a/src/useGame.ts +++ b/src/useGame.ts @@ -14,7 +14,7 @@ export function useGame(options: GameOptions, platformHook?: unknown): GameManag const hero = cursor.hero; const renderer = useHeroRender({ gameStatus: gameStatusManager.gameStatus, board, hero }); - const scoreManager = useScore({ board, hero, gameStatusManager, keyManager }); + const scoreManager = useScore({ board, hero, gameStatusManager, keyManager, rows, cols }); const gameManager: GameManager = { containerRef, diff --git a/src/useScore/types.ts b/src/useScore/types.ts index b02a135..91c46e8 100644 --- a/src/useScore/types.ts +++ b/src/useScore/types.ts @@ -20,7 +20,5 @@ export type ScoreManager = { distToKey: number; distToExit: number; keystrokes: number; - optimalSteps: number; - efficiency: number; finalScore: number | null; }; diff --git a/src/useScore/useScore.ts b/src/useScore/useScore.ts index 7c1c054..ded67f1 100644 --- a/src/useScore/useScore.ts +++ b/src/useScore/useScore.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState } from "react"; +import { useEffect, useState } from "react"; import { useTimer } from "./useTimer"; import { useScoreTime } from "./useScoreTime"; import { useScorePaths } from "./useScorePaths"; @@ -11,11 +11,13 @@ type UseScoreParams = { hero: UseHeroType; gameStatusManager: GameStatusManager; keyManager?: { keyLog: any[] }; + rows: number; + cols: number; }; -export function useScore({ board, hero, gameStatusManager, keyManager }: UseScoreParams) { +export function useScore({ board, hero, gameStatusManager, keyManager, rows, cols }: UseScoreParams) { const timer = useTimer(); - const { gameStatus, setGameStatus } = gameStatusManager; + const { gameStatus } = gameStatusManager; const { timeValue, startTimer, stopTimer, resetTimer } = timer; const { @@ -29,96 +31,25 @@ export function useScore({ board, hero, gameStatusManager, keyManager }: UseScor const distToExit = entranceToExit || heroToExit; const [finalScore, setFinalScore] = useState(null); - const optimalRef = useRef(0); const keystrokes = keyManager ? keyManager.keyLog.length : 0; useScoreTime({ gameStatus, timer }); - useEffect(() => { - const maze = board.mazeInstanceRef.current; - if (!maze) return; - - const k = maze.getDistance("entrance", "key"); - const e = maze.getDistance("key", "exit"); - const total = k + e; - - if (Number.isFinite(total) && total > 0) { - optimalRef.current = total; - } - }, [gameStatus, board.mazeInstanceRef]); - - const optimalSteps = optimalRef.current; - - const efficiency = - optimalSteps > 0 ? Math.round((keystrokes / optimalSteps) * 100) : 0; - - const [logicFirst, setLogicFirst] = useState(null); - const [logicLast, setLogicLast] = useState(null); - - useEffect(() => { - if (gameStatus === "started") { - setLogicFirst(null); - setLogicLast(null); - setPlayerSteps(0); - prevPosRef.current = null; - } - }, [gameStatus]); - - useEffect(() => { - const onTick = (e: Event) => { - const t = - (e as CustomEvent<{ t?: number }>).detail?.t ?? performance.now(); - setLogicFirst((prev) => prev ?? t); - setLogicLast(t); - }; - window.addEventListener("maze-logic-tick", onTick as EventListener); - return () => - window.removeEventListener("maze-logic-tick", onTick as EventListener); - }, []); - - const [, setPlayerSteps] = useState(0); - const prevPosRef = useRef<{ row: number; col: number } | null>(null); - - useEffect(() => { - const pos = hero.heroPos; - if (!pos) { - prevPosRef.current = null; - return; - } - - if (!["started", "has-key"].includes(gameStatus)) { - prevPosRef.current = pos; - return; - } - - const prev = prevPosRef.current; - prevPosRef.current = pos; - if (!prev) return; - - const delta = Math.abs(pos.row - prev.row) + Math.abs(pos.col - prev.col); - if (delta > 0) setPlayerSteps((s) => s + delta); - }, [gameStatus, hero.heroPos]); - - useEffect(() => { - if (gameStatus === "started" && efficiency > 150) { - setGameStatus("game-over"); - } - }, [efficiency, gameStatus, setGameStatus]); - useEffect(() => { if (gameStatus !== "game-won") return; - const logicMs = - logicFirst != null && logicLast != null - ? Math.max(1, logicLast - logicFirst) - : timeValue; - - const optimalMs = optimalSteps * 200; - const ratio = Math.max(0.1, optimalMs / logicMs); - const score = Math.min(100000, Math.round(ratio * 100000)); + const seconds = timeValue / 1000; + const mazeSize = rows * cols; + + const timePenalty = seconds / 10; + const keystrokePenalty = keystrokes / 2; + const sizeMultiplier = Math.max(1.0, mazeSize / 500); + + const baseScore = 1000 - timePenalty - keystrokePenalty; + const score = Math.min(1000, Math.max(0, Math.round(baseScore * sizeMultiplier))); setFinalScore(score); - }, [gameStatus, logicFirst, logicLast, timeValue, optimalSteps]); + }, [gameStatus, timeValue, keystrokes, rows, cols]); return { timeValue, @@ -128,8 +59,6 @@ export function useScore({ board, hero, gameStatusManager, keyManager }: UseScor distToKey, distToExit, keystrokes, - optimalSteps, - efficiency, finalScore, }; }