1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
#![allow(clippy::new_without_default)]

//! This module contains the [`Eevee`] and [`EvolvedEevee`] type, as well as its
//! method implementations.
//!
//! In this module, we'll be implementing a variable-type pokemon that represents
//! some of the Eevee evolution line:
//!  - Eevee
//!      - Vaporeon
//!      - Flareon
//!      - Leafeon
//!
//! To model the evolution line, we use a type system that represents the possible
//! states that the Eevee can be in.
//!
//! We have an [`Eevee`] type that contains stats about the
//! Eevee, and several methods that can retrieve and modify these stats. We also have an
//! [`EvolvedEevee`] type that can be one of Vaporeon, Flareon, and Leafeon.
//!
//! An [`Eevee`] can [`evolve`](Eevee::evolve) into an [`EvolvedEevee`],
//! and an [`EvolvedEevee`] can [`devolve`](EvolvedEevee::devolve)
//! back into an [`Eevee`].
//!
//! See the documentation below for each of these types to learn more!

/// This type represents a basic [`Eevee`] pokemon. It has a level, as well as health,
/// attack, and defense stats.
///
/// Something to note is that while it is possible to implement getters and setters for struct
/// fields as if they were objects in OOP-paradigm languages, it is generally unnecessary for Rust.
/// It can even get you into trouble with the borrow checker when dealing
/// with fields that are references (we may talk about this when we get to _lifetimes_ in week 7).
///
/// Marking a field as `pub`, coupled with the borrow checker, will give you very similar
/// semantics as to normal getters and setters.
///
/// There are, of course, places where you _do_ want these.
/// And when we talk about traits in week 5,
/// we will _need_ to have them if we want to share behavior among types.
#[derive(Debug, PartialEq, Eq)]
pub struct Eevee {
    /// For this part of the homework,
    /// [`level`](Eevee::level) doesn't actually represent anything important.
    pub level: u8,
    pub health: u16,
    pub attack: u16,
    pub defense: u16,
}

/// These stones are used to evolve an [`Eevee`] into an [`EvolvedEevee`].
///
/// Don't worry too much about the `#[non_exhaustive]` attribute.
/// It's basically a way of saying that
/// there could be more variants of [`ElementalStone`] added in the future.
#[non_exhaustive]
pub enum ElementalStone {
    /// This Water Stone turns an [`Eevee`] into an [`Vaporeon`](EvolvedEevee::Vaporeon)
    HydroStone,
    /// This Fire Stone turns an [`Eevee`] into an [`Flareon`](EvolvedEevee::Flareon)
    PyroStone,
    /// This Electric Stone turns an [`Eevee`] into an [`Leafeon`](EvolvedEevee::Leafeon)
    MossyStone,
    /// Placeholder for future stones we might want to add for new evolutions!
    DullRock,
}

/// This type represent an evolved Eevee in the form of either Vaporeon, Flareon, or Leafeon.
///
/// An [`EvolvedEevee`] contains an inner [`Eevee`] as well as a secondary attribute value.
/// This attribute value changes one of the inner [`Eevee`]'s base stats depending on which
/// of the 3 types the [`EvolvedEevee`] is.
#[derive(Debug, PartialEq, Eq)]
pub enum EvolvedEevee {
    /// The secondary attribute for `Vaporeon` is added to the base health.
    Vaporeon(Eevee, u16),
    /// The secondary attribute for `Flareon` is added to the base attack.
    Flareon(Eevee, u16),
    /// The secondary attribute for `Leafeon` is added to the base defense.
    Leafeon(Eevee, u16),
}

impl Eevee {
    /// Creates an Eevee with the following base stats:
    /// - `level: 0`
    /// - `health: 100`
    /// - `attack: 55`
    /// - `defense: 20`
    ///
    /// _Note: We 0-level because we're programmers_ 😎
    ///
    /// ```
    /// # use pokelab::pokemon::eevee::*;
    /// #
    /// let new_eevee = Eevee::new();
    /// assert_eq!(new_eevee.level, 0);
    /// assert_eq!(new_eevee.health, 100);
    /// assert_eq!(new_eevee.attack, 55);
    /// assert_eq!(new_eevee.defense, 20);
    /// ```
    pub fn new() -> Self {
        todo!()
    }

    /// Deals `damage` amount of damage to the Eevee's health.
    ///
    /// ```
    /// # use pokelab::pokemon::eevee::*;
    /// #
    /// let mut new_eevee = Eevee::new();
    /// assert_eq!(new_eevee.health, 100);
    /// assert_eq!(new_eevee.defense, 20);
    ///
    /// new_eevee.take_damage(10);
    /// assert_eq!(new_eevee.health, 100); // Not enough damage to overcome defense
    ///
    /// new_eevee.take_damage(30);
    /// assert_eq!(new_eevee.health, 90); // 30 - 20 = 10 damage taken
    /// ```
    ///
    /// This function should panic with the message `"Eevee fainted!"` if it takes more damage
    /// than it has health. In other words, it should faint when it reaches 0 health.
    pub fn take_damage(&mut self, damage: u16) {
        todo!()
    }

