konnektoren_core/achievements/
achievement_evaluator.rs

1use super::achievement_definition::{AchievementDefinition, AchievementDefinitions};
2use super::achievement_statistic::*;
3use super::game_statistics::GameStatistics;
4use crate::game::Game;
5use eval::{eval, to_value as eval_to_value};
6
7pub struct AchievementEvaluator {
8    definitions: Vec<AchievementDefinition>,
9}
10impl AchievementEvaluator {
11    pub fn new(yaml_content: &str) -> Result<Self, serde_yaml::Error> {
12        let definitions: AchievementDefinitions = serde_yaml::from_str(yaml_content)?;
13        Ok(AchievementEvaluator {
14            definitions: definitions.achievements,
15        })
16    }
17
18    pub fn evaluate(&self, game: &Game) -> Vec<&AchievementDefinition> {
19        let statistics = GameStatistics::new(game);
20        self.definitions
21            .iter()
22            .filter(|def| self.evaluate_condition(&def.condition, &statistics))
23            .collect()
24    }
25
26    fn evaluate_condition(&self, condition: &str, statistics: &GameStatistics) -> bool {
27        let expression = self.prepare_expression(condition, statistics);
28        let true_value = eval_to_value(true);
29        match eval(&expression) {
30            Ok(value) => value == true_value,
31            Err(_) => false,
32        }
33    }
34
35    fn prepare_expression(&self, condition: &str, statistics: &GameStatistics) -> String {
36        condition
37            .replace(
38                "total_challenges",
39                &statistics.total_challenges().to_string(),
40            )
41            .replace(
42                "average_performance",
43                &statistics.average_performance().to_string(),
44            )
45            .replace("total_xp", &statistics.total_xp().to_string())
46            .replace(
47                "completed_game_paths",
48                &statistics.completed_game_paths().to_string(),
49            )
50            .replace(
51                "perfect_challenges",
52                &statistics.perfect_challenges().to_string(),
53            )
54            .replace(
55                "different_challenge_types_completed",
56                &statistics.different_challenge_types_completed().to_string(),
57            )
58            .replace("&", "&&")
59            .replace("|", "||")
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::challenges::ChallengeHistory;
67    use crate::game::Game;
68
69    const TEST_YAML: &str = r#"
70    achievements:
71      - id: xp_master
72        name: XP Master
73        description: Earn 1000 XP
74        icon: 🏆
75        condition: "total_xp > 1000"
76      - id: challenge_champion
77        name: Challenge Champion
78        description: Complete 50 challenges
79        icon: 🏅
80        condition: "total_challenges >= 50"
81      - id: path_finder
82        name: Path Finder
83        description: Complete 3 game paths
84        icon: 🧭
85        condition: "completed_game_paths >= 3 && perfect_challenges >= 10"
86    "#;
87
88    #[test]
89    fn test_achievement_evaluator() {
90        let evaluator = AchievementEvaluator::new(TEST_YAML).unwrap();
91
92        let mut game = Game::default();
93        game.challenge_history = ChallengeHistory { challenges: vec![] };
94
95        let achieved = evaluator.evaluate(&game);
96
97        assert_eq!(achieved.len(), 0);
98    }
99}