Implement platform-controlled GameOptions for complete game behavior customization
- Add GameOptions with gameMode, downwardSpeed, boardDimensions, and wordList - Platform now controls ALL game behavior through options - changeGameMode now accepts full GameOptions instead of just mode - downwardSpeed as single px/s value applies to any mode - wordList is mode-agnostic, platform provides what they need - All options are fully optional with sensible defaults - Update example app to use new options API
This commit is contained in:
+5
-2
@@ -61,7 +61,10 @@ export type FallingLetter = {
|
||||
export type KeyLogEntry = { key: string; timestamp: number };
|
||||
|
||||
export type GameOptions = {
|
||||
initialGameMode?: GameMode;
|
||||
gameMode?: GameMode; // Game mode (defaults to 'letters')
|
||||
downwardSpeed?: number; // Falling text speed in px/s (defaults to mode's built-in speed)
|
||||
boardDimensions?: [number, number]; // Canvas dimensions [width, height] (defaults to [800, 600])
|
||||
wordList?: string[]; // Custom word/sentence/paragraph list (optional, mode-agnostic)
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
@@ -171,7 +174,7 @@ export type GameManager = {
|
||||
|
||||
// Game-specific
|
||||
handleTypedLetter: (letter: string) => void;
|
||||
changeGameMode: (mode: GameMode) => void;
|
||||
changeGameMode: (options: GameOptions) => void;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
||||
@@ -6,16 +6,17 @@ import { useGameStatus } from "../useGameStatus";
|
||||
import { useScore } from "../useScore";
|
||||
|
||||
export function useGame(
|
||||
options?: GameOptions,
|
||||
initialOptions?: GameOptions,
|
||||
platformHook?: unknown
|
||||
): GameManager {
|
||||
const [keyLog, setKeyLog] = useState<KeyLogEntry[]>([]);
|
||||
const [gameMode, setGameModeState] = useState<GameMode>(options?.initialGameMode ?? 'letters');
|
||||
const [options, setOptions] = useState<GameOptions>(initialOptions ?? {});
|
||||
const gameMode = options.gameMode ?? 'letters';
|
||||
const gameModeRef = useRef<GameMode>(gameMode);
|
||||
|
||||
const boardManager = useBoard();
|
||||
const cursorManager = useCursor();
|
||||
const gameStatusManager = useGameStatus(undefined, gameMode);
|
||||
const gameStatusManager = useGameStatus(undefined, options);
|
||||
|
||||
// Wrap renderBoard to include player position and lasers
|
||||
const renderBoardWrapped = (letters: any = [], lasers: Laser[] = [], shipState: ShipState = 'normal', shipExplosionTime: number = 0) => {
|
||||
@@ -68,8 +69,8 @@ export function useGame(
|
||||
gameStatusManager.setGameMode(gameMode);
|
||||
}, [gameMode]);
|
||||
|
||||
const changeGameMode = (mode: GameMode) => {
|
||||
setGameModeState(mode);
|
||||
const changeGameMode = (newOptions: GameOptions) => {
|
||||
setOptions(newOptions);
|
||||
// Don't quit - just switch modes and let game continue
|
||||
};
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useState, useRef } from "react";
|
||||
import type { GameStatus, FallingLetter, ShipState, GameMode, Laser } from "../types";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
import type { GameStatus, FallingLetter, ShipState, GameMode, Laser, GameOptions } from "../types";
|
||||
import { getRandomWord } from "../wordLists";
|
||||
|
||||
const LETTER_SPEEDS: Record<GameMode, number> = {
|
||||
@@ -15,7 +15,24 @@ const MARGIN_RIGHT = 50; // Safe margin from right edge
|
||||
const CANVAS_WIDTH = 800;
|
||||
const SPACESHIP_Y = 600 - 40; // Position of the ship on canvas
|
||||
|
||||
export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: FallingLetter[], lasers: Laser[]) => void, gameMode: GameMode = 'letters') {
|
||||
export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: FallingLetter[], lasers: Laser[]) => void, options: GameOptions = {}) {
|
||||
const gameMode = options.gameMode ?? 'letters';
|
||||
const downwardSpeed = options.downwardSpeed;
|
||||
const wordList = options.wordList;
|
||||
|
||||
// Store options in refs so they can be accessed in the game loop
|
||||
const downwardSpeedRef = useRef<number | undefined>(downwardSpeed);
|
||||
const wordListRef = useRef<string[] | undefined>(wordList);
|
||||
|
||||
// Update refs when options change
|
||||
useEffect(() => {
|
||||
downwardSpeedRef.current = downwardSpeed;
|
||||
}, [downwardSpeed]);
|
||||
|
||||
useEffect(() => {
|
||||
wordListRef.current = wordList;
|
||||
}, [wordList]);
|
||||
|
||||
const [gameStatus, setGameStatus] = useState<GameStatus>("waiting");
|
||||
const shipStateRef = useRef<ShipState>("normal");
|
||||
const shipExplosionTimeRef = useRef<number>(0);
|
||||
@@ -39,7 +56,7 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal
|
||||
|
||||
// Spawn new letter/word if enough time has passed and no letters active
|
||||
if (currentTime - lastSpawnRef.current > SPAWN_INTERVAL && lettersRef.current.length < MAX_ACTIVE_LETTERS) {
|
||||
const fullText = getRandomWord(gameModeRef.current);
|
||||
const fullText = getRandomWord(gameModeRef.current, wordListRef.current);
|
||||
const firstChar = fullText[0].toUpperCase();
|
||||
|
||||
// For sentences/paragraphs, spawn centered. For letters/words, spawn randomly
|
||||
@@ -62,7 +79,7 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal
|
||||
}
|
||||
|
||||
// Update letter positions
|
||||
const speed = LETTER_SPEEDS[gameModeRef.current];
|
||||
const speed = downwardSpeedRef.current ?? LETTER_SPEEDS[gameModeRef.current];
|
||||
lettersRef.current.forEach(letter => {
|
||||
letter.y += (speed * deltaTime) / 1000;
|
||||
});
|
||||
|
||||
+4
-2
@@ -41,7 +41,9 @@ export function getRandomItem<T>(array: readonly T[]): T {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
export function getRandomWord(gameMode: 'letters' | 'words' | 'sentences' | 'paragraphs'): string {
|
||||
const list = wordLists[gameMode];
|
||||
export function getRandomWord(gameMode: 'letters' | 'words' | 'sentences' | 'paragraphs', customList?: string[]): string {
|
||||
// Use custom list if provided, otherwise use default for the game mode
|
||||
// Letters mode always uses default alphabet
|
||||
const list = customList && gameMode !== 'letters' ? customList : wordLists[gameMode];
|
||||
return String(getRandomItem(list));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user