no more ReturnType<typeof>
This commit is contained in:
@@ -0,0 +1,134 @@
|
|||||||
|
# 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)
|
||||||
+987
@@ -0,0 +1,987 @@
|
|||||||
|
# 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 # All shared TypeScript types
|
||||||
|
├── useGame.ts # Main orchestrator hook
|
||||||
|
├── useGameStatus/
|
||||||
|
│ └── useGameStatus.ts # Game state management
|
||||||
|
├── useBoard/
|
||||||
|
│ ├── index.ts # Public exports
|
||||||
|
│ ├── 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
|
||||||
|
│ ├── 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
|
||||||
|
├── useScore.ts # Main scoring hook
|
||||||
|
├── useScorePaths.ts # Distance calculations
|
||||||
|
├── useScoreTime.ts # Timer integration
|
||||||
|
└── useTimer.ts # Timer state management
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Type System
|
||||||
|
|
||||||
|
All types are centralized in `src/types.ts` for consistency and maintainability.
|
||||||
|
|
||||||
|
### Main Types
|
||||||
|
|
||||||
|
#### GameManager
|
||||||
|
The primary interface returned by `useGame()`.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
type GameManager = {
|
||||||
|
// DOM reference for rendering
|
||||||
|
containerRef: RefObject<HTMLDivElement | null>;
|
||||||
|
|
||||||
|
// 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<HTMLDivElement | null>;
|
||||||
|
mazeRef: RefObject<MazeCell[][]>;
|
||||||
|
mazeInstanceRef: RefObject<MazeGenerator | null>;
|
||||||
|
rendererRef: RefObject<MazeRenderer | null>;
|
||||||
|
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<HTMLDivElement | null>;
|
||||||
|
mazeRef: RefObject<MazeCell[][]>;
|
||||||
|
mazeInstanceRef: RefObject<MazeGenerator | null>;
|
||||||
|
rendererRef: RefObject<MazeRenderer | null>;
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<div ref={containerRef} />
|
||||||
|
<button onClick={startGame}>Start</button>
|
||||||
|
<p>Status: {gameStatus}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 (
|
||||||
|
<div>
|
||||||
|
<div ref={containerRef} />
|
||||||
|
|
||||||
|
{gameStatus === 'waiting' && (
|
||||||
|
<button onClick={startGame}>Start Game</button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{(gameStatus === 'started' || gameStatus === 'has-key') && (
|
||||||
|
<div>
|
||||||
|
<p>Time: {formatTime(timeValue)}</p>
|
||||||
|
<p>Efficiency: {efficiency}% {efficiency > 150 && '(Game Over!)'}</p>
|
||||||
|
<p>Keystrokes: {keystrokes} / {optimalSteps}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{gameStatus === 'game-won' && (
|
||||||
|
<div>
|
||||||
|
<h2>You Won!</h2>
|
||||||
|
<p>Final Score: {finalScore}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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 in `src/types.ts`. Import them directly:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import type { GameManager, CursorManager, ScoreManager } from './types';
|
||||||
|
import type { TimerManager, HeroRenderManager, AnimationSystem } from './types';
|
||||||
|
```
|
||||||
|
|
||||||
|
### 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
|
||||||
|
- **Type Safe**: Comprehensive TypeScript support
|
||||||
|
- **Extensible**: Platform hook system for customization
|
||||||
|
- **Well Structured**: Clear separation of concerns
|
||||||
|
|
||||||
|
For detailed implementation, refer to source files in `src/`.
|
||||||
@@ -63,6 +63,7 @@ function App() {
|
|||||||
<div className="text-center text-2xl text-muted-foreground">
|
<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 === "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 change direction • 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 change direction • Press <kbd className="px-2 py-1 bg-muted rounded">q</kbd> to quit</p>}
|
||||||
|
{gameStatus === "has-key" && <p>Use <kbd className="px-2 py-1 bg-muted rounded">hjkl</kbd> to change direction • 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-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>}
|
{gameStatus === "game-won" && <p>You Won! Press <kbd className="px-2 py-1 bg-muted rounded">space</kbd> to play again</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+61
-5
@@ -1,6 +1,5 @@
|
|||||||
import type { RefObject } from 'react';
|
import type { RefObject } from 'react';
|
||||||
import { MazeRenderer, MazeGenerator } from "./useBoard";
|
import { MazeRenderer, MazeGenerator } from "./useBoard";
|
||||||
import type { ScoreManager } from './useScore';
|
|
||||||
export * from './useGame';
|
export * from './useGame';
|
||||||
export * from './useBoard';
|
export * from './useBoard';
|
||||||
export * from './useCursor';
|
export * from './useCursor';
|
||||||
@@ -35,7 +34,7 @@ export type CellTag = "wall" | "door" | PositionTag;
|
|||||||
|
|
||||||
export type MazeCell = CellTag[];
|
export type MazeCell = CellTag[];
|
||||||
|
|
||||||
export interface MazeData {
|
export type MazeData = {
|
||||||
maze: MazeCell[][];
|
maze: MazeCell[][];
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@@ -70,14 +69,15 @@ export type GameStatusManager = {
|
|||||||
export type CursorMode = 'normal' | 'insert' | 'replace' | 'visual' | 'visual-line';
|
export type CursorMode = 'normal' | 'insert' | 'replace' | 'visual' | 'visual-line';
|
||||||
|
|
||||||
export type UseHeroType = {
|
export type UseHeroType = {
|
||||||
heroPos: Coord | null;
|
position: 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;
|
canMoveTo: (coord: Coord) => boolean;
|
||||||
moveTo: (coord: Coord) => void;
|
moveTo: (coord: Coord) => void;
|
||||||
pickupKey: () => void;
|
pickupKey: () => void;
|
||||||
reachExit: () => void;
|
reachExit: () => void;
|
||||||
reset: () => void;
|
reset: () => void;
|
||||||
|
heroPos: Coord | null;
|
||||||
|
setHeroPos: (pos: Coord | null) => void;
|
||||||
|
moveHero: (dr: number, dc: number, steps: number, gameStatus: GameStatus, setGameStatus: (s: GameStatus) => void) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CursorManager = {
|
export type CursorManager = {
|
||||||
@@ -119,3 +119,59 @@ export type HeroRenderer = {
|
|||||||
|
|
||||||
// Legacy type for backward compatibility
|
// Legacy type for backward compatibility
|
||||||
export type Cursor = CursorManager;
|
export type Cursor = CursorManager;
|
||||||
|
|
||||||
|
export type Motion = { dr: number; dc: number; steps: number };
|
||||||
|
|
||||||
|
export type VimMotionSystem = {
|
||||||
|
processKey: (key: string) => { type: 'move'|'anchor'|'repeat'|'count', data: any } | null;
|
||||||
|
resetCount: () => void;
|
||||||
|
getCount: () => number;
|
||||||
|
hasCount: () => boolean;
|
||||||
|
setLastMotion: (motion: Motion) => void;
|
||||||
|
repeatLastMotion: () => Motion | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AnimationSystem = {
|
||||||
|
animateMovement: (from: Coord, to: Coord, steps: number, onStep: (coord: Coord) => void, onComplete: () => void) => void;
|
||||||
|
cancelAnimation: () => void;
|
||||||
|
isAnimating: () => boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type HeroRenderManager = {
|
||||||
|
render: () => void;
|
||||||
|
updatePosition: (coord: Coord) => void;
|
||||||
|
showCoordinates: (show: boolean) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TimerManager = {
|
||||||
|
timeValue: number;
|
||||||
|
startTimer: () => void;
|
||||||
|
stopTimer: () => void;
|
||||||
|
resetTimer: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ScorePathsResult = {
|
||||||
|
entranceToKey: number;
|
||||||
|
entranceToExit: number;
|
||||||
|
heroToKey: number;
|
||||||
|
heroToExit: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ScoreManager = {
|
||||||
|
timeValue: number;
|
||||||
|
startTimer: () => void;
|
||||||
|
stopTimer: () => void;
|
||||||
|
resetTimer: () => void;
|
||||||
|
distToKey: number;
|
||||||
|
distToExit: number;
|
||||||
|
keystrokes: number;
|
||||||
|
optimalSteps: number;
|
||||||
|
efficiency: number;
|
||||||
|
finalScore: number | null;
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import type { Coord, MazeCell } from '../types';
|
import type { Coord, MazeCell, MazeNavigator } from '../types';
|
||||||
|
|
||||||
export type MazeNavigator = {
|
export type { MazeNavigator } from '../types';
|
||||||
isValidMove: (from: Coord, to: Coord) => boolean;
|
|
||||||
findAnchorTarget: (from: Coord, direction: 'left'|'right'|'top'|'bottom') => Coord | null;
|
|
||||||
validatePath: (from: Coord, direction: Coord, steps: number) => boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useMazeNavigation(maze: MazeCell[][]): MazeNavigator {
|
export function useMazeNavigation(maze: MazeCell[][]): MazeNavigator {
|
||||||
function isValidMove(_from: Coord, to: Coord): boolean {
|
function isValidMove(_from: Coord, to: Coord): boolean {
|
||||||
@@ -87,5 +83,3 @@ export function useMazeNavigation(maze: MazeCell[][]): MazeNavigator {
|
|||||||
validatePath
|
validatePath
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type MazeNavigationManager = ReturnType<typeof useMazeNavigation>;
|
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
import type { Coord } from '../types';
|
import type { Coord, AnimationSystem } from '../types';
|
||||||
|
|
||||||
export type AnimationSystem = {
|
export type { AnimationSystem } from '../types';
|
||||||
animateMovement: (from: Coord, to: Coord, steps: number, onStep: (coord: Coord) => void, onComplete: () => void) => void;
|
|
||||||
cancelAnimation: () => void;
|
|
||||||
isAnimating: () => boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useAnimation(): AnimationSystem {
|
export function useAnimation(): AnimationSystem {
|
||||||
const animatingRef = useRef(false);
|
const animatingRef = useRef(false);
|
||||||
@@ -89,5 +85,3 @@ export function useAnimation(): AnimationSystem {
|
|||||||
isAnimating
|
isAnimating
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnimationManager = ReturnType<typeof useAnimation>;
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import type { BoardManager, GameStatusManager, GameStatus, Coord } from "../types";
|
import type { BoardManager, GameStatusManager, GameStatus, Coord, UseHeroType } from "../types";
|
||||||
import { useHeroRender } from "./useHeroRender";
|
import { useHeroRender } from "./useHeroRender";
|
||||||
import { useMazeNavigation } from "../useBoard";
|
import { useMazeNavigation } from "../useBoard";
|
||||||
import { useAnimation } from "./useAnimation";
|
import { useAnimation } from "./useAnimation";
|
||||||
|
|
||||||
export function useHero(board: BoardManager, gameStatusManager: GameStatusManager) {
|
export function useHero(board: BoardManager, gameStatusManager: GameStatusManager): UseHeroType {
|
||||||
const { mazeRef } = board;
|
const { mazeRef } = board;
|
||||||
const [heroPos, setHeroPos] = useState<Coord | null>(null);
|
const [heroPos, setHeroPos] = useState<Coord | null>(null);
|
||||||
const { gameStatus, setGameStatus } = gameStatusManager;
|
const { gameStatus, setGameStatus } = gameStatusManager;
|
||||||
@@ -187,5 +187,3 @@ export function useHero(board: BoardManager, gameStatusManager: GameStatusManage
|
|||||||
|
|
||||||
return hero;
|
return hero;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UseHeroType = ReturnType<typeof useHero>;
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import type { BoardManager, GameStatus, Coord } from "../types";
|
import type { BoardManager, GameStatus, Coord, UseHeroType, HeroRenderManager } from "../types";
|
||||||
import type { UseHeroType } from "./useHero";
|
|
||||||
|
export type { HeroRenderManager } from "../types";
|
||||||
|
|
||||||
type UseHeroRenderParams = {
|
type UseHeroRenderParams = {
|
||||||
gameStatus: GameStatus;
|
gameStatus: GameStatus;
|
||||||
@@ -9,7 +10,7 @@ type UseHeroRenderParams = {
|
|||||||
relative?: boolean;
|
relative?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useHeroRender(params: UseHeroRenderParams) {
|
export function useHeroRender(params: UseHeroRenderParams): HeroRenderManager {
|
||||||
const { gameStatus, board, hero, relative = true } = params;
|
const { gameStatus, board, hero, relative = true } = params;
|
||||||
const { containerRef } = board;
|
const { containerRef } = board;
|
||||||
const playerPos = hero.heroPos;
|
const playerPos = hero.heroPos;
|
||||||
@@ -163,5 +164,3 @@ export function useHeroRender(params: UseHeroRenderParams) {
|
|||||||
showCoordinates
|
showCoordinates
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UseHeroRenderType = ReturnType<typeof useHeroRender>;
|
|
||||||
|
|||||||
@@ -1,15 +1,8 @@
|
|||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
import type { VimMotionSystem, Motion } from '../types';
|
||||||
|
|
||||||
export type Motion = { dr: number; dc: number; steps: number };
|
export type { Motion } from '../types';
|
||||||
|
export type { VimMotionSystem } from '../types';
|
||||||
export type VimMotionSystem = {
|
|
||||||
processKey: (key: string) => { type: 'move'|'anchor'|'repeat'|'count', data: any } | null;
|
|
||||||
resetCount: () => void;
|
|
||||||
getCount: () => number;
|
|
||||||
hasCount: () => boolean;
|
|
||||||
setLastMotion: (motion: Motion) => void;
|
|
||||||
repeatLastMotion: () => Motion | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useVimMotions(): VimMotionSystem {
|
export function useVimMotions(): VimMotionSystem {
|
||||||
const countRef = useRef<string>('');
|
const countRef = useRef<string>('');
|
||||||
@@ -112,5 +105,3 @@ export function useVimMotions(): VimMotionSystem {
|
|||||||
repeatLastMotion
|
repeatLastMotion
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VimMotionManager = ReturnType<typeof useVimMotions>;
|
|
||||||
@@ -131,5 +131,3 @@ export function useScore({ board, hero, gameStatusManager, keyManager }: UseScor
|
|||||||
finalScore,
|
finalScore,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ScoreManager = ReturnType<typeof useScore>;
|
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import type { BoardManager } from "../types";
|
import type { BoardManager, Coord, ScorePathsResult } from "../types";
|
||||||
|
|
||||||
|
export type { ScorePathsResult } from "../types";
|
||||||
|
|
||||||
type UseScorePathsParams = {
|
type UseScorePathsParams = {
|
||||||
board: BoardManager;
|
board: BoardManager;
|
||||||
heroPos: { row: number; col: number } | null;
|
heroPos: Coord | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useScorePaths({ board, heroPos }: UseScorePathsParams) {
|
export function useScorePaths({ board, heroPos }: UseScorePathsParams): ScorePathsResult {
|
||||||
const { mazeInstanceRef } = board;
|
const { mazeInstanceRef } = board;
|
||||||
|
|
||||||
const [distances, setDistances] = useState({
|
const [distances, setDistances] = useState({
|
||||||
@@ -40,5 +42,3 @@ export function useScorePaths({ board, heroPos }: UseScorePathsParams) {
|
|||||||
|
|
||||||
return distances;
|
return distances;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UseScorePathsReturn = ReturnType<typeof useScorePaths>;
|
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import type { UseTimerReturn } from "./useTimer";
|
import type { TimerManager, GameStatus } from "../types";
|
||||||
import type { GameStatus } from "../types";
|
|
||||||
|
|
||||||
type UseScoreTimeParams = {
|
type UseScoreTimeParams = {
|
||||||
gameStatus: GameStatus;
|
gameStatus: GameStatus;
|
||||||
timer: UseTimerReturn;
|
timer: TimerManager;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function useScoreTime({ gameStatus, timer }: UseScoreTimeParams) {
|
export function useScoreTime({ gameStatus, timer }: UseScoreTimeParams) {
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
|
import type { TimerManager } from '../types';
|
||||||
|
|
||||||
export function useTimer() {
|
export type { TimerManager } from '../types';
|
||||||
|
|
||||||
|
export function useTimer(): TimerManager {
|
||||||
const [timeValue, setTimeValue] = useState(0);
|
const [timeValue, setTimeValue] = useState(0);
|
||||||
const startTimeRef = useRef<number | null>(null);
|
const startTimeRef = useRef<number | null>(null);
|
||||||
const frameRef = useRef<number | null>(null);
|
const frameRef = useRef<number | null>(null);
|
||||||
@@ -44,5 +47,3 @@ export function useTimer() {
|
|||||||
|
|
||||||
return { timeValue, startTimer, stopTimer, resetTimer };
|
return { timeValue, startTimer, stopTimer, resetTimer };
|
||||||
}
|
}
|
||||||
|
|
||||||
export type UseTimerReturn = ReturnType<typeof useTimer>;
|
|
||||||
|
|||||||
Reference in New Issue
Block a user