Skip to main content

lean_ctx/core/buddy/
evolution.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
4pub enum EvolutionStage {
5    #[default]
6    Egg,
7    Baby,
8    Teen,
9    Adult,
10    Mythic,
11}
12
13impl EvolutionStage {
14    pub fn from_level(level: u32) -> Self {
15        match level {
16            0..=4 => Self::Egg,
17            5..=14 => Self::Baby,
18            15..=34 => Self::Teen,
19            35..=64 => Self::Adult,
20            _ => Self::Mythic,
21        }
22    }
23
24    pub fn label(&self) -> &'static str {
25        match self {
26            Self::Egg => "Egg",
27            Self::Baby => "Baby",
28            Self::Teen => "Teen",
29            Self::Adult => "Adult",
30            Self::Mythic => "Mythic",
31        }
32    }
33
34    pub fn icon(&self) -> &'static str {
35        match self {
36            Self::Egg => "🥚",
37            Self::Baby => "🐣",
38            Self::Teen => "🌱",
39            Self::Adult => "⚔️",
40            Self::Mythic => "✨",
41        }
42    }
43
44    pub fn sprite_height(&self) -> usize {
45        match self {
46            Self::Egg => 3,
47            Self::Baby => 8,
48            Self::Teen => 10,
49            Self::Adult => 12,
50            Self::Mythic => 15,
51        }
52    }
53
54    /// The next stage, or `None` if already Mythic.
55    pub fn next(&self) -> Option<Self> {
56        match self {
57            Self::Egg => Some(Self::Baby),
58            Self::Baby => Some(Self::Teen),
59            Self::Teen => Some(Self::Adult),
60            Self::Adult => Some(Self::Mythic),
61            Self::Mythic => None,
62        }
63    }
64
65    /// Level required to reach this stage.
66    pub fn min_level(&self) -> u32 {
67        match self {
68            Self::Egg => 0,
69            Self::Baby => 5,
70            Self::Teen => 15,
71            Self::Adult => 35,
72            Self::Mythic => 65,
73        }
74    }
75
76    /// Progress (0.0..1.0) toward the next evolution within the current stage.
77    pub fn progress(&self, level: u32) -> f64 {
78        let Some(next) = self.next() else {
79            return 1.0;
80        };
81        let range = next.min_level() - self.min_level();
82        let within = level.saturating_sub(self.min_level());
83        (within as f64 / range as f64).min(1.0)
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn stage_from_level() {
93        assert_eq!(EvolutionStage::from_level(0), EvolutionStage::Egg);
94        assert_eq!(EvolutionStage::from_level(4), EvolutionStage::Egg);
95        assert_eq!(EvolutionStage::from_level(5), EvolutionStage::Baby);
96        assert_eq!(EvolutionStage::from_level(14), EvolutionStage::Baby);
97        assert_eq!(EvolutionStage::from_level(15), EvolutionStage::Teen);
98        assert_eq!(EvolutionStage::from_level(34), EvolutionStage::Teen);
99        assert_eq!(EvolutionStage::from_level(35), EvolutionStage::Adult);
100        assert_eq!(EvolutionStage::from_level(64), EvolutionStage::Adult);
101        assert_eq!(EvolutionStage::from_level(65), EvolutionStage::Mythic);
102        assert_eq!(EvolutionStage::from_level(99), EvolutionStage::Mythic);
103    }
104
105    #[test]
106    fn progress_within_stage() {
107        let stage = EvolutionStage::Egg;
108        assert!((stage.progress(0) - 0.0).abs() < 0.01);
109        assert!((stage.progress(4) - 0.8).abs() < 0.01);
110        assert_eq!(EvolutionStage::Mythic.progress(99), 1.0);
111    }
112
113    #[test]
114    fn next_chain() {
115        let mut s = EvolutionStage::Egg;
116        let chain: Vec<_> = std::iter::from_fn(|| {
117            let next = s.next()?;
118            s = next;
119            Some(next)
120        })
121        .collect();
122        assert_eq!(chain.len(), 4);
123        assert_eq!(chain[3], EvolutionStage::Mythic);
124    }
125}