diff --git a/example/src/App.tsx b/example/src/App.tsx index dd083f7..f540bdf 100755 --- a/example/src/App.tsx +++ b/example/src/App.tsx @@ -11,33 +11,56 @@ function App() { const gameManager = useGame({ gameMode }, useKeyBindings); const { containerRef, gameStatus, scoreManager } = gameManager; const lastTransitionIndexRef = useRef(-1); + const triggeredScoreTransitionsRef = useRef>(new Set()); + const lastTriggeredModeRef = useRef(null); useEffect(() => { if (gameStatus !== "started") return; + // Handle score-based transitions + if (scenario.scoreTransitions) { + const currentScore = scoreManager.score; + const nextTransition = scenario.scoreTransitions.find( + (t) => currentScore >= t.scoreThreshold && !triggeredScoreTransitionsRef.current.has(t.scoreThreshold) + ); + + if (nextTransition) { + triggeredScoreTransitionsRef.current.add(nextTransition.scoreThreshold); + setGameMode(nextTransition.targetMode); + gameManager.changeGameMode({ gameMode: nextTransition.targetMode }); + } + return; + } + + // Handle count-based transitions + if (!scenario.transitions) return; + + // Skip if we already triggered a transition for this mode + if (lastTriggeredModeRef.current === gameMode) { + return; + } + const currentTransitionIdx = scenario.transitions.findIndex((t) => t.fromMode === gameMode); - console.log(`[DEBUG] gameMode=${gameMode}, currentTransitionIdx=${currentTransitionIdx}, lastTransitionIndex=${lastTransitionIndexRef.current}`); if (currentTransitionIdx === -1 || currentTransitionIdx <= lastTransitionIndexRef.current) { - console.log(`[DEBUG] SKIPPING - already processed or no transition found`); return; } const currentTransition = scenario.transitions[currentTransitionIdx]; const currentCount = scoreManager.currentCount; - console.log(`[DEBUG] currentCount=${currentCount}, threshold=${currentTransition.threshold}, lettersCompleted=${scoreManager.lettersCompleted}, wordsCompleted=${scoreManager.wordsCompleted}, sentencesCompleted=${scoreManager.sentencesCompleted}`); - if (currentCount >= currentTransition.threshold) { - console.log(`[DEBUG] TRIGGERING TRANSITION: ${currentTransition.fromMode} -> ${currentTransition.targetMode}`); lastTransitionIndexRef.current = currentTransitionIdx; + lastTriggeredModeRef.current = gameMode; // Remember we triggered from this mode setGameMode(currentTransition.targetMode); gameManager.changeGameMode({ gameMode: currentTransition.targetMode }); } - }, [scoreManager.currentCount, gameMode, gameManager, scenario, gameStatus, scoreManager.lettersCompleted, scoreManager.wordsCompleted, scoreManager.sentencesCompleted]); + }, [scoreManager.currentCount, gameMode, gameManager, scenario, gameStatus, scoreManager.score]); useEffect(() => { lastTransitionIndexRef.current = -1; + triggeredScoreTransitionsRef.current.clear(); + lastTriggeredModeRef.current = null; }, [scenarioId]); const formatTime = (ms: number) => { diff --git a/example/src/testStateMachines.ts b/example/src/testStateMachines.ts index 690a2bc..7edbd77 100644 --- a/example/src/testStateMachines.ts +++ b/example/src/testStateMachines.ts @@ -7,16 +7,47 @@ export type ModeTransition = { description: string; }; +export type ScoreTransition = { + scoreThreshold: number; + targetMode: GameMode; + description: string; +}; + export type TestScenario = { id: string; name: string; description: string; initialMode: GameMode; - transitions: ModeTransition[]; + transitions?: ModeTransition[]; + scoreTransitions?: ScoreTransition[]; }; export const testScenarios: Record = { - progressive: { + scoreBasedProgression: { + id: 'scoreBasedProgression', + name: 'Score-Based Progression', + description: 'Change modes based on score: score 10 → words, score 50 → sentences, score 100 → paragraphs. Tests delayed application until current item completes.', + initialMode: 'letters', + scoreTransitions: [ + { + scoreThreshold: 10, + targetMode: 'words', + description: 'At score 10, request switch to words', + }, + { + scoreThreshold: 50, + targetMode: 'sentences', + description: 'At score 50, request switch to sentences', + }, + { + scoreThreshold: 100, + targetMode: 'paragraphs', + description: 'At score 100, request switch to paragraphs', + }, + ], + }, + + progressive: { id: 'progressive', name: 'Progressive Mode Ladder', description: 'Automatically progress through all modes: 10 letters → 10 words → 5 sentences → 3 paragraphs', diff --git a/src/types.ts b/src/types.ts index b4862cb..97a5220 100755 --- a/src/types.ts +++ b/src/types.ts @@ -135,6 +135,7 @@ export type GameStatusManager = { setGameLoopCallback: (callback: (deltaTime: number, letters: FallingLetter[]) => void) => void; setGameMode: (mode: GameMode) => void; setPendingOptions: (options: GameOptions) => void; + hasPendingOptions: () => boolean; addCorrect: () => void; addMissed: () => void; addDeath: () => void; diff --git a/src/useGameStatus/useGameStatus.ts b/src/useGameStatus/useGameStatus.ts index a5cc501..8e702fe 100755 --- a/src/useGameStatus/useGameStatus.ts +++ b/src/useGameStatus/useGameStatus.ts @@ -333,12 +333,7 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal const setPendingOptions = (newOptions: GameOptions) => { pendingOptionsRef.current = newOptions; - - // Update currentGameMode immediately so getCurrentCount() returns the correct value - if (newOptions.gameMode) { - setCurrentGameMode(newOptions.gameMode); - gameModeRef.current = newOptions.gameMode; - } + // Don't update anything immediately - wait for applyPendingOptions }; const applyPendingOptions = () => { @@ -404,6 +399,10 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal return 0; }; + const hasPendingOptions = () => { + return pendingOptionsRef.current !== null; + }; + return { gameStatus, setGameStatus, @@ -416,6 +415,7 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal setGameLoopCallback, setGameMode, setPendingOptions, + hasPendingOptions, addCorrect, addMissed, addDeath,