konnektoren_core/commands/
game_command.rs

1//! This module contains the implementation of game-level commands.
2
3use super::command::CommandTrait;
4use super::command_type::CommandType;
5use crate::challenges::Timed;
6use crate::commands::error::{CommandError, Result};
7use crate::game::GamePath;
8use crate::game::GameState;
9use crate::game::error::GameError;
10
11/// Represents game-level commands that can be executed on the game state.
12#[derive(Debug, Clone, PartialEq)]
13pub enum GameCommand {
14    /// Command to move to the next challenge.
15    NextChallenge,
16    /// Command to move to the previous challenge.
17    PreviousChallenge,
18}
19
20impl CommandTrait for GameCommand {
21    /// Executes the game command on the given game state.
22    ///
23    /// # Arguments
24    ///
25    /// * `state` - A mutable reference to the current game state.
26    ///
27    /// # Returns
28    ///
29    /// A `Result` indicating success or containing an error if the command execution failed.
30    fn execute(&self, state: &mut GameState) -> Result<()> {
31        match self {
32            GameCommand::NextChallenge => Self::next_challenge(state),
33            GameCommand::PreviousChallenge => Self::previous_challenge(state),
34        }
35    }
36
37    /// Gets the type of the command.
38    fn get_type(&self) -> CommandType {
39        CommandType::Game
40    }
41}
42
43impl GameCommand {
44    /// Moves the game state to the next challenge.
45    ///
46    /// # Arguments
47    ///
48    /// * `state` - A mutable reference to the current game state.
49    ///
50    /// # Returns
51    ///
52    /// A `Result` indicating success or containing an error if there are no more challenges.
53    pub fn next_challenge(state: &mut GameState) -> Result<()> {
54        let current_game_path: &GamePath = state
55            .game
56            .game_paths
57            .get(state.current_game_path)
58            .ok_or(CommandError::GameError(GameError::GamePathNotFound))?;
59
60        if state.current_challenge_index + 1 >= current_game_path.challenge_ids().len() {
61            return Err(CommandError::GameError(GameError::InvalidGameState(
62                "No more challenges".to_string(),
63            )));
64        }
65        state.current_challenge_index += 1;
66
67        let challenge_config = &current_game_path.challenges[state.current_challenge_index];
68        state.challenge = state
69            .game
70            .create_challenge(&challenge_config.id)
71            .map_err(CommandError::GameError)?;
72
73        state.challenge.start();
74        state.current_task_index = 0;
75
76        Ok(())
77    }
78
79    /// Moves the game state to the previous challenge.
80    ///
81    /// # Arguments
82    ///
83    /// * `state` - A mutable reference to the current game state.
84    ///
85    /// # Returns
86    ///
87    /// A `Result` indicating success or containing an error if there are no previous challenges.
88    pub fn previous_challenge(state: &mut GameState) -> Result<()> {
89        if state.current_challenge_index == 0 {
90            return Err(CommandError::GameError(GameError::InvalidGameState(
91                "No previous challenges".to_string(),
92            )));
93        }
94
95        state.current_challenge_index -= 1;
96
97        let current_game_path: &GamePath = state
98            .game
99            .game_paths
100            .get(state.current_game_path)
101            .ok_or(CommandError::GameError(GameError::GamePathNotFound))?;
102
103        let challenge_config = &current_game_path.challenges[state.current_challenge_index];
104
105        match state.game.create_challenge(&challenge_config.id) {
106            Ok(challenge) => {
107                state.challenge = challenge;
108                state.challenge.start();
109                state.current_task_index = 0;
110                Ok(())
111            }
112            Err(err) => Err(CommandError::GameError(err)),
113        }
114    }
115}
116
117#[cfg(test)]
118mod tests {
119    use super::*;
120    use crate::game::GameState;
121
122    #[test]
123    fn next_challenge() {
124        let mut state = GameState::default();
125        let command = GameCommand::NextChallenge;
126        let result = command.execute(&mut state);
127        assert!(result.is_ok());
128        assert_eq!(state.current_challenge_index, 1);
129    }
130
131    #[test]
132    fn previous_challenge() {
133        let mut state = GameState::default();
134        let command = GameCommand::NextChallenge;
135        let result = command.execute(&mut state);
136        assert!(result.is_ok());
137        assert_eq!(state.current_challenge_index, 1);
138        let command = GameCommand::PreviousChallenge;
139        let result = command.execute(&mut state);
140        assert!(result.is_ok());
141        assert_eq!(state.current_challenge_index, 0);
142    }
143
144    #[test]
145    fn next_challenge_no_more() {
146        let mut state = GameState::default();
147        let command = GameCommand::NextChallenge;
148        command.execute(&mut state).unwrap();
149        command.execute(&mut state).unwrap();
150        command.execute(&mut state).unwrap();
151        command.execute(&mut state).unwrap();
152        command.execute(&mut state).unwrap();
153        command.execute(&mut state).unwrap();
154        command.execute(&mut state).unwrap();
155        assert_eq!(state.current_challenge_index, 7);
156        let result = command.execute(&mut state);
157        assert!(result.is_err());
158
159        // Check the error type
160        if let Err(error) = result {
161            match error {
162                CommandError::GameError(GameError::InvalidGameState(msg)) => {
163                    assert_eq!(msg, "No more challenges");
164                }
165                _ => panic!("Unexpected error type: {:?}", error),
166            }
167        }
168    }
169
170    #[test]
171    fn previous_challenge_no_more() {
172        let mut state = GameState::default();
173        let command = GameCommand::PreviousChallenge;
174        let result = command.execute(&mut state);
175        assert!(result.is_err());
176
177        // Check the error type
178        if let Err(error) = result {
179            match error {
180                CommandError::GameError(GameError::InvalidGameState(msg)) => {
181                    assert_eq!(msg, "No previous challenges");
182                }
183                _ => panic!("Unexpected error type: {:?}", error),
184            }
185        }
186    }
187
188    #[test]
189    fn test_game_path_not_found() {
190        let mut state = GameState::default();
191        state.current_game_path = 999; // Invalid index
192
193        let command = GameCommand::NextChallenge;
194        let result = command.execute(&mut state);
195        assert!(result.is_err());
196
197        // Check the error type
198        if let Err(error) = result {
199            match error {
200                CommandError::GameError(GameError::GamePathNotFound) => {}
201                _ => panic!("Unexpected error type: {:?}", error),
202            }
203        }
204    }
205}