refactor: integrate key management into cursor module
- Move useKeyBindings functionality into useCursor as useGameKeys - Flatten GameManager interface using intersection types - Rename clearLog/getLog to clearKeyLog/getKeyLog for clarity - Remove separate useKeyBindings module and exports - Update all documentation to reflect new architecture - Maintain backward compatibility for platform hooks
This commit is contained in:
+26
-32
@@ -43,14 +43,9 @@ type GameOptions = {
|
|||||||
```tsx
|
```tsx
|
||||||
type GameManager = {
|
type GameManager = {
|
||||||
containerRef: RefObject<HTMLDivElement | null>;
|
containerRef: RefObject<HTMLDivElement | null>;
|
||||||
gameStatus: GameStatus;
|
|
||||||
renderBoard: () => void;
|
renderBoard: () => void;
|
||||||
startGame: () => void;
|
|
||||||
togglePause: (pause?: boolean) => void;
|
|
||||||
quitGame: () => void;
|
|
||||||
cursor: Cursor;
|
cursor: Cursor;
|
||||||
keyManager: GameKeyManager;
|
} & GameStatusManager & GameKeyManager;
|
||||||
};
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### `GameStatus`
|
### `GameStatus`
|
||||||
@@ -96,8 +91,8 @@ type GameStatusManager = {
|
|||||||
```tsx
|
```tsx
|
||||||
type GameKeyManager = {
|
type GameKeyManager = {
|
||||||
keyLog: KeyLogEntry[];
|
keyLog: KeyLogEntry[];
|
||||||
clearLog: () => void;
|
clearKeyLog: () => void;
|
||||||
getLog: () => KeyLogEntry[];
|
getKeyLog: () => KeyLogEntry[];
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -141,26 +136,29 @@ Manages the game board/level structure.
|
|||||||
- `rendererRef` - Renderer instance
|
- `rendererRef` - Renderer instance
|
||||||
- `renderBoard()` - Initialize/render the board
|
- `renderBoard()` - Initialize/render the board
|
||||||
|
|
||||||
### `useCursor(board: BoardManager) -> Cursor`
|
### `useCursor(board: BoardManager, gameStatusManager: GameStatusManager) -> Cursor & { keyManager: GameKeyManager }`
|
||||||
Manages player/actor movement and state.
|
Manages player/actor movement and state, now includes integrated key management.
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
- Position tracking
|
- Position tracking
|
||||||
- Mode management
|
- Mode management
|
||||||
- Movement methods (with count support)
|
- Movement methods (with count support)
|
||||||
- Directional movement helpers
|
- Directional movement helpers
|
||||||
|
- Integrated `keyManager` for key logging and input handling
|
||||||
|
|
||||||
### `useKeyBindings({ cursor, gameStatus }) -> GameKeyManager`
|
### `useGameKeys({ cursor, gameStatusManager }) -> GameKeyManager`
|
||||||
Handles keyboard input and maps keys to cursor actions.
|
Internal hook that handles keyboard input and maps keys to cursor actions.
|
||||||
|
|
||||||
**Parameters:**
|
**Parameters:**
|
||||||
- `cursor` - Cursor binding context with movement methods
|
- `cursor` - Cursor binding context with movement methods
|
||||||
- `gameStatus` - Current game status for state checking
|
- `gameStatusManager` - Game status manager for state checking
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
- `keyLog` - Array of key press entries with timestamps
|
- `keyLog` - Array of key press entries with timestamps
|
||||||
- `clearLog()` - Clear the key log
|
- `clearKeyLog()` - Clear the key log
|
||||||
- `getLog()` - Get current key log
|
- `getKeyLog()` - Get current key log
|
||||||
|
|
||||||
|
**Note:** This is now integrated within `useCursor` and not exposed as a separate hook.
|
||||||
|
|
||||||
### `useGameStatus() -> GameStatusManager`
|
### `useGameStatus() -> GameStatusManager`
|
||||||
Manages game state and lifecycle.
|
Manages game state and lifecycle.
|
||||||
@@ -260,11 +258,12 @@ The unified API is designed to accommodate:
|
|||||||
- ✅ **Type definitions** - Complete
|
- ✅ **Type definitions** - Complete
|
||||||
- ✅ **Core architecture** - Defined
|
- ✅ **Core architecture** - Defined
|
||||||
- ✅ **useGameStatus implementation** - Complete
|
- ✅ **useGameStatus implementation** - Complete
|
||||||
- ✅ **useKeyBindings implementation** - Complete
|
- ✅ **useGameKeys implementation** - Complete (integrated into useCursor)
|
||||||
- ✅ **useCursor implementation** - Complete
|
- ✅ **useCursor implementation** - Complete (with integrated key management)
|
||||||
- ✅ **useBoard implementation** - Complete
|
- ✅ **useBoard implementation** - Complete
|
||||||
- ✅ **useGame implementation** - Complete
|
- ✅ **useGame implementation** - Complete (with flattened GameManager interface)
|
||||||
- ✅ **React 19 optimization** - No useCallback/useMemo usage
|
- ✅ **React 19 optimization** - No useCallback/useMemo usage
|
||||||
|
- ✅ **API refactoring** - Complete (key management moved into cursor module)
|
||||||
- ⏳ **Platform integration** - Pending
|
- ⏳ **Platform integration** - Pending
|
||||||
- ⏳ **Advanced features** - Pending (scoring, animations, etc.)
|
- ⏳ **Advanced features** - Pending (scoring, animations, etc.)
|
||||||
|
|
||||||
@@ -306,13 +305,14 @@ function App() {
|
|||||||
### Platform Hook Implementation
|
### Platform Hook Implementation
|
||||||
```tsx
|
```tsx
|
||||||
export const useKeyBindings = (gameManager: GameManager) => {
|
export const useKeyBindings = (gameManager: GameManager) => {
|
||||||
const { gameStatus, startGame, quitGame, cursor } = gameManager;
|
const { gameStatus, startGame, quitGame, cursor, clearKeyLog } = gameManager;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (e: KeyboardEvent) => {
|
const handler = (e: KeyboardEvent) => {
|
||||||
// Start game with 'i'
|
// Start game with 'i'
|
||||||
if (e.key === "i" || e.key === "I") {
|
if (e.key === "i" || e.key === "I") {
|
||||||
if (["waiting", "game-over", "game-won"].includes(gameStatus)) {
|
if (["waiting", "game-over", "game-won"].includes(gameStatus)) {
|
||||||
|
clearKeyLog();
|
||||||
startGame();
|
startGame();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -336,24 +336,21 @@ export const useKeyBindings = (gameManager: GameManager) => {
|
|||||||
|
|
||||||
window.addEventListener("keydown", handler);
|
window.addEventListener("keydown", handler);
|
||||||
return () => window.removeEventListener("keydown", handler);
|
return () => window.removeEventListener("keydown", handler);
|
||||||
}, [gameStatus, startGame, quitGame, cursor]);
|
}, [gameStatus, startGame, quitGame, cursor, clearKeyLog]);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
### Internal Key Bindings Integration
|
### Internal Key Bindings Integration
|
||||||
```tsx
|
```tsx
|
||||||
// Inside useGame - simplified integration
|
// Inside useGame - simplified integration
|
||||||
const keyManager = useKeyBindings({ cursor, gameStatus });
|
const { keyManager, ...cursor } = useCursor(board, gameStatusManager);
|
||||||
|
|
||||||
const gameManager = {
|
const gameManager = {
|
||||||
containerRef,
|
containerRef,
|
||||||
gameStatus,
|
|
||||||
renderBoard,
|
renderBoard,
|
||||||
startGame,
|
|
||||||
togglePause,
|
|
||||||
quitGame,
|
|
||||||
cursor,
|
cursor,
|
||||||
keyManager,
|
...keyManager,
|
||||||
|
...gameStatusManager,
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -428,13 +425,9 @@ Platform hooks receive the complete `GameManager` interface:
|
|||||||
```tsx
|
```tsx
|
||||||
type GameManager = {
|
type GameManager = {
|
||||||
containerRef: RefObject<HTMLDivElement | null>;
|
containerRef: RefObject<HTMLDivElement | null>;
|
||||||
gameStatus: GameStatus;
|
|
||||||
renderBoard: () => void;
|
renderBoard: () => void;
|
||||||
startGame: () => void;
|
|
||||||
togglePause: (pause?: boolean) => void;
|
|
||||||
quitGame: () => void;
|
|
||||||
cursor: Cursor;
|
cursor: Cursor;
|
||||||
};
|
} & GameStatusManager & GameKeyManager;
|
||||||
```
|
```
|
||||||
|
|
||||||
This allows platforms to:
|
This allows platforms to:
|
||||||
@@ -442,4 +435,5 @@ This allows platforms to:
|
|||||||
- Implement analytics tracking
|
- Implement analytics tracking
|
||||||
- Add save/load functionality
|
- Add save/load functionality
|
||||||
- Integrate with platform-specific UI
|
- Integrate with platform-specific UI
|
||||||
- Extend game behavior without modifying core logic
|
- Extend game behavior without modifying core logic
|
||||||
|
- Access key logging functionality via `clearKeyLog()` and `getKeyLog()`
|
||||||
@@ -2,15 +2,14 @@ import { useEffect } from "react";
|
|||||||
import type { GameManager } from "../../src";
|
import type { GameManager } from "../../src";
|
||||||
|
|
||||||
export const useKeyBindings = (gameManager: GameManager) => {
|
export const useKeyBindings = (gameManager: GameManager) => {
|
||||||
const { gameStatus, renderBoard, startGame, quitGame, keyManager } = gameManager;
|
const { gameStatus, renderBoard, startGame, quitGame, clearKeyLog } = gameManager;
|
||||||
const { clearLog } = keyManager;
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handler = (ev: KeyboardEvent) => {
|
const handler = (ev: KeyboardEvent) => {
|
||||||
// start game
|
// start game
|
||||||
if (ev.code === "Space") {
|
if (ev.code === "Space") {
|
||||||
if (["waiting", "game-over", "game-won"].includes(gameStatus)) {
|
if (["waiting", "game-over", "game-won"].includes(gameStatus)) {
|
||||||
clearLog();
|
clearKeyLog();
|
||||||
renderBoard();
|
renderBoard();
|
||||||
if (gameStatus === "game-over" || gameStatus === "game-won") {
|
if (gameStatus === "game-over" || gameStatus === "game-won") {
|
||||||
quitGame();
|
quitGame();
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
export * from './types';
|
export * from './types';
|
||||||
export * from './useGame';
|
export * from './useGame';
|
||||||
export * from './useGameStatus';
|
export * from './useGameStatus';
|
||||||
export * from './useKeyBindings';
|
|
||||||
|
|||||||
+3
-9
@@ -8,15 +8,9 @@ export type GameStatus = 'waiting' | 'started' | 'hasKey' | 'paused' | 'game-ove
|
|||||||
|
|
||||||
export type GameManager = {
|
export type GameManager = {
|
||||||
containerRef: RefObject<HTMLDivElement | null>;
|
containerRef: RefObject<HTMLDivElement | null>;
|
||||||
gameStatus: GameStatus;
|
|
||||||
renderBoard: () => void;
|
renderBoard: () => void;
|
||||||
startGame: () => void;
|
|
||||||
togglePause: (pause?: boolean) => void;
|
|
||||||
quitGame: () => void;
|
|
||||||
|
|
||||||
cursor: Cursor;
|
cursor: Cursor;
|
||||||
keyManager: GameKeyManager;
|
} & GameStatusManager & GameKeyManager;
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
export type BoardManager = {
|
export type BoardManager = {
|
||||||
@@ -55,8 +49,8 @@ export type KeyLogEntry = { key: string; timestamp: number };
|
|||||||
|
|
||||||
export type GameKeyManager = {
|
export type GameKeyManager = {
|
||||||
keyLog: KeyLogEntry[];
|
keyLog: KeyLogEntry[];
|
||||||
clearLog: () => void;
|
clearKeyLog: () => void;
|
||||||
getLog: () => KeyLogEntry[];
|
getKeyLog: () => KeyLogEntry[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type GameStatusManager = {
|
export type GameStatusManager = {
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import { useRef, useEffect } from 'react'
|
import { useRef, useEffect } from 'react'
|
||||||
import type { CursorMode, Coord, BoardManager, GameStatus } from '../types'
|
import type { CursorMode, Coord, BoardManager, GameStatusManager } from '../types'
|
||||||
import { useHero } from './useHero'
|
import { useHero } from './useHero';
|
||||||
|
import { useGameKeys } from './useGameKeys';
|
||||||
|
|
||||||
type Motion = { dr: number; dc: number; steps: number };
|
type Motion = { dr: number; dc: number; steps: number };
|
||||||
|
|
||||||
export function useCursor(board: BoardManager, gameStatus: GameStatus) {
|
export function useCursor(board: BoardManager, gameStatusManager: GameStatusManager) {
|
||||||
const position = useRef<Coord>({ row: 0, col: 0 })
|
const position = useRef<Coord>({ row: 0, col: 0 })
|
||||||
const mode = useRef<CursorMode>('normal')
|
const mode = useRef<CursorMode>('normal')
|
||||||
const countRef = useRef<string>("");
|
const countRef = useRef<string>("");
|
||||||
const lastMotionRef = useRef<Motion | null>(null);
|
const lastMotionRef = useRef<Motion | null>(null);
|
||||||
const lastKeyRef = useRef<string>("");
|
const lastKeyRef = useRef<string>("");
|
||||||
|
|
||||||
const hero = useHero(board, gameStatus);
|
const hero = useHero(board, gameStatusManager);
|
||||||
|
|
||||||
// Sync cursor position with hero position
|
// Sync cursor position with hero position
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -219,7 +220,7 @@ export function useCursor(board: BoardManager, gameStatus: GameStatus) {
|
|||||||
return lastKeyRef.current;
|
return lastKeyRef.current;
|
||||||
}
|
}
|
||||||
|
|
||||||
const cursorAPI = {
|
const cursor = {
|
||||||
position: () => position.current,
|
position: () => position.current,
|
||||||
mode: () => mode.current,
|
mode: () => mode.current,
|
||||||
move,
|
move,
|
||||||
@@ -237,12 +238,11 @@ export function useCursor(board: BoardManager, gameStatus: GameStatus) {
|
|||||||
setCount,
|
setCount,
|
||||||
setLastKey,
|
setLastKey,
|
||||||
getLastKey,
|
getLastKey,
|
||||||
|
hero,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const keyManager = useGameKeys({ cursor, gameStatusManager });
|
||||||
|
|
||||||
return {
|
return { ...cursor, keyManager };
|
||||||
...cursorAPI,
|
|
||||||
hero,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
import type { GameStatus, KeyLogEntry, UseHeroType, Cursor } from "../types";
|
import type { GameStatusManager, KeyLogEntry, Cursor } from "../types";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
|
||||||
|
|
||||||
type UseKeyBindingsParams = {
|
type UseGameKeysParams = {
|
||||||
cursor: Cursor;
|
cursor: Cursor;
|
||||||
hero: UseHeroType;
|
gameStatusManager: GameStatusManager;
|
||||||
gameStatus: GameStatus;
|
|
||||||
setGameStatus: (status: GameStatus) => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const RELEVANT_KEYS = new Set([
|
const RELEVANT_KEYS = new Set([
|
||||||
@@ -14,12 +12,14 @@ const RELEVANT_KEYS = new Set([
|
|||||||
"1", "2", "3", "4", "5", "6", "7", "8", "9"
|
"1", "2", "3", "4", "5", "6", "7", "8", "9"
|
||||||
]);
|
]);
|
||||||
|
|
||||||
export const useKeyBindings = ({ cursor, gameStatus, setGameStatus }: UseKeyBindingsParams) => {
|
export const useGameKeys = (props: UseGameKeysParams) => {
|
||||||
|
const { cursor, gameStatusManager } = props;
|
||||||
|
const { gameStatus, setGameStatus } = gameStatusManager;
|
||||||
const [keyLog, setKeyLog] = useState<KeyLogEntry[]>([]);
|
const [keyLog, setKeyLog] = useState<KeyLogEntry[]>([]);
|
||||||
const logRef = useRef<KeyLogEntry[]>([]);
|
const logRef = useRef<KeyLogEntry[]>([]);
|
||||||
const { hero } = cursor;
|
const { hero } = cursor;
|
||||||
|
|
||||||
const clearLog = () => {
|
const clearKeyLog = () => {
|
||||||
logRef.current = [];
|
logRef.current = [];
|
||||||
setKeyLog([]);
|
setKeyLog([]);
|
||||||
};
|
};
|
||||||
@@ -116,8 +116,8 @@ export const useKeyBindings = ({ cursor, gameStatus, setGameStatus }: UseKeyBind
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
keyLog,
|
keyLog,
|
||||||
clearLog,
|
clearKeyLog,
|
||||||
getLog: () => logRef.current,
|
getKeyLog: () => logRef.current,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
import { useRef, useState, useEffect } from "react";
|
import { useRef, useState, useEffect } from "react";
|
||||||
import type { BoardManager, GameStatus, Coord } from "../types";
|
import type { BoardManager, GameStatusManager, GameStatus, Coord } from "../types";
|
||||||
import { useHeroRender } from "./useHeroRender";
|
import { useHeroRender } from "./useHeroRender";
|
||||||
|
|
||||||
export function useHero(board: BoardManager, gameStatus: GameStatus) {
|
export function useHero(board: BoardManager, gameStatusManager: GameStatusManager) {
|
||||||
const { mazeRef, containerRef } = board;
|
const { mazeRef, containerRef } = board;
|
||||||
const [heroPos, setHeroPos] = useState<Coord | null>(null);
|
const [heroPos, setHeroPos] = useState<Coord | null>(null);
|
||||||
|
|
||||||
const animatingRef = useRef(false);
|
const animatingRef = useRef(false);
|
||||||
|
const { gameStatus } = gameStatusManager;
|
||||||
|
|
||||||
function getCellEl(r: number, c: number): HTMLElement | null {
|
function getCellEl(r: number, c: number): HTMLElement | null {
|
||||||
const container = containerRef.current;
|
const container = containerRef.current;
|
||||||
|
|||||||
+3
-12
@@ -1,7 +1,6 @@
|
|||||||
import type { GameManager, GameOptions } from './types';
|
import type { GameManager, GameOptions } from './types';
|
||||||
import { useBoard } from './useBoard';
|
import { useBoard } from './useBoard';
|
||||||
import { useCursor } from './useCursor';
|
import { useCursor } from './useCursor';
|
||||||
import { useKeyBindings } from './useKeyBindings';
|
|
||||||
import { useGameStatus } from './useGameStatus';
|
import { useGameStatus } from './useGameStatus';
|
||||||
|
|
||||||
export function useGame(options: GameOptions, platformHook?: unknown): GameManager {
|
export function useGame(options: GameOptions, platformHook?: unknown): GameManager {
|
||||||
@@ -9,22 +8,14 @@ export function useGame(options: GameOptions, platformHook?: unknown): GameManag
|
|||||||
const board = useBoard(cols, rows);
|
const board = useBoard(cols, rows);
|
||||||
const { containerRef, renderBoard } = board;
|
const { containerRef, renderBoard } = board;
|
||||||
const gameStatusManager = useGameStatus();
|
const gameStatusManager = useGameStatus();
|
||||||
|
const { keyManager, ...cursor } = useCursor(board, gameStatusManager);
|
||||||
const { gameStatus, startGame, togglePause, quitGame, setGameStatus } = gameStatusManager;
|
|
||||||
|
|
||||||
const cursor = useCursor(board, gameStatus);
|
|
||||||
|
|
||||||
const keyManager = useKeyBindings({ cursor, gameStatus, setGameStatus });
|
|
||||||
|
|
||||||
const gameManager = {
|
const gameManager = {
|
||||||
containerRef,
|
containerRef,
|
||||||
gameStatus,
|
...keyManager,
|
||||||
|
...gameStatusManager,
|
||||||
renderBoard,
|
renderBoard,
|
||||||
startGame,
|
|
||||||
togglePause,
|
|
||||||
quitGame,
|
|
||||||
cursor,
|
cursor,
|
||||||
keyManager,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof platformHook === 'function') {
|
if (typeof platformHook === 'function') {
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './useKeyBindings';
|
|
||||||
Reference in New Issue
Block a user