- Add useMazeNavigation for pure maze geometry and path validation - Add useVimMotions for pure Vim-style input parsing - Add useAnimation for pure movement animation system - Refactor useCursor to use specialized hooks with cleaner interface - Refactor useHero to pure game logic without DOM manipulation - Refactor useHeroRender to pure visual presentation layer - Fix dual position system - hero position now authoritative - Replace global events with callback props and return values - Implement state-driven rendering instead of direct DOM queries - Update GameManager interface with new HeroManager and HeroRenderer - Maintain backward compatibility with legacy Cursor type - Update UNIFIED_API.md with new architecture and interfaces
292 lines
7.9 KiB
Markdown
292 lines
7.9 KiB
Markdown
# 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
|
|
<div className={`cell ${heroPosition === coord ? 'hero' : ''}`}>
|
|
```
|
|
|
|
#### 3.3 Replace Direct Queries
|
|
**Current:** `container.querySelector("#maze")`
|
|
**New:** Ref-based access
|
|
|
|
```tsx
|
|
const mazeRef = useRef<HTMLElement>(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<HTMLDivElement | null>;
|
|
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. |