diff --git a/src/app.ts b/src/app.ts index e571e6f..ea38818 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,7 +1,8 @@ import type { ScreenName } from "./types"; import { openDB, getProgress, saveProgress } from "./storage/db"; import { initWelcomeScreen, initLessonScreen } from "./ui/screens"; -import { initForestScreen } from "./forest/scene"; +import { initForestScreen, renderForestScene } from "./forest/scene"; +import { startPreGeneration, initRewardScreen } from "./forest/reward"; import { getGreeting } from "./companion/companion"; import { companions } from "./companion/characters"; import { showParentCodePrompt, initParentScreen } from "./ui/parent"; @@ -25,10 +26,44 @@ export function getDB(): IDBDatabase { const MAX_LEVEL = 6; -async function handleLessonComplete(completedLevel: number): Promise { +/** + * Ensure progress object has all fields (migration for older saved data). + */ +function migrateProgress( + progress: NonNullable>>, +): void { + if (!progress.errorKeyCounts) progress.errorKeyCounts = {}; + if (!progress.lastPlayedLevels) progress.lastPlayedLevels = []; + if (progress.totalCorrect === undefined) progress.totalCorrect = 0; + if (progress.totalErrors === undefined) progress.totalErrors = 0; +} + +async function handleLessonComplete( + completedLevel: number, + stats: { + totalCorrect: number; + totalErrors: number; + errorKeys: Record; + }, +): Promise { // Update progress after lesson completion const progress = await getProgress(db); if (!progress) return; + migrateProgress(progress); + + // Accumulate exercise stats (D-15, D-16) + progress.totalCorrect += stats.totalCorrect; + progress.totalErrors += stats.totalErrors; + for (const [key, count] of Object.entries(stats.errorKeys)) { + progress.errorKeyCounts[key] = + (progress.errorKeyCounts[key] ?? 0) + count; + } + + // Track last played levels (keep last 3) for repetition detection + progress.lastPlayedLevels.push(completedLevel); + if (progress.lastPlayedLevels.length > 3) { + progress.lastPlayedLevels = progress.lastPlayedLevels.slice(-3); + } // Add level to completedLevels if not already there if (!progress.completedLevels.includes(completedLevel)) { @@ -36,8 +71,11 @@ async function handleLessonComplete(completedLevel: number): Promise { } // Advance to next level if this was the current level (LEVL-02, LEVL-03) - // Cap at MAX_LEVEL + 1 so we don't go beyond defined levels - if (completedLevel === progress.currentLevel && completedLevel < MAX_LEVEL) { + // Cap at MAX_LEVEL so we don't go beyond defined levels + if ( + completedLevel === progress.currentLevel && + completedLevel < MAX_LEVEL + ) { progress.currentLevel = completedLevel + 1; } @@ -52,9 +90,38 @@ async function handleLessonComplete(completedLevel: number): Promise { await saveProgress(db, progress); - // Re-init forest screen with updated progress, then navigate - await initForestScreen(db, startLessonFromForest); - showScreen("forest"); + // Show REWARD screen instead of going directly to forest + showScreen("reward"); + + // Set companion avatar on reward screen + const companion = companions.find( + (c) => c.type === progress.selectedCharacter, + ); + if (companion) { + const avatar = document.getElementById( + "reward-companion-avatar", + ) as HTMLImageElement; + avatar.src = companion.avatarUrl; + avatar.alt = companion.name; + } + + const elementId = await initRewardScreen( + db, + completedLevel, + progress.selectedCharacter, + () => { + // "Weiter ueben" -- start next lesson + startLessonFromForest( + Math.min(progress.currentLevel, MAX_LEVEL), + ); + }, + async () => { + // "Zurueck zum Wald" -- show forest with new element animated + await initForestScreen(db, startLessonFromForest); + await renderForestScene(db, elementId); + showScreen("forest"); + }, + ); } async function handleLessonCancel(): Promise { @@ -69,9 +136,27 @@ function startLessonFromForest(level: number): void { lessonCleanup = null; } - // Get layout from progress (already loaded) + // Get layout and determine review/encouragement from progress getProgress(db).then((progress) => { - const layout = progress?.selectedLayout ?? "de"; + if (!progress) return; + migrateProgress(progress); + + const layout = progress.selectedLayout ?? "de"; + + // Review every 3rd session (D-05) + const isReview = + progress.totalSessions > 0 && + progress.totalSessions % 3 === 0; + + // Encouragement if last 2 played levels are the same as current (D-06) + const last2 = progress.lastPlayedLevels.slice(-2); + const showEncouragement = + last2.length === 2 && + last2[0] === level && + last2[1] === level; + + // Start image pre-generation (D-11) -- fire and forget + startPreGeneration(db); showScreen("lesson"); @@ -81,6 +166,8 @@ function startLessonFromForest(level: number): void { layout, handleLessonComplete, handleLessonCancel, + isReview, + showEncouragement, ); }); } @@ -104,6 +191,8 @@ export async function initApp(): Promise { const progress = await getProgress(db); if (progress && progress.selectedCharacter) { + migrateProgress(progress); + // Returning user -- init forest and go there await initForestScreen(db, startLessonFromForest); showScreen("forest");