    /// Given an Elemental Stone, evolve the Eevee into an [`EvolvedEevee`].
    ///
    /// If given a stone that an Eevee cannot use to evolve, this function should
    /// panic with the message `"Encountered a weird rock..."`.
    ///
    /// ```
    /// # use pokelab::pokemon::eevee::*;
    /// #
    /// let mut new_eevee = Eevee::new();
    ///
    /// let vaporeon = new_eevee.evolve(ElementalStone::HydroStone);
    /// assert!(matches!(vaporeon, EvolvedEevee::Vaporeon(_, _)));
    /// ```
    pub fn evolve(self, evolution: ElementalStone) -> EvolvedEevee {
        todo!()
    }
}

/// There's a lot of boiler plate code here that we'll implement for you.
/// All you need to do is implement
/// [`take_damage`](EvolvedEevee::take_damage) and [`devolve`](EvolvedEevee::devolve).
///
/// In all honesty, if you had to actually implement this logic, you would probably have each
/// [`EvolvedEevee`] variant be its own standalone type,
/// each implementing something called a _trait_.
/// However, we haven't talked about traits yet, so stay tuned till week 5!
impl EvolvedEevee {
    pub fn get_level(&self) -> u8 {
        match self {
            EvolvedEevee::Vaporeon(base, _) => base.level,
            EvolvedEevee::Flareon(base, _) => base.level,
            EvolvedEevee::Leafeon(base, _) => base.level,
        }
    }

    pub fn get_health(&self) -> u16 {
        match self {
            EvolvedEevee::Vaporeon(base, h) => base.health + h,
            EvolvedEevee::Flareon(base, _) => base.health,
            EvolvedEevee::Leafeon(base, _) => base.health,
        }
    }

    pub fn get_attack(&self) -> u16 {
        match self {
            EvolvedEevee::Vaporeon(base, _) => base.attack,
            EvolvedEevee::Flareon(base, a) => base.attack + a,
            EvolvedEevee::Leafeon(base, _) => base.attack,
        }
    }

    pub fn get_defense(&self) -> u16 {
        match self {
            EvolvedEevee::Vaporeon(base, _) => base.defense,
            EvolvedEevee::Flareon(base, _) => base.defense,
            EvolvedEevee::Leafeon(base, d) => base.defense + d,
        }
    }

    pub fn set_secondary_attribute(&mut self, extra: u16) {
        match self {
            EvolvedEevee::Vaporeon(_, attr) => *attr = extra,
            EvolvedEevee::Flareon(_, attr) => *attr = extra,
            EvolvedEevee::Leafeon(_, attr) => *attr = extra,
        }
    }

    /// Deals `damage` amount of damage to the [`EvolvedEevee`]'s health.
    ///
    /// This is similar to [`Eevee::take_damage`],
    /// but the logic is slightly different for [`Vaporeon`](EvolvedEevee::Vaporeon)
    /// and [`Flareon`](EvolvedEevee::Flareon), since they have
    /// extra health and extra defense, respectively.
    ///
    /// For Vaporeon, you will want to apply the damage to the extra health,
    /// until the extra health runs out. For Leafeon, you apply the extra defense
    /// on every [`take_damage`](EvolvedEevee::take_damage) call.
    ///
    /// It's also fine to just panic with the same message, `"Eevee fainted!"`.
    ///
    /// ```
    /// # use pokelab::pokemon::eevee::*;
    /// #
    /// let mut flareon = Eevee::new().evolve(ElementalStone::PyroStone);
    /// flareon.take_damage(40);
    /// assert_eq!(flareon.get_health(), 80);
    ///
    /// let mut vaporeon = Eevee::new().evolve(ElementalStone::HydroStone);
    /// vaporeon.set_secondary_attribute(20); // Adds 20 extra health
    /// vaporeon.take_damage(40);
    /// assert_eq!(vaporeon.get_health(), 100);
    ///
    /// let mut leafeon = Eevee::new().evolve(ElementalStone::MossyStone);
    /// leafeon.set_secondary_attribute(5); // Adds 5 extra defense
    /// for _ in 0..100 {
    ///     leafeon.take_damage(25);
    /// }
    /// assert_eq!(leafeon.get_health(), 100);
    /// ```
    pub fn take_damage(&mut self, damage: u16) {
        todo!()
    }

    /// Devolves an [`EvolvedEevee`] into an [`Eevee`].
    ///
    /// Note that base stats like health should not change even after devolving.
    ///
    /// ```
    /// # use pokelab::pokemon::eevee::*;
    /// #
    /// let leafeon = Eevee::new().evolve(ElementalStone::MossyStone);
    /// assert!(matches!(leafeon, EvolvedEevee::Leafeon(_, _)));
    ///
    /// let back_to_eevee: Eevee = leafeon.devolve();
    /// ```
    pub fn devolve(self) -> Eevee {
        todo!()
    }
}