konnektoren_core/commands/
challenge_command.rs1use super::command::CommandTrait;
4use super::command_type::CommandType;
5use crate::challenges::Timed;
6use crate::challenges::error::ChallengeError;
7use crate::challenges::{
8 Challenge, ChallengeInput, ChallengeResult, ChallengeType, ContextItemChoiceAnswers,
9 GapFillAnswer, MultipleChoiceOption, OrderingResult, Solvable, SortTableRow,
10};
11use crate::commands::error::{CommandError, Result};
12use crate::game::GamePath;
13use crate::game::GameState;
14use crate::game::error::GameError;
15
16#[allow(clippy::large_enum_variant)]
18#[derive(Debug, Clone, PartialEq)]
19pub enum ChallengeCommand {
20 Start(Challenge),
21 NextTask,
23 PreviousTask,
25 SolveOption(usize),
27 Finish(Option<ChallengeResult>),
29}
30
31impl CommandTrait for ChallengeCommand {
32 fn execute(&self, state: &mut GameState) -> Result<()> {
42 match self {
43 ChallengeCommand::Start(challenge_type) => Self::start_challenge(state, challenge_type),
44 ChallengeCommand::NextTask => Self::next_task(state),
45 ChallengeCommand::PreviousTask => Self::previous_task(state),
46 ChallengeCommand::SolveOption(option_index) => Self::solve_option(state, *option_index),
47 ChallengeCommand::Finish(result) => Self::finish_challenge(state, result),
48 }
49 }
50
51 fn get_type(&self) -> CommandType {
53 CommandType::Challenge
54 }
55}
56
57impl ChallengeCommand {
58 fn start_challenge(state: &mut GameState, challenge: &Challenge) -> Result<()> {
60 let mut challenge = challenge.clone();
61 challenge.start();
62 state.challenge = challenge;
63 state.current_task_index = 0;
64 Ok(())
65 }
66
67 fn next_task(state: &mut GameState) -> Result<()> {
77 let current_game_path: &GamePath = state
78 .game
79 .game_paths
80 .get(state.current_game_path)
81 .ok_or(CommandError::GameError(GameError::GamePathNotFound))?;
82
83 let challenge_config = ¤t_game_path.challenges[state.current_challenge_index];
84 let max_questions = challenge_config.tasks.len();
85
86 if state.current_task_index >= max_questions - 1 {
87 return Err(CommandError::ChallengeError(ChallengeError::NoMoreTasks));
88 }
89
90 let result_len = state.challenge.challenge_result.len();
92 if result_len <= state.current_task_index {
93 let default_input = match &state.challenge.challenge_type {
95 ChallengeType::MultipleChoice(mc) => {
96 if let Some(first_option) = mc.options.first() {
97 ChallengeInput::MultipleChoice(MultipleChoiceOption {
98 id: first_option.id,
99 name: first_option.name.clone(),
100 })
101 } else {
102 return Err(CommandError::ChallengeError(
103 ChallengeError::InvalidChallengeType,
104 ));
105 }
106 }
107 ChallengeType::ContextualChoice(cc) => {
108 if let Some(first_item) = cc.items.first() {
109 let ids = vec![0; first_item.choices.len()];
111 ChallengeInput::ContextualChoice(ContextItemChoiceAnswers { ids })
112 } else {
113 return Err(CommandError::ChallengeError(
114 ChallengeError::InvalidChallengeType,
115 ));
116 }
117 }
118 ChallengeType::GapFill(_) => ChallengeInput::GapFill(GapFillAnswer {
119 question_index: state.current_task_index,
120 answers: vec![],
121 }),
122 ChallengeType::SortTable(_) => ChallengeInput::SortTable(SortTableRow::default()),
123 ChallengeType::Ordering(_) => ChallengeInput::Ordering(OrderingResult::default()),
124 _ => {
125 state.current_task_index += 1;
127 return Ok(());
128 }
129 };
130
131 let _ = state.challenge.challenge_result.add_input(default_input);
132 }
133
134 state.current_task_index += 1;
135 Ok(())
136 }
137
138 fn previous_task(state: &mut GameState) -> Result<()> {
148 if state.current_task_index == 0 {
149 return Err(CommandError::ChallengeError(
150 ChallengeError::NoPreviousTasks,
151 ));
152 }
153 state.current_task_index -= 1;
154 Ok(())
155 }
156
157 fn solve_option(state: &mut GameState, option_index: usize) -> Result<()> {
168 let challenge_input = match state.challenge.challenge_type {
169 ChallengeType::MultipleChoice(ref dataset) => {
170 let option =
171 dataset
172 .options
173 .get(option_index)
174 .ok_or(CommandError::ChallengeError(
175 ChallengeError::InvalidOptionId(option_index),
176 ))?;
177
178 ChallengeInput::MultipleChoice(MultipleChoiceOption {
179 id: option.id,
180 name: option.name.clone(),
181 })
182 }
183 _ => {
184 return Err(CommandError::ChallengeError(
185 ChallengeError::InvalidChallengeType,
186 ));
187 }
188 };
189
190 state
191 .challenge
192 .solve(challenge_input, state.current_task_index)
193 .map_err(CommandError::ChallengeError)?;
194
195 let _ = Self::next_task(state);
197
198 Ok(())
199 }
200
201 fn finish_challenge(
203 state: &mut GameState,
204 custom_result: &Option<ChallengeResult>,
205 ) -> Result<()> {
206 state.challenge.update_end_time();
207 if let Some(result) = custom_result {
209 state.challenge.challenge_result = result.clone();
210 }
211 Ok(())
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219 use crate::game::GameState;
220
221 #[test]
222 fn test_solve_option() {
223 let mut state = GameState::default();
224 state.current_game_path = 0;
225 state.current_challenge_index = 0;
226 state.current_task_index = 0;
227
228 let result = ChallengeCommand::solve_option(&mut state, 0);
229 assert!(result.is_ok());
230 assert_eq!(state.current_task_index, 1);
231 }
232
233 #[test]
234 fn test_solve_option_invalid() {
235 let mut state = GameState::default();
236 state.current_game_path = 0;
237 state.current_challenge_index = 0;
238 state.current_task_index = 0;
239
240 let result = ChallengeCommand::solve_option(&mut state, 999);
242 assert!(result.is_err());
243
244 if let Err(error) = result {
246 match error {
247 CommandError::ChallengeError(ChallengeError::InvalidOptionId(999)) => {}
248 _ => panic!("Unexpected error type: {:?}", error),
249 }
250 }
251 }
252
253 #[test]
254 fn test_next_task() {
255 let mut state = GameState::default();
256 state.current_game_path = 0;
257 state.current_challenge_index = 0;
258 state.current_task_index = 0;
259
260 let result = ChallengeCommand::next_task(&mut state);
261 assert!(result.is_ok());
262 assert_eq!(state.current_task_index, 1);
263 }
264
265 #[test]
266 fn test_next_task_no_more() {
267 let mut state = GameState::default();
268 state.current_game_path = 0;
269 state.current_challenge_index = 0;
270
271 let current_game_path = &state.game.game_paths[state.current_game_path];
273 let challenge_config = ¤t_game_path.challenges[state.current_challenge_index];
274 let max_tasks = challenge_config.tasks.len();
275 state.current_task_index = max_tasks - 1;
276
277 let result = ChallengeCommand::next_task(&mut state);
278 assert!(result.is_err());
279
280 if let Err(error) = result {
282 match error {
283 CommandError::ChallengeError(ChallengeError::NoMoreTasks) => {}
284 _ => panic!("Unexpected error type: {:?}", error),
285 }
286 }
287 }
288
289 #[test]
290 fn test_previous_task() {
291 let mut state = GameState::default();
292 state.current_game_path = 0;
293 state.current_challenge_index = 0;
294 state.current_task_index = 1;
295
296 let result = ChallengeCommand::previous_task(&mut state);
297 assert!(result.is_ok());
298 assert_eq!(state.current_task_index, 0);
299 }
300
301 #[test]
302 fn test_previous_task_no_more() {
303 let mut state = GameState::default();
304 state.current_game_path = 0;
305 state.current_challenge_index = 0;
306 state.current_task_index = 0;
307
308 let result = ChallengeCommand::previous_task(&mut state);
309 assert!(result.is_err());
310
311 if let Err(error) = result {
313 match error {
314 CommandError::ChallengeError(ChallengeError::NoPreviousTasks) => {}
315 _ => panic!("Unexpected error type: {:?}", error),
316 }
317 }
318 }
319
320 #[test]
321 fn test_finish_challenge() {
322 let mut state = GameState::default();
323 state.current_game_path = 0;
324 state.current_challenge_index = 0;
325 state.current_task_index = 1;
326
327 let result = ChallengeCommand::finish_challenge(&mut state, &None);
328 assert!(result.is_ok());
329 }
330
331 #[test]
332 fn test_execute() {
333 let mut state = GameState::default();
334 state.current_game_path = 0;
335 state.current_challenge_index = 0;
336 state.current_task_index = 0;
337
338 let result = ChallengeCommand::SolveOption(0).execute(&mut state);
339 assert!(result.is_ok());
340 assert_eq!(state.current_task_index, 1);
341 }
342
343 #[test]
344 fn test_invalid_challenge_type() {
345 let mut state = GameState::default();
347
348 state.challenge.challenge_type = ChallengeType::Informative(Default::default());
351
352 let result = ChallengeCommand::solve_option(&mut state, 0);
353 assert!(result.is_err());
354
355 if let Err(error) = result {
357 match error {
358 CommandError::ChallengeError(ChallengeError::InvalidChallengeType) => {}
359 _ => panic!("Unexpected error type: {:?}", error),
360 }
361 }
362 }
363
364 #[test]
365 fn test_game_path_not_found() {
366 let mut state = GameState::default();
367 state.current_game_path = 999; let result = ChallengeCommand::next_task(&mut state);
370 assert!(result.is_err());
371
372 if let Err(error) = result {
374 match error {
375 CommandError::GameError(GameError::GamePathNotFound) => {}
376 _ => panic!("Unexpected error type: {:?}", error),
377 }
378 }
379 }
380}