Skip to content

Commit 10886e3

Browse files
committed
feat: update to newest version of codequest
1 parent 1b482b1 commit 10886e3

40 files changed

+268
-184
lines changed

eslint.config.mjs

+3
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,7 @@ export default antfu({
66
quotes: 'single',
77
semi: true,
88
},
9+
ignores: [
10+
'src/questions/*.md',
11+
],
912
});

example-question.md

+15-15
Original file line numberDiff line numberDiff line change
@@ -37,21 +37,21 @@ You're tasked with implementing a function to find the most frequent element in
3737

3838
```go
3939
func mostFrequent(arr []int) int {
40-
maxCount := 0
41-
result := arr[0]
42-
for i := range arr {
43-
count := 0
44-
for j := range arr {
45-
if arr[i] == arr[j] {
46-
count++
47-
}
48-
}
49-
if count > maxCount {
50-
maxCount = count
51-
result = arr[i]
52-
}
53-
}
54-
return result
40+
maxCount := 0
41+
result := arr[0]
42+
for i := range arr {
43+
count := 0
44+
for j := range arr {
45+
if arr[i] == arr[j] {
46+
count++
47+
}
48+
}
49+
if count > maxCount {
50+
maxCount = count
51+
result = arr[i]
52+
}
53+
}
54+
return result
5555
}
5656
```
5757

src/App.tsx

+7-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
1-
import Quiz from '@/components/Quiz';
1+
import React, { Suspense } from 'react';
2+
import LoadingScreen from '@/components/LoadingScreen';
3+
4+
const Quiz = React.lazy(() => import('@/components/Quiz'));
25

36
function App() {
47
return (
5-
<Quiz />
8+
<Suspense fallback={<LoadingScreen message="Initializing CodeQuest..." />}>
9+
<Quiz />
10+
</Suspense>
611
);
712
}
813

src/components/LoadingScreen.tsx

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import React from 'react';
2+
import { Loader2 } from 'lucide-react';
3+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
4+
import { Progress } from '@/components/ui/progress';
5+
import { config } from '@/config';
6+
7+
interface LoadingScreenProps {
8+
message: string;
9+
}
10+
11+
const LoadingScreen: React.FC<LoadingScreenProps> = ({ message }) => {
12+
return (
13+
<div className="flex items-center justify-center min-h-screen bg-gray-100">
14+
<Card className="w-full max-w-md">
15+
<CardHeader>
16+
<CardTitle className="text-2xl font-bold text-center">{config.title}</CardTitle>
17+
</CardHeader>
18+
<CardContent>
19+
<div className="flex flex-col items-center space-y-4">
20+
<Loader2 className="h-8 w-8 animate-spin text-primary" />
21+
<p className="text-center text-muted-foreground">{message}</p>
22+
<Progress value={66} className="w-full" />
23+
</div>
24+
</CardContent>
25+
</Card>
26+
</div>
27+
);
28+
};
29+
30+
export default LoadingScreen;

src/components/Quiz/Answers.tsx

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,30 +2,31 @@ import React from 'react';
22
import { Button } from '@/components/ui/button';
33
import RenderContent from '@/components/RenderContent';
44
import { cn } from '@/lib/utils';
5+
import type { ParsedQuestion } from '@/lib/markdownParser';
56

67
interface AnswersProps {
7-
answers: string[];
8+
question: ParsedQuestion;
89
onAnswer: (index: number) => void;
910
disabled: boolean;
1011
selectedAnswer: number | null;
11-
correctAnswer: number;
1212
answered: boolean;
1313
}
1414

1515
const Answers: React.FC<AnswersProps> = ({
16-
answers,
16+
question,
1717
onAnswer,
1818
disabled,
1919
selectedAnswer,
20-
correctAnswer,
2120
answered,
2221
}) => {
22+
const correctAnswers = question.correctAnswers || [question.correctAnswer];
23+
2324
return (
2425
<div className="space-y-4 mt-4">
2526
<h2 className="text-lg font-semibold">Answers</h2>
26-
{answers.map((answer, index) => {
27+
{question.answers.map((answer, index) => {
2728
const isSelected = selectedAnswer === index;
28-
const isCorrect = correctAnswer - 1 === index;
29+
const isCorrect = correctAnswers.includes(index + 1);
2930

3031
let buttonClass = '';
3132
if (answered) {

src/components/Quiz/Completion.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ const Completion: React.FC<CompletionProps> = ({ score, badges, onReset }) => {
2828
<h1 className="text-2xl font-bold text-center">🎉 Congratulations! 🎉</h1>
2929
</CardHeader>
3030
<CardContent>
31-
<p className="text-center mb-4">You've become an OpenEHR Integration Master!</p>
31+
<p className="text-center mb-4">
32+
You've become a
33+
{' '}
34+
{config.title}
35+
{' '}
36+
Master!
37+
</p>
3238
<p className="text-center mb-4">
3339
Final Score:&nbsp;
3440
{score}

src/components/Quiz/GameOver.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const GameOver: React.FC<GameOverProps> = ({ score, onReset, badges }) => {
2626
<h1 className="text-2xl font-bold text-center">Game Over</h1>
2727
</CardHeader>
2828
<CardContent>
29-
<p className="text-center mb-4">Your OpenEHR journey has come to an end.</p>
29+
<p className="text-center mb-4">Your Code Quest journey has come to an end.</p>
3030
<p className="text-center mb-4">
3131
Final Score:
3232
{score}
File renamed without changes.

src/components/Quiz/Result.tsx

+33-5
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,52 @@ import React from 'react';
22
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
33
import { Button } from '@/components/ui/button';
44
import RenderContent from '@/components/RenderContent';
5+
import type { ParsedQuestion } from '@/lib/markdownParser';
6+
import { isCorrectAnswer } from '@/lib/quiz-utils';
57

68
interface ResultProps {
7-
isCorrect: boolean;
8-
explanation: string;
9+
question: ParsedQuestion;
10+
selectedAnswer: number | null;
11+
isLastQuestion: boolean;
912
onNext: () => void;
1013
}
1114

12-
const Result: React.FC<ResultProps> = ({ isCorrect, explanation, onNext }) => {
15+
const Result: React.FC<ResultProps> = ({ question, selectedAnswer, isLastQuestion, onNext }) => {
16+
const isCorrect = selectedAnswer !== null && isCorrectAnswer(question, selectedAnswer);
17+
const correctAnswers = question.correctAnswers || [question.correctAnswer];
18+
1319
return (
1420
<>
1521
<Alert className={`mt-4 ${isCorrect ? 'bg-green-100' : 'bg-red-100'}`}>
1622
<AlertTitle>{isCorrect ? 'Correct!' : 'Incorrect'}</AlertTitle>
1723
<AlertDescription>
18-
<RenderContent content={explanation} />
24+
{correctAnswers.length === 1 && (
25+
<p className="mb-4">
26+
The correct answer is:
27+
{' '}
28+
{correctAnswers.map(a => `Answer ${a}`).join(', ')}
29+
</p>
30+
)}
31+
32+
{correctAnswers.length > 1 && (
33+
<p className="mb-4">
34+
The correct answers are:
35+
{' '}
36+
{correctAnswers.map(a => `Answer ${a}`).join(', ')}
37+
</p>
38+
)}
39+
{selectedAnswer !== null
40+
? (
41+
''
42+
)
43+
: (
44+
<p>You didn't select an answer</p>
45+
)}
46+
<RenderContent content={question.explanation} />
1947
</AlertDescription>
2048
</Alert>
2149
<Button onClick={onNext} className="w-full mt-4">
22-
Next Level
50+
{isLastQuestion ? 'Show Results' : 'Next Level'}
2351
</Button>
2452
</>
2553
);

src/components/Quiz/index.tsx

+38-16
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React, { useCallback, useEffect, useMemo, useState } from 'react';
22
import { AlertCircle, Brain, Github, Shield, Terminal, Volume2, VolumeX, Zap } from 'lucide-react';
3-
import GameOver from './GameOver.tsx';
4-
import Completion from './Completion.tsx';
5-
import Context from './Context.tsx';
6-
import Answers from './Answers.tsx';
7-
import Result from './Result.tsx';
3+
import GameOver from '@/components/Quiz/GameOver';
4+
import Completion from '@/components/Quiz/Completion';
5+
import Context from '@/components/Quiz/Context';
6+
import Answers from '@/components/Quiz/Answers';
7+
import Result from '@/components/Quiz/Result';
8+
import LoadingScreen from '@/components/LoadingScreen';
89
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert';
910
import { Button } from '@/components/ui/button';
1011
import {
@@ -21,10 +22,11 @@ import {
2122
TooltipProvider,
2223
TooltipTrigger,
2324
} from '@/components/ui/tooltip';
24-
import HealthBar from '@/components/HealthBar';
25+
import HealthBar from '@/components/Quiz/HealthBar';
2526
import useSoundEffects from '@/hooks/useSoundEffects';
2627
import type { ParsedQuestion } from '@/lib/markdownParser.ts';
2728
import { loadAllQuestions } from '@/lib/markdownParser.ts';
29+
import { isCorrectAnswer } from '@/lib/quiz-utils';
2830
import { renderContent } from '@/components/RenderContent';
2931
import { config } from '@/config';
3032
import './styles.css';
@@ -58,13 +60,15 @@ const Quiz: React.FC = () => {
5860
const [isLoading, setIsLoading] = useState(true);
5961
const [isAdditionalInformationOpen, setIsAdditionalInformationOpen] = useState(false);
6062
const [timeRemaining, setTimeRemaining] = useState<number | null>(null);
63+
const [isQuizComplete, setIsQuizComplete] = useState(false);
6164

6265
const currentQuestion = useMemo(() => questions[gameState.currentLevel], [questions, gameState.currentLevel]);
66+
const isLastQuestion = gameState.currentLevel === questions.length - 1;
6367

6468
const handleAnswer = useCallback((selectedIndex: number) => {
6569
setSelectedAnswer(selectedIndex);
6670
const currentQuestion = questions[gameState.currentLevel];
67-
const isCorrect = selectedIndex === currentQuestion.correctAnswer - 1;
71+
const isCorrect = isCorrectAnswer(currentQuestion, selectedIndex);
6872
const isTimedOut = selectedIndex === -1;
6973

7074
setGameState((prevState) => {
@@ -89,7 +93,19 @@ const Quiz: React.FC = () => {
8993
}
9094

9195
if (isTimedOut) {
92-
setSelectedAnswer(currentQuestion.correctAnswer - 1);
96+
// Handle timed out scenario for both single and multiple correct answers
97+
if (currentQuestion.correctAnswer !== undefined) {
98+
setSelectedAnswer(currentQuestion.correctAnswer - 1);
99+
}
100+
else if (currentQuestion.correctAnswers !== undefined && currentQuestion.correctAnswers.length > 0) {
101+
// If multiple correct answers, select the first one
102+
setSelectedAnswer(currentQuestion.correctAnswers[0] - 1);
103+
}
104+
else {
105+
// Fallback if no correct answer is defined (shouldn't happen, but just in case)
106+
console.error('No correct answer defined for the current question');
107+
setSelectedAnswer(null);
108+
}
93109
}
94110
}, [gameState.currentLevel, questions, soundEnabled, playCorrectSound, playWrongSound]);
95111

@@ -147,17 +163,22 @@ const Quiz: React.FC = () => {
147163
if (prevState.isCorrect && prevState.answeredWithoutHint) {
148164
newBadges.push(`Level ${prevState.currentLevel + 1} Master`);
149165
}
166+
const newLevel = prevState.currentLevel + 1;
167+
if (isLastQuestion) {
168+
setIsQuizComplete(true);
169+
return prevState; // Don't update the state if it's the last question
170+
}
150171
return {
151172
...prevState,
152-
currentLevel: prevState.currentLevel + 1,
173+
currentLevel: newLevel,
153174
hintUsed: false,
154175
answerSelected: false,
155176
isCorrect: false,
156177
answeredWithoutHint: false,
157178
badges: newBadges,
158179
};
159180
});
160-
}, []);
181+
}, [questions.length, isLastQuestion]);
161182

162183
const useHint = useCallback(() => {
163184
setGameState(prevState => ({ ...prevState, hintUsed: true }));
@@ -174,6 +195,7 @@ const Quiz: React.FC = () => {
174195
isCorrect: false,
175196
answeredWithoutHint: false,
176197
});
198+
setIsQuizComplete(false);
177199
}, []);
178200

179201
const toggleSound = useCallback(() => {
@@ -207,14 +229,14 @@ const Quiz: React.FC = () => {
207229
};
208230

209231
if (isLoading) {
210-
return <div>Loading questions...</div>;
232+
return <LoadingScreen message="Loading questions..." />;
211233
}
212234

213235
if (gameState.playerHealth <= 0) {
214236
return <GameOver score={gameState.score} badges={gameState.badges} onReset={resetGame} />;
215237
}
216238

217-
if (gameState.currentLevel >= questions.length && questions.length > 0) {
239+
if (isQuizComplete) {
218240
return <Completion score={gameState.score} badges={gameState.badges} onReset={resetGame} />;
219241
}
220242

@@ -308,17 +330,17 @@ const Quiz: React.FC = () => {
308330
<Card className="w-full max-w-4xl mt-4">
309331
<CardContent>
310332
<Answers
311-
answers={currentQuestion.answers}
333+
question={currentQuestion}
312334
onAnswer={handleAnswer}
313335
disabled={gameState.answerSelected}
314336
selectedAnswer={selectedAnswer}
315-
correctAnswer={currentQuestion.correctAnswer}
316337
answered={gameState.answerSelected}
317338
/>
318339
{gameState.answerSelected && (
319340
<Result
320-
isCorrect={gameState.isCorrect}
321-
explanation={currentQuestion.explanation}
341+
question={currentQuestion}
342+
selectedAnswer={selectedAnswer}
343+
isLastQuestion={isLastQuestion}
322344
onNext={nextLevel}
323345
/>
324346
)}

src/components/ui/collapsible.tsx

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import * as CollapsiblePrimitive from "@radix-ui/react-collapsible"
1+
import * as CollapsiblePrimitive from '@radix-ui/react-collapsible';
22

3-
const Collapsible = CollapsiblePrimitive.Root
3+
const Collapsible = CollapsiblePrimitive.Root;
44

5-
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger
5+
const CollapsibleTrigger = CollapsiblePrimitive.CollapsibleTrigger;
66

7-
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent
7+
const CollapsibleContent = CollapsiblePrimitive.CollapsibleContent;
88

9-
export { Collapsible, CollapsibleTrigger, CollapsibleContent }
9+
export { Collapsible, CollapsibleTrigger, CollapsibleContent };

src/lib/markdownParser.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export interface ParsedQuestion {
1010
outro: string;
1111
};
1212
answers: string[];
13-
correctAnswer: number;
13+
correctAnswer?: number;
14+
correctAnswers?: number[];
1415
timeLimit?: number;
1516
difficulty?: string;
1617
explanation: string;
@@ -112,6 +113,7 @@ export function parseQuestionContent(content: string): ParsedQuestion {
112113
description: frontMatter.description,
113114
level: frontMatter.level,
114115
correctAnswer: frontMatter.correctAnswer,
116+
correctAnswers: frontMatter.correctAnswers,
115117
timeLimit: frontMatter.timeLimit,
116118
difficulty: frontMatter.difficulty,
117119
...parsedContent,

src/lib/quiz-utils.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import type { ParsedQuestion } from './markdownParser';
2+
3+
export function isCorrectAnswer(question: ParsedQuestion, selectedIndex: number): boolean {
4+
if (question.correctAnswer !== undefined) {
5+
return selectedIndex + 1 === question.correctAnswer;
6+
}
7+
if (question.correctAnswers !== undefined) {
8+
return question.correctAnswers.includes(selectedIndex + 1);
9+
}
10+
throw new Error('Question has no correct answer defined');
11+
}

0 commit comments

Comments
 (0)