From 5c398b556f8bd24415b21f224cabcb59b458995e Mon Sep 17 00:00:00 2001 From: Andre Padez Date: Thu, 30 Oct 2025 22:22:03 +0000 Subject: [PATCH] feat(phase1.2): fix GameManager interface to match Unified API - Create comprehensive types.ts with all required manager interfaces - Define GameManager with proper Unified API compliance - Implement CursorManager interface in useCursor with VIM motions - Add SnakeCursorManager type with snake-specific extensions - Update useGame to return proper GameManager interface - Support both old (cols, rows) and new (options) signatures - Add quitGame alias for stopGame in useGameStatus - Wrap renderBoard to match Unified API signature (no parameters) - Add placeholder for totalKeystrokes and finalScore in ScoreManager - All TypeScript builds successfully - Example app builds without errors --- src/types.ts | 155 +++++++++++++++++++++++++++-- src/useCursor/useCursor.ts | 68 ++++++++++++- src/useGame/useGame.ts | 71 +++++++++---- src/useGameStatus/useGameStatus.ts | 1 + src/useScore/useScore.ts | 2 + 5 files changed, 271 insertions(+), 26 deletions(-) diff --git a/src/types.ts b/src/types.ts index 1a73470..9ccec73 100755 --- a/src/types.ts +++ b/src/types.ts @@ -1,18 +1,157 @@ -import { useGame } from "./useGame"; +import type { RefObject, MutableRefObject } from 'react'; -export type UseGameType = ReturnType; +// ============================================================================ +// Core Game Types +// ============================================================================ -export type GamePhase = "idle" | "playing"; -export type PlayStatus = "started" | "game-over" | "game-won"; -export type GameStatus = "waiting" | "started" | "game-over" | "game-won"; +export type GameStatus = 'waiting' | 'started' | 'game-won' | 'game-over'; +export type GamePhase = 'idle' | 'playing'; +export type PlayStatus = 'started' | 'game-over' | 'game-won'; export const isPlaying = (status: GameStatus): status is PlayStatus => - status === "started" || status === "game-over" || status === "game-won"; + status === 'started' || status === 'game-over' || status === 'game-won'; export const getGamePhase = (status: GameStatus): GamePhase => - status === "waiting" ? "idle" : "playing"; + status === 'waiting' ? 'idle' : 'playing'; -export type Direction = "up" | "down" | "left" | "right"; +// ============================================================================ +// Snake-Specific Types +// ============================================================================ + +export type Direction = 'up' | 'down' | 'left' | 'right'; export type Position = { r: number; c: number }; export type SnakeBody = Position[]; +// ============================================================================ +// Input & Key Tracking +// ============================================================================ + +export type KeyLogEntry = { key: string; timestamp: number }; + +export type GameOptions = { + cols: number; + rows: number; + timeLimit?: number; +}; + +// ============================================================================ +// Manager Interfaces - Board +// ============================================================================ + +export type BoardManager = { + containerRef: RefObject; + renderBoard: (cols: number, rows: number) => void; +}; + +// ============================================================================ +// Manager Interfaces - Cursor/Snake +// ============================================================================ + +export type CursorMode = 'normal'; + +export type CursorManager = { + position: () => Position; + mode: () => CursorMode; + + // VIM-style motions (required by Unified API) + moveLeft: (count?: number) => void; + moveRight: (count?: number) => void; + moveUp: (count?: number) => void; + moveDown: (count?: number) => void; + moveToStart: () => void; + moveToEnd: () => void; + moveToTop: () => void; + moveToBottom: () => void; + repeatLastMotion: () => void; +}; + +export type SnakeCursorManager = CursorManager & { + snakeBody: SnakeBody; + direction: Direction; + changeDirection: (dir: Direction) => void; + // Snake-specific for internal use + snakeBodyRef: MutableRefObject; + directionRef: MutableRefObject; + initSnake: () => void; + clearSnake: () => void; + moveSnake: (grow?: boolean) => 'continue' | 'wall-collision' | 'self-collision'; + renderSnake: (body: SnakeBody) => void; +}; + +// ============================================================================ +// Manager Interfaces - Score +// ============================================================================ + +export type ScoreManager = { + timeValue: number; + totalKeystrokes: number; + finalScore: number | null; + startTimer: () => void; + stopTimer: () => void; + resetTimer: () => void; +}; + +// ============================================================================ +// Manager Interfaces - Game Status +// ============================================================================ + +export type GameStatusManager = { + gameStatus: GameStatus; + setGameStatus: (status: GameStatus) => void; + startGame: () => void; + quitGame: () => void; + level: number; + score: number; + togglePause: () => void; +}; + +// ============================================================================ +// Manager Interfaces - Key Tracking +// ============================================================================ + +export type GameKeyManager = { + keyLog: KeyLogEntry[]; + clearKeyLog: () => void; + getKeyLog: () => KeyLogEntry[]; +}; + +// ============================================================================ +// Unified Game Manager - Main Interface +// ============================================================================ + +export type GameManager = { + // Required rendering + containerRef: RefObject; + renderBoard: () => void; + + // Required managers + cursor: SnakeCursorManager; + scoreManager: ScoreManager; + + // Required lifecycle + gameStatus: GameStatus; + setGameStatus: (status: GameStatus) => void; + startGame: () => void; + quitGame: () => void; + + // Required state + level: number; + score: number; + + // Required key tracking + keyLog: KeyLogEntry[]; + clearKeyLog: () => void; + getKeyLog: () => KeyLogEntry[]; + + // Game-specific additions + togglePause: () => void; +}; + +// ============================================================================ +// Hook Return Types +// ============================================================================ + +// Forward declarations for hook return types +// These will be imported from their respective modules + +export type UseGameType = ReturnType; diff --git a/src/useCursor/useCursor.ts b/src/useCursor/useCursor.ts index eb8127c..2601f36 100755 --- a/src/useCursor/useCursor.ts +++ b/src/useCursor/useCursor.ts @@ -1,5 +1,5 @@ import { useRef, useState } from "react"; -import type { Direction, SnakeBody } from "../types"; +import type { Direction, SnakeBody, Position } from "../types"; import type { UseBoardType } from "../useBoard"; export function useCursor( @@ -163,7 +163,73 @@ export function useCursor( return "continue"; }; + // Unified API compliant methods + const position = (): Position => { + const head = snakeBodyRef.current[0]; + return head || { r: 0, c: 0 }; + }; + + const mode = () => 'normal' as const; + + const moveLeft = (_count?: number) => { + changeDirection('left'); + }; + + const moveRight = (_count?: number) => { + changeDirection('right'); + }; + + const moveUp = (_count?: number) => { + changeDirection('up'); + }; + + const moveDown = (_count?: number) => { + changeDirection('down'); + }; + + const moveToStart = () => { + // Snake-specific: move to leftmost position on current row + changeDirection('left'); + }; + + const moveToEnd = () => { + // Snake-specific: move to rightmost position on current row + changeDirection('right'); + }; + + const moveToTop = () => { + // Snake-specific: move to top of board + changeDirection('up'); + }; + + const moveToBottom = () => { + // Snake-specific: move to bottom of board + changeDirection('down'); + }; + + const lastMotionRef = useRef(null); + + const repeatLastMotion = () => { + if (lastMotionRef.current) { + changeDirection(lastMotionRef.current); + } + }; + return { + // Unified API methods + position, + mode, + moveLeft, + moveRight, + moveUp, + moveDown, + moveToStart, + moveToEnd, + moveToTop, + moveToBottom, + repeatLastMotion, + + // Snake-specific methods (with proper values) snakeBody, snakeBodyRef, direction, diff --git a/src/useGame/useGame.ts b/src/useGame/useGame.ts index 82fd20c..259a2f4 100755 --- a/src/useGame/useGame.ts +++ b/src/useGame/useGame.ts @@ -1,4 +1,5 @@ import { useEffect } from "react"; +import type { GameManager, GameOptions } from "../types"; import { useBoard } from "../useBoard"; import { useCursor } from "../useCursor"; import { useFood } from "./useFood"; @@ -6,7 +7,23 @@ import { useGameStatus } from "../useGameStatus"; import { useKeyBindings, type UseKeyBindingsType } from "./useKeyBindings"; import { useScore } from "../useScore"; -export function useGame(cols: number, rows: number, platformHook?: unknown) { +export function useGame(optionsOrCols: GameOptions | number, rows?: number, platformHook?: unknown): GameManager { + // Support both old signature (cols, rows) and new signature (options) + let cols: number; + let actualPlatformHook = platformHook; + + if (typeof optionsOrCols === 'object') { + // New signature: useGame(options, platformHook) + const options = optionsOrCols as GameOptions; + cols = options.cols; + rows = options.rows; + actualPlatformHook = rows as unknown as typeof platformHook; + } else { + // Old signature: useGame(cols, rows, platformHook) + cols = optionsOrCols; + rows = rows || 20; + } + const boardManager = useBoard(); const { containerRef, renderBoard } = boardManager; @@ -16,7 +33,7 @@ export function useGame(cols: number, rows: number, platformHook?: unknown) { const foodManager = useFood(cols, rows, boardManager); const gameManager = useGameStatus(boardManager, snakeManager, foodManager); - const { gameStatus, startGame, stopGame, togglePause, score, level } = gameManager; + const { gameStatus, setGameStatus, startGame, quitGame, togglePause, score, level } = gameManager; useEffect(() => { renderBoard(cols, rows); @@ -31,25 +48,45 @@ export function useGame(cols: number, rows: number, platformHook?: unknown) { const scoreManager = useScore({ gameStatus }); - const fullGameManager = { - containerRef, - gameStatus, - startGame, - stopGame, - togglePause, - score, - level, - ...snakeManager, - ...foodManager, - ...keyBindings, - scoreManager, + // Wrap renderBoard to match Unified API signature + const renderBoardWrapped = () => { + renderBoard(cols, rows); }; - if (typeof platformHook === "function") { - platformHook(fullGameManager); + // Build Unified API compliant GameManager + const gameManagerResult: GameManager = { + // Required rendering + containerRef, + renderBoard: renderBoardWrapped, + + // Required managers + cursor: snakeManager, + scoreManager, + + // Required lifecycle + gameStatus, + setGameStatus, + startGame, + quitGame, + + // Required state + level, + score, + + // Required key tracking + keyLog: keyBindings.keyLog, + clearKeyLog: keyBindings.clearLog, + getKeyLog: keyBindings.getLog, + + // Game-specific additions + togglePause, + }; + + if (typeof actualPlatformHook === "function") { + actualPlatformHook(gameManagerResult); } - return fullGameManager; + return gameManagerResult; } export type { GameStatus } from "../types"; diff --git a/src/useGameStatus/useGameStatus.ts b/src/useGameStatus/useGameStatus.ts index 1476a36..5463aee 100755 --- a/src/useGameStatus/useGameStatus.ts +++ b/src/useGameStatus/useGameStatus.ts @@ -158,6 +158,7 @@ export function useGameStatus( setGameStatus, startGame, stopGame, + quitGame: stopGame, paused, togglePause: () => setPaused(curr => !curr), score, diff --git a/src/useScore/useScore.ts b/src/useScore/useScore.ts index ea5f781..a512f18 100755 --- a/src/useScore/useScore.ts +++ b/src/useScore/useScore.ts @@ -14,6 +14,8 @@ export function useScore({ gameStatus }: UseScoreParams) { return { timeValue, + totalKeystrokes: 0, // TODO: Track from keyBindings + finalScore: null, // TODO: Calculate on game-won startTimer, stopTimer, resetTimer,