From 7c36f9dc144f75d6d1a63fc4d0c6fa6f9f134495 Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Sun, 19 Oct 2025 12:56:35 +0100 Subject: [PATCH] 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 --- UNIFIED_API.md | 58 +++++++++---------- example/src/useKeyBindings.ts | 5 +- src/index.ts | 1 - src/types.ts | 12 +--- src/useCursor/useCursor.ts | 18 +++--- .../useGameKeys.ts} | 18 +++--- src/useCursor/useHero.ts | 6 +- src/useGame.ts | 15 +---- src/useKeyBindings/index.ts | 1 - 9 files changed, 55 insertions(+), 79 deletions(-) rename src/{useKeyBindings/useKeyBindings.ts => useCursor/useGameKeys.ts} (88%) delete mode 100644 src/useKeyBindings/index.ts diff --git a/UNIFIED_API.md b/UNIFIED_API.md index 5264c56..5e855f0 100644 --- a/UNIFIED_API.md +++ b/UNIFIED_API.md @@ -43,14 +43,9 @@ type GameOptions = { ```tsx type GameManager = { containerRef: RefObject; - gameStatus: GameStatus; renderBoard: () => void; - startGame: () => void; - togglePause: (pause?: boolean) => void; - quitGame: () => void; cursor: Cursor; - keyManager: GameKeyManager; -}; +} & GameStatusManager & GameKeyManager; ``` ### `GameStatus` @@ -96,8 +91,8 @@ type GameStatusManager = { ```tsx type GameKeyManager = { keyLog: KeyLogEntry[]; - clearLog: () => void; - getLog: () => KeyLogEntry[]; + clearKeyLog: () => void; + getKeyLog: () => KeyLogEntry[]; }; ``` @@ -141,26 +136,29 @@ Manages the game board/level structure. - `rendererRef` - Renderer instance - `renderBoard()` - Initialize/render the board -### `useCursor(board: BoardManager) -> Cursor` -Manages player/actor movement and state. +### `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 -### `useKeyBindings({ cursor, gameStatus }) -> GameKeyManager` -Handles keyboard input and maps keys to cursor actions. +### `useGameKeys({ cursor, gameStatusManager }) -> GameKeyManager` +Internal hook that handles keyboard input and maps keys to cursor actions. **Parameters:** - `cursor` - Cursor binding context with movement methods -- `gameStatus` - Current game status for state checking +- `gameStatusManager` - Game status manager for state checking **Returns:** - `keyLog` - Array of key press entries with timestamps -- `clearLog()` - Clear the key log -- `getLog()` - Get current key log +- `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. @@ -260,11 +258,12 @@ The unified API is designed to accommodate: - ✅ **Type definitions** - Complete - ✅ **Core architecture** - Defined - ✅ **useGameStatus implementation** - Complete -- ✅ **useKeyBindings implementation** - Complete -- ✅ **useCursor implementation** - Complete +- ✅ **useGameKeys implementation** - Complete (integrated into useCursor) +- ✅ **useCursor implementation** - Complete (with integrated key management) - ✅ **useBoard implementation** - Complete -- ✅ **useGame 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) - ⏳ **Platform integration** - Pending - ⏳ **Advanced features** - Pending (scoring, animations, etc.) @@ -306,13 +305,14 @@ function App() { ### Platform Hook Implementation ```tsx export const useKeyBindings = (gameManager: GameManager) => { - const { gameStatus, startGame, quitGame, cursor } = 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; } @@ -336,24 +336,21 @@ export const useKeyBindings = (gameManager: GameManager) => { window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); - }, [gameStatus, startGame, quitGame, cursor]); + }, [gameStatus, startGame, quitGame, cursor, clearKeyLog]); }; ``` ### Internal Key Bindings Integration ```tsx // Inside useGame - simplified integration -const keyManager = useKeyBindings({ cursor, gameStatus }); +const { keyManager, ...cursor } = useCursor(board, gameStatusManager); const gameManager = { containerRef, - gameStatus, renderBoard, - startGame, - togglePause, - quitGame, cursor, - keyManager, + ...keyManager, + ...gameStatusManager, }; ``` @@ -428,13 +425,9 @@ Platform hooks receive the complete `GameManager` interface: ```tsx type GameManager = { containerRef: RefObject; - gameStatus: GameStatus; renderBoard: () => void; - startGame: () => void; - togglePause: (pause?: boolean) => void; - quitGame: () => void; cursor: Cursor; -}; +} & GameStatusManager & GameKeyManager; ``` This allows platforms to: @@ -442,4 +435,5 @@ This allows platforms to: - Implement analytics tracking - Add save/load functionality - Integrate with platform-specific UI -- Extend game behavior without modifying core logic \ No newline at end of file +- Extend game behavior without modifying core logic +- Access key logging functionality via `clearKeyLog()` and `getKeyLog()` \ No newline at end of file diff --git a/example/src/useKeyBindings.ts b/example/src/useKeyBindings.ts index 89e9953..63f5690 100644 --- a/example/src/useKeyBindings.ts +++ b/example/src/useKeyBindings.ts @@ -2,15 +2,14 @@ import { useEffect } from "react"; import type { GameManager } from "../../src"; export const useKeyBindings = (gameManager: GameManager) => { - const { gameStatus, renderBoard, startGame, quitGame, keyManager } = gameManager; - const { clearLog } = keyManager; + const { gameStatus, renderBoard, startGame, quitGame, clearKeyLog } = gameManager; useEffect(() => { const handler = (ev: KeyboardEvent) => { // start game if (ev.code === "Space") { if (["waiting", "game-over", "game-won"].includes(gameStatus)) { - clearLog(); + clearKeyLog(); renderBoard(); if (gameStatus === "game-over" || gameStatus === "game-won") { quitGame(); diff --git a/src/index.ts b/src/index.ts index 7a70b36..189d9bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ export * from './types'; export * from './useGame'; export * from './useGameStatus'; -export * from './useKeyBindings'; diff --git a/src/types.ts b/src/types.ts index 92f38b2..0ef2d55 100644 --- a/src/types.ts +++ b/src/types.ts @@ -8,15 +8,9 @@ export type GameStatus = 'waiting' | 'started' | 'hasKey' | 'paused' | 'game-ove export type GameManager = { containerRef: RefObject; - gameStatus: GameStatus; renderBoard: () => void; - startGame: () => void; - togglePause: (pause?: boolean) => void; - quitGame: () => void; - cursor: Cursor; - keyManager: GameKeyManager; -}; +} & GameStatusManager & GameKeyManager; export type BoardManager = { @@ -55,8 +49,8 @@ export type KeyLogEntry = { key: string; timestamp: number }; export type GameKeyManager = { keyLog: KeyLogEntry[]; - clearLog: () => void; - getLog: () => KeyLogEntry[]; + clearKeyLog: () => void; + getKeyLog: () => KeyLogEntry[]; }; export type GameStatusManager = { diff --git a/src/useCursor/useCursor.ts b/src/useCursor/useCursor.ts index 4e576fb..472bd05 100644 --- a/src/useCursor/useCursor.ts +++ b/src/useCursor/useCursor.ts @@ -1,17 +1,18 @@ import { useRef, useEffect } from 'react' -import type { CursorMode, Coord, BoardManager, GameStatus } from '../types' -import { useHero } from './useHero' +import type { CursorMode, Coord, BoardManager, GameStatusManager } from '../types' +import { useHero } from './useHero'; +import { useGameKeys } from './useGameKeys'; 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({ row: 0, col: 0 }) const mode = useRef('normal') const countRef = useRef(""); const lastMotionRef = useRef(null); const lastKeyRef = useRef(""); - const hero = useHero(board, gameStatus); + const hero = useHero(board, gameStatusManager); // Sync cursor position with hero position useEffect(() => { @@ -219,7 +220,7 @@ export function useCursor(board: BoardManager, gameStatus: GameStatus) { return lastKeyRef.current; } - const cursorAPI = { + const cursor = { position: () => position.current, mode: () => mode.current, move, @@ -237,12 +238,11 @@ export function useCursor(board: BoardManager, gameStatus: GameStatus) { setCount, setLastKey, getLastKey, + hero, } + const keyManager = useGameKeys({ cursor, gameStatusManager }); - return { - ...cursorAPI, - hero, - }; + return { ...cursor, keyManager }; } diff --git a/src/useKeyBindings/useKeyBindings.ts b/src/useCursor/useGameKeys.ts similarity index 88% rename from src/useKeyBindings/useKeyBindings.ts rename to src/useCursor/useGameKeys.ts index dbea190..4263cb9 100644 --- a/src/useKeyBindings/useKeyBindings.ts +++ b/src/useCursor/useGameKeys.ts @@ -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"; -type UseKeyBindingsParams = { +type UseGameKeysParams = { cursor: Cursor; - hero: UseHeroType; - gameStatus: GameStatus; - setGameStatus: (status: GameStatus) => void; + gameStatusManager: GameStatusManager; }; const RELEVANT_KEYS = new Set([ @@ -14,12 +12,14 @@ const RELEVANT_KEYS = new Set([ "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([]); const logRef = useRef([]); const { hero } = cursor; - const clearLog = () => { + const clearKeyLog = () => { logRef.current = []; setKeyLog([]); }; @@ -116,8 +116,8 @@ export const useKeyBindings = ({ cursor, gameStatus, setGameStatus }: UseKeyBind return { keyLog, - clearLog, - getLog: () => logRef.current, + clearKeyLog, + getKeyLog: () => logRef.current, }; }; diff --git a/src/useCursor/useHero.ts b/src/useCursor/useHero.ts index 79f1ab3..0c718ae 100644 --- a/src/useCursor/useHero.ts +++ b/src/useCursor/useHero.ts @@ -1,12 +1,12 @@ 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"; -export function useHero(board: BoardManager, gameStatus: GameStatus) { +export function useHero(board: BoardManager, gameStatusManager: GameStatusManager) { const { mazeRef, containerRef } = board; const [heroPos, setHeroPos] = useState(null); - const animatingRef = useRef(false); + const { gameStatus } = gameStatusManager; function getCellEl(r: number, c: number): HTMLElement | null { const container = containerRef.current; diff --git a/src/useGame.ts b/src/useGame.ts index 8a61951..404e9a5 100644 --- a/src/useGame.ts +++ b/src/useGame.ts @@ -1,7 +1,6 @@ import type { GameManager, GameOptions } from './types'; import { useBoard } from './useBoard'; import { useCursor } from './useCursor'; -import { useKeyBindings } from './useKeyBindings'; import { useGameStatus } from './useGameStatus'; 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 { containerRef, renderBoard } = board; const gameStatusManager = useGameStatus(); - - const { gameStatus, startGame, togglePause, quitGame, setGameStatus } = gameStatusManager; - - const cursor = useCursor(board, gameStatus); - - const keyManager = useKeyBindings({ cursor, gameStatus, setGameStatus }); + const { keyManager, ...cursor } = useCursor(board, gameStatusManager); const gameManager = { containerRef, - gameStatus, + ...keyManager, + ...gameStatusManager, renderBoard, - startGame, - togglePause, - quitGame, cursor, - keyManager, } if (typeof platformHook === 'function') { diff --git a/src/useKeyBindings/index.ts b/src/useKeyBindings/index.ts deleted file mode 100644 index e5c8fc3..0000000 --- a/src/useKeyBindings/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './useKeyBindings';