diff --git a/components/GameApp.tsx b/components/GameApp.tsx index 7c0458c..18a147b 100644 --- a/components/GameApp.tsx +++ b/components/GameApp.tsx @@ -37,10 +37,10 @@ const GameApp: React.FC = ({ mode, assignment }) => { const [feedback, setFeedback] = useState(null); const [isDragging, setIsDragging] = useState(false); const [attemptsUsed, setAttemptsUsed] = useState(0); - const [hasRevealed, setHasRevealed] = useState(false); - const maxAttempts = useMemo(() => assignment?.options?.attempts === 'two-then-reveal' ? 2 : Infinity, [assignment]); - const timeoutRef = useRef | null>(null); + const maxAttempts = assignment?.options.attemptsPerItem; + const attemptsLimit = typeof maxAttempts === 'number' ? maxAttempts : null; + const outOfAttempts = mode === 'homework' && attemptsLimit !== null && attemptsUsed >= attemptsLimit; // --- Effects for Initialization and Mode Switching --- useEffect(() => { @@ -55,6 +55,7 @@ const GameApp: React.FC = ({ mode, assignment }) => { setStudentName(savedName); const storageKey = `ss::${assignment.id}::${savedName}`; const savedProgress = loadProgress(storageKey); + setAttemptsUsed(savedProgress?.attemptsUsed ?? 0); if (savedProgress && savedProgress.results.length > 0) { setProgress(savedProgress); setShowResumePrompt(true); @@ -156,10 +157,14 @@ const GameApp: React.FC = ({ mode, assignment }) => { assignmentId: assignment.id, version: assignment.version, student: { name: studentName }, - summary: { total: assignment.sentences.length, solvedWithinMax: 0, firstTry: 0, reveals: 0, avgAttempts: 0 }, + summary: { correct: 0, total: assignment.sentences.length, reveals: 0 }, + attemptsUsed: 0, results: [] }; setProgress(initialProgress); + const storageKey = `ss::${assignment.id}::${studentName}`; + saveProgress(storageKey, initialProgress); + setAttemptsUsed(0); setCurrentSentenceIndex(0); setupNewSentence(0); setShowResumePrompt(false); @@ -293,14 +298,14 @@ const GameApp: React.FC = ({ mode, assignment }) => { ...progress, results: [...progress.results, result], summary: { - total: progress.summary.total, - solvedWithinMax: progress.summary.solvedWithinMax + solvedInc, - firstTry: progress.summary.firstTry + firstTryInc, - reveals: progress.summary.reveals + (result.revealed ? 1 : 0), - avgAttempts: newAvg - } + ...progress.summary, + correct: progress.summary.correct + (result.ok ? 1 : 0), + reveals: progress.summary.reveals + (result.revealed ? 1 : 0) + }, + attemptsUsed: 0 }; setProgress(newProgress); + setAttemptsUsed(0); const storageKey = `ss::${assignment.id}::${studentName}`; saveProgress(storageKey, newProgress); }; @@ -309,6 +314,7 @@ const GameApp: React.FC = ({ mode, assignment }) => { const attempts = attemptsUsed + 1; setAttemptsUsed(attempts); let isCorrect = false; + let shouldShowFeedback = true; if (isChunkMode && currentChunks) { const userChunks = userSentence.map(w => w.text.trim().toLowerCase()); @@ -320,7 +326,37 @@ const GameApp: React.FC = ({ mode, assignment }) => { } if (mode === 'homework') { - updateProgress({ index: currentSentenceIndex, ok: isCorrect, revealed: false, attempts: 1 }); + if (isCorrect) { + updateProgress({ index: currentSentenceIndex, ok: true, revealed: false }); + } else { + const newAttempts = attemptsUsed + 1; + setAttemptsUsed(newAttempts); + if (progress && assignment) { + const storageKey = `ss::${assignment.id}::${studentName}`; + const newProgress = { ...progress, attemptsUsed: newAttempts }; + setProgress(newProgress); + saveProgress(storageKey, newProgress); + } + if (attemptsLimit !== null && newAttempts >= attemptsLimit) { + if (assignment?.options.revealAnswerAfterMaxAttempts) { + handleReveal(); + return; + } else { + updateProgress({ index: currentSentenceIndex, ok: false, revealed: false }); + } + } else { + shouldShowFeedback = false; + } + } + } + + if (shouldShowFeedback) { + setFeedback({ + type: isCorrect ? 'success' : 'error', + message: isCorrect ? 'Correct! Well done!' : `Not quite. The correct answer is: "${correctSentenceText}"`, + }); + } else { + setFeedback(null); } }; @@ -354,23 +390,41 @@ const GameApp: React.FC = ({ mode, assignment }) => { setStudentName(newName); }; - const renderGameContent = () => { - const isFinalStep = !!feedback && (feedback.type === 'success' || hasRevealed || attemptsUsed >= maxAttempts); - return ( - <> - {isLoading ? ( -
- -
- ) : ( -
- handleDrop(wordId, sourceZoneId, 'available-words')} onWordClick={(wordId) => handleWordClick(wordId, 'available-words')} isDragging={isDragging} setIsDragging={setIsDragging} /> - handleDrop(wordId, sourceZoneId, 'user-sentence', index)} onWordClick={(wordId) => handleWordClick(wordId, 'user-sentence')} isDragging={isDragging} setIsDragging={setIsDragging} isSentenceZone={true} /> - - {feedback && ( -
- {feedback.message} -
+ const renderGameContent = () => ( + <> + {isLoading ? ( +
+ +
+ ) : ( +
+ handleDrop(wordId, sourceZoneId, 'available-words')} onWordClick={(wordId) => handleWordClick(wordId, 'available-words')} isDragging={isDragging} setIsDragging={setIsDragging} /> + handleDrop(wordId, sourceZoneId, 'user-sentence', index)} onWordClick={(wordId) => handleWordClick(wordId, 'user-sentence')} isDragging={isDragging} setIsDragging={setIsDragging} isSentenceZone={true} /> + + {feedback && ( +
+ {feedback.message} +
+ )} + +
+ {!feedback ? ( + <> + {mode === 'homework' && ( + <> + + + + )} + + {mode === 'homework' && ( + + )} + + ) : ( + )}
diff --git a/types.ts b/types.ts index 199db57..0a05bf9 100644 --- a/types.ts +++ b/types.ts @@ -56,6 +56,12 @@ export interface StudentProgress { assignmentId: string; version: number; student: { name: string }; - summary: Summary; + summary: { + correct: number; + total: number; + reveals: number; + }; + attemptsUsed: number; + results: Result[]; } diff --git a/utils/__tests__/storage.test.ts b/utils/__tests__/storage.test.ts index d6c7a58..c2e730f 100644 --- a/utils/__tests__/storage.test.ts +++ b/utils/__tests__/storage.test.ts @@ -8,7 +8,8 @@ describe('saveProgress', () => { assignmentId: 'a1', version: 1, student: { name: 'Alice' }, - summary: { total: 1, solvedWithinMax: 0, firstTry: 0, reveals: 0, avgAttempts: 0 }, + summary: { correct: 0, total: 1, reveals: 0 }, + attemptsUsed: 0, results: [] }; @@ -47,7 +48,8 @@ describe('loadProgress', () => { assignmentId: 'a1', version: 1, student: { name: 'Alice' }, - summary: { total: 1, solvedWithinMax: 0, firstTry: 0, reveals: 0, avgAttempts: 0 }, + summary: { correct: 0, total: 1, reveals: 0 }, + attemptsUsed: 0, results: [] }; diff --git a/utils/storage.ts b/utils/storage.ts index 0f68bfa..4a774aa 100644 --- a/utils/storage.ts +++ b/utils/storage.ts @@ -12,21 +12,11 @@ export const loadProgress = (key: string): StudentProgress | null => { try { const data = localStorage.getItem(key); if (!data) return null; - - const parsed = JSON.parse(data) as StudentProgress & { summary?: any }; - - if (parsed?.summary) { - const s = parsed.summary as any; - parsed.summary = { - total: s.total ?? 0, - solvedWithinMax: s.solvedWithinMax ?? s.correct ?? 0, - firstTry: s.firstTry ?? 0, - reveals: s.reveals ?? 0, - avgAttempts: s.avgAttempts ?? 0, - }; + const parsed = JSON.parse(data); + if (typeof parsed.attemptsUsed !== 'number') { + parsed.attemptsUsed = 0; } - - return parsed; + return parsed as StudentProgress; } catch (error) { console.error("Failed to load progress from localStorage", error); return null;