Track content completion per mode independently, preserving history across switches

This commit is contained in:
2025-11-04 20:24:24 +00:00
parent 157e2a71f4
commit c147fd42d2
5 changed files with 254 additions and 79 deletions
+38 -26
View File
@@ -105,38 +105,50 @@ export type CursorManager = {
// ============================================================================
export type ScoreManager = {
timeValue: number;
totalKeystrokes: number;
score: number;
correct: number;
missed: number;
deaths: number;
};
timeValue: number;
totalKeystrokes: number;
score: number;
correct: number;
missed: number;
deaths: number;
lettersCompleted: number;
wordsCompleted: number;
sentencesCompleted: number;
paragraphsCompleted: number;
currentCount: number; // Count for current game mode
};
// ============================================================================
// Manager Interfaces - Game Status
// ============================================================================
export type GameStatusManager = {
gameStatus: GameStatus;
setGameStatus: (status: GameStatus) => void;
startGame: () => void;
quitGame: () => void;
level: number;
shipState: ShipState;
shipExplosionTime: number;
updatePlayerX: (x: number) => void;
handleTypedLetter: (letter: string) => void;
setGameLoopCallback: (callback: (deltaTime: number, letters: FallingLetter[]) => void) => void;
setGameMode: (mode: GameMode) => void;
addCorrect: () => void;
addMissed: () => void;
addDeath: () => void;
score: number;
correct: number;
missed: number;
deaths: number;
};
gameStatus: GameStatus;
setGameStatus: (status: GameStatus) => void;
startGame: () => void;
quitGame: () => void;
level: number;
shipState: ShipState;
shipExplosionTime: number;
updatePlayerX: (x: number) => void;
handleTypedLetter: (letter: string) => void;
setGameLoopCallback: (callback: (deltaTime: number, letters: FallingLetter[]) => void) => void;
setGameMode: (mode: GameMode) => void;
setPendingOptions: (options: GameOptions) => void;
addCorrect: () => void;
addMissed: () => void;
addDeath: () => void;
trackCompletion: (mode: GameMode) => void;
score: number;
correct: number;
missed: number;
deaths: number;
lettersCompleted: number;
wordsCompleted: number;
sentencesCompleted: number;
paragraphsCompleted: number;
getCurrentCount: () => number;
};
// ============================================================================
// Manager Interfaces - Key Tracking
+4 -4
View File
@@ -69,10 +69,10 @@ export function useGame(
gameStatusManager.setGameMode(gameMode);
}, [gameMode]);
const changeGameMode = (newOptions: GameOptions) => {
setOptions(newOptions);
// Don't quit - just switch modes and let game continue
};
const changeGameMode = (newOptions: GameOptions) => {
setOptions(newOptions);
gameStatusManager.setPendingOptions(newOptions);
};
// Set the game loop callback
useEffect(() => {
+109 -40
View File
@@ -23,6 +23,7 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal
// 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);
const pendingOptionsRef = useRef<GameOptions | null>(null);
// Update refs when options change
useEffect(() => {
@@ -33,13 +34,17 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal
wordListRef.current = wordList;
}, [wordList]);
const [gameStatus, setGameStatus] = useState<GameStatus>("waiting");
const shipStateRef = useRef<ShipState>("normal");
const shipExplosionTimeRef = useRef<number>(0);
const [score, setScore] = useState(0);
const [correct, setCorrect] = useState(0);
const [missed, setMissed] = useState(0);
const [deaths, setDeaths] = useState(0);
const [gameStatus, setGameStatus] = useState<GameStatus>("waiting");
const shipStateRef = useRef<ShipState>("normal");
const shipExplosionTimeRef = useRef<number>(0);
const [score, setScore] = useState(0);
const [correct, setCorrect] = useState(0);
const [missed, setMissed] = useState(0);
const [deaths, setDeaths] = useState(0);
const [lettersCompleted, setLettersCompleted] = useState(0);
const [wordsCompleted, setWordsCompleted] = useState(0);
const [sentencesCompleted, setSentencesCompleted] = useState(0);
const [paragraphsCompleted, setParagraphsCompleted] = useState(0);
const gameLoopRef = useRef<number | null>(null);
const lastFrameTimeRef = useRef<number>(0);
const lettersRef = useRef<FallingLetter[]>([]);
@@ -132,6 +137,8 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal
lastSpawnRef.current = performance.now();
shipStateRef.current = 'normal';
shipExplosionTimeRef.current = 0;
// Apply pending options after death
applyPendingOptions();
}, 500); // Match the 500ms explosion animation
}
}
@@ -265,16 +272,19 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal
}
}
// Check if word is complete
if (charIndex >= fullText.length) {
// Word complete - explode and clear
currentLetter.state = 'exploding';
currentLetter.stateStartTime = performance.now();
setTimeout(() => {
lettersRef.current = [];
lastSpawnRef.current = 0;
}, 300);
// Check if word is complete
if (charIndex >= fullText.length) {
// Word complete - explode and clear
currentLetter.state = 'exploding';
currentLetter.stateStartTime = performance.now();
trackCompletion(gameModeRef.current);
setTimeout(() => {
lettersRef.current = [];
lastSpawnRef.current = 0;
// Apply pending options after letter completes
applyPendingOptions();
}, 300);
} else {
// More characters to type - advance to next character
currentLetter.charIndex = charIndex;
@@ -319,6 +329,32 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal
gameModeRef.current = mode;
};
const setPendingOptions = (newOptions: GameOptions) => {
pendingOptionsRef.current = newOptions;
};
const applyPendingOptions = () => {
if (!pendingOptionsRef.current) return;
const pending = pendingOptionsRef.current;
pendingOptionsRef.current = null;
// Update game mode
if (pending.gameMode) {
gameModeRef.current = pending.gameMode;
}
// Update speed ref
if (pending.downwardSpeed !== undefined) {
downwardSpeedRef.current = pending.downwardSpeed;
}
// Update word list ref
if (pending.wordList !== undefined) {
wordListRef.current = pending.wordList;
}
};
const addCorrect = () => {
setScore(prev => prev + 1);
setCorrect(prev => prev + 1);
@@ -329,29 +365,62 @@ export function useGameStatus(_onGameLoopTick?: (deltaTime: number, letters: Fal
setMissed(prev => prev + 1);
};
const addDeath = () => {
setDeaths(prev => prev + 1);
};
const addDeath = () => {
setDeaths(prev => prev + 1);
};
return {
gameStatus,
setGameStatus,
startGame,
quitGame,
handleTypedLetter,
shipState: shipStateRef.current, // Return ref value for immediate updates
shipExplosionTime: shipExplosionTimeRef.current, // Return ref value for immediate updates
updatePlayerX,
setGameLoopCallback,
setGameMode,
addCorrect,
addMissed,
addDeath,
score,
correct,
missed,
deaths,
};
}
const trackCompletion = (mode: GameMode) => {
if (mode === 'letters') {
setLettersCompleted(prev => prev + 1);
} else if (mode === 'words') {
setWordsCompleted(prev => prev + 1);
} else if (mode === 'sentences') {
setSentencesCompleted(prev => prev + 1);
} else if (mode === 'paragraphs') {
setParagraphsCompleted(prev => prev + 1);
}
};
const getCurrentCount = (): number => {
const mode = gameModeRef.current;
if (mode === 'letters') {
return lettersCompleted;
} else if (mode === 'words') {
return wordsCompleted;
} else if (mode === 'sentences') {
return sentencesCompleted;
} else if (mode === 'paragraphs') {
return paragraphsCompleted;
}
return 0;
};
return {
gameStatus,
setGameStatus,
startGame,
quitGame,
handleTypedLetter,
shipState: shipStateRef.current, // Return ref value for immediate updates
shipExplosionTime: shipExplosionTimeRef.current, // Return ref value for immediate updates
updatePlayerX,
setGameLoopCallback,
setGameMode,
setPendingOptions,
addCorrect,
addMissed,
addDeath,
trackCompletion,
score,
correct,
missed,
deaths,
lettersCompleted,
wordsCompleted,
sentencesCompleted,
paragraphsCompleted,
getCurrentCount,
};
}
export type UseGameStatusType = ReturnType<typeof useGameStatus>;
+14 -9
View File
@@ -14,14 +14,19 @@ export function useScore(gameStatusManager: any): ScoreManager {
return () => clearInterval(interval);
}, [gameStatusManager.gameStatus]);
return {
timeValue,
totalKeystrokes: 0, // TODO: Track keystrokes from keyboard input
score: gameStatusManager.score,
correct: gameStatusManager.correct,
missed: gameStatusManager.missed,
deaths: gameStatusManager.deaths,
};
}
return {
timeValue,
totalKeystrokes: 0, // TODO: Track keystrokes from keyboard input
score: gameStatusManager.score,
correct: gameStatusManager.correct,
missed: gameStatusManager.missed,
deaths: gameStatusManager.deaths,
lettersCompleted: gameStatusManager.lettersCompleted,
wordsCompleted: gameStatusManager.wordsCompleted,
sentencesCompleted: gameStatusManager.sentencesCompleted,
paragraphsCompleted: gameStatusManager.paragraphsCompleted,
currentCount: gameStatusManager.getCurrentCount(),
};
}
export type UseScoreType = ReturnType<typeof useScore>;