1use crate::{
2 AnswerResult, Category, CategoryScore, Difficulty, DifficultyScore, Question, QuizConfig,
3 QuizScore,
4};
5
6#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
8pub struct QuizSession {
9 pub questions: Vec<Question>,
10 pub answers: Vec<Option<u8>>,
11 pub current_index: usize,
12}
13
14impl QuizSession {
15 pub fn new(config: QuizConfig) -> Self {
17 let all = crate::questions::all_questions();
18
19 let mut filtered: Vec<Question> = all
20 .into_iter()
21 .filter(|q| {
22 if !config.categories.is_empty() && !config.categories.contains(&q.category) {
23 return false;
24 }
25 if let Some(ref diff) = config.difficulty {
26 if q.difficulty != *diff {
27 return false;
28 }
29 }
30 true
31 })
32 .collect();
33
34 if config.shuffle {
35 let mut rng = config.seed.unwrap_or(0x517cc1b727220a95);
37 for i in (1..filtered.len()).rev() {
38 rng ^= rng << 13;
39 rng ^= rng >> 7;
40 rng ^= rng << 17;
41 let j = (rng as usize) % (i + 1);
42 filtered.swap(i, j);
43 }
44 }
45
46 if config.num_questions > 0 {
47 filtered.truncate(config.num_questions as usize);
48 }
49
50 let len = filtered.len();
51 Self {
52 questions: filtered,
53 answers: vec![None; len],
54 current_index: 0,
55 }
56 }
57
58 pub fn current_question(&self) -> Option<Question> {
60 self.questions.get(self.current_index).cloned()
61 }
62
63 pub fn answer(&mut self, choice: u8) -> AnswerResult {
65 let q = &self.questions[self.current_index];
66 let is_correct = choice == q.correct_index;
67 let result = AnswerResult {
68 is_correct,
69 correct_index: q.correct_index,
70 explanation: q.explanation.clone(),
71 reference: q.reference.clone(),
72 };
73 self.answers[self.current_index] = Some(choice);
74 self.current_index += 1;
75 result
76 }
77
78 pub fn skip(&mut self) -> bool {
80 if self.is_finished() {
81 return false;
82 }
83 self.current_index += 1;
85 true
86 }
87
88 pub fn is_finished(&self) -> bool {
90 self.current_index >= self.questions.len()
91 }
92
93 pub fn progress(&self) -> (usize, usize) {
95 (self.current_index, self.questions.len())
96 }
97
98 pub fn score(&self) -> QuizScore {
100 let mut correct = 0u32;
101 let mut wrong = 0u32;
102 let mut skipped = 0u32;
103
104 let mut cat_correct: std::collections::HashMap<Category, u32> =
106 std::collections::HashMap::new();
107 let mut cat_total: std::collections::HashMap<Category, u32> =
108 std::collections::HashMap::new();
109
110 let mut diff_correct: std::collections::HashMap<Difficulty, u32> =
112 std::collections::HashMap::new();
113 let mut diff_total: std::collections::HashMap<Difficulty, u32> =
114 std::collections::HashMap::new();
115
116 for (i, q) in self.questions.iter().enumerate() {
117 if i >= self.current_index {
118 break;
119 }
120 *cat_total.entry(q.category).or_default() += 1;
121 *diff_total.entry(q.difficulty).or_default() += 1;
122
123 match self.answers[i] {
124 Some(choice) => {
125 if choice == q.correct_index {
126 correct += 1;
127 *cat_correct.entry(q.category).or_default() += 1;
128 *diff_correct.entry(q.difficulty).or_default() += 1;
129 } else {
130 wrong += 1;
131 }
132 }
133 None => {
134 skipped += 1;
135 }
136 }
137 }
138
139 let by_category: Vec<CategoryScore> = cat_total
140 .into_iter()
141 .map(|(cat, total)| CategoryScore {
142 correct: *cat_correct.get(&cat).unwrap_or(&0),
143 category: cat,
144 total,
145 })
146 .collect();
147
148 let by_difficulty: Vec<DifficultyScore> = diff_total
149 .into_iter()
150 .map(|(diff, total)| DifficultyScore {
151 correct: *diff_correct.get(&diff).unwrap_or(&0),
152 difficulty: diff,
153 total,
154 })
155 .collect();
156
157 QuizScore {
158 correct,
159 wrong,
160 skipped,
161 total: self.questions.len() as u32,
162 by_category,
163 by_difficulty,
164 }
165 }
166}