Add score-based transitions and fix mode switching to prevent cascading and visual glitches
This commit is contained in:
+29
-6
@@ -11,33 +11,56 @@ function App() {
|
||||
const gameManager = useGame({ gameMode }, useKeyBindings);
|
||||
const { containerRef, gameStatus, scoreManager } = gameManager;
|
||||
const lastTransitionIndexRef = useRef<number>(-1);
|
||||
const triggeredScoreTransitionsRef = useRef<Set<number>>(new Set());
|
||||
const lastTriggeredModeRef = useRef<GameMode | null>(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) => {
|
||||
|
||||
@@ -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<string, TestScenario> = {
|
||||
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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user