fbsim_core/
team.rs

1#![doc = include_str!("../docs/team.md")]
2pub mod coach;
3pub mod defense;
4pub mod offense;
5
6#[cfg(feature = "rocket_okapi")]
7use rocket_okapi::okapi::schemars;
8#[cfg(feature = "rocket_okapi")]
9use rocket_okapi::okapi::schemars::JsonSchema;
10use serde::{Serialize, Deserialize, Deserializer};
11
12use crate::game::play::PlaySimulatable;
13use crate::game::score::ScoreSimulatable;
14use crate::team::coach::FootballTeamCoach;
15use crate::team::defense::FootballTeamDefense;
16use crate::team::offense::FootballTeamOffense;
17
18pub const DEFAULT_TEAM_NAME: &str = "Null Island Defaults";
19pub const DEFAULT_TEAM_SHORT_NAME: &str = "NULL";
20
21/// # `FootballTeamRaw` struct
22///
23/// A `FootballTeamRaw` is a `FootballTeam` before its properties have been
24/// validated
25#[cfg_attr(feature = "rocket_okapi", derive(JsonSchema))]
26#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Default, Serialize, Deserialize)]
27pub struct FootballTeamRaw {
28    name: String,
29    short_name: String,
30    coach: FootballTeamCoach,
31    defense: FootballTeamDefense,
32    offense: FootballTeamOffense
33}
34
35impl FootballTeamRaw {
36    pub fn validate(&self) -> Result<(), String> {
37        // Ensure the team name is no longer than 64 characters
38        if self.name.len() > 64 {
39            return Err(
40                format!(
41                    "Team name is longer than 64 characters: {}",
42                    self.name
43                )
44            )
45        }
46
47        // Ensure the team acronym is no longer than 4 characters
48        if self.short_name.len() > 4 {
49            return Err(
50                format!(
51                    "Team short name is longer than 4 characters: {}",
52                    self.short_name
53                )
54            )
55        }
56        Ok(())
57    }
58}
59
60/// # `FootballTeam` struct
61///
62/// A `FootballTeam` represents a football team
63#[cfg_attr(feature = "rocket_okapi", derive(JsonSchema))]
64#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Debug, Default, Serialize)]
65pub struct FootballTeam {
66    name: String,
67    short_name: String,
68    coach: FootballTeamCoach,
69    defense: FootballTeamDefense,
70    offense: FootballTeamOffense
71}
72
73impl TryFrom<FootballTeamRaw> for FootballTeam {
74    type Error = String;
75
76    fn try_from(item: FootballTeamRaw) -> Result<Self, Self::Error> {
77        // Validate the raw team
78        match item.validate() {
79            Ok(()) => (),
80            Err(error) => return Err(error),
81        };
82
83        // If valid, then convert
84        Ok(
85            FootballTeam{
86                name: item.name,
87                short_name: item.short_name,
88                coach: item.coach,
89                offense: item.offense,
90                defense: item.defense
91            }
92        )
93    }
94}
95
96impl<'de> Deserialize<'de> for FootballTeam {
97    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
98    where
99        D: Deserializer<'de>,
100    {
101        // Only deserialize if the conversion from raw succeeds
102        let raw = FootballTeamRaw::deserialize(deserializer)?;
103        FootballTeam::try_from(raw).map_err(serde::de::Error::custom)
104    }
105}
106
107impl PlaySimulatable for FootballTeam {
108    /// Borrow the team's coach
109    ///
110    /// ### Example
111    /// ```
112    /// use fbsim_core::game::play::PlaySimulatable;
113    /// use fbsim_core::team::FootballTeam;
114    ///
115    /// let my_team = FootballTeam::new();
116    /// let my_coach = my_team.coach();
117    /// ```
118    fn coach(&self) -> &FootballTeamCoach {
119        &self.coach
120    }
121
122    /// Borrow the team's offense
123    ///
124    /// ### Example
125    /// ```
126    /// use fbsim_core::game::play::PlaySimulatable;
127    /// use fbsim_core::team::FootballTeam;
128    ///
129    /// let my_team = FootballTeam::new();
130    /// let my_offense = my_team.offense();
131    /// ```
132    fn offense(&self) -> &FootballTeamOffense {
133        &self.offense
134    }
135
136    /// Borrow the team's defense
137    ///
138    /// ### Example
139    /// ```
140    /// use fbsim_core::game::play::PlaySimulatable;
141    /// use fbsim_core::team::FootballTeam;
142    ///
143    /// let my_team = FootballTeam::new();
144    /// let my_defense = my_team.defense();
145    /// ```
146    fn defense(&self) -> &FootballTeamDefense {
147        &self.defense
148    }
149}
150
151impl ScoreSimulatable for FootballTeam {
152    /// Get the football team's name
153    ///
154    /// ### Example
155    /// ```
156    /// use fbsim_core::team::FootballTeam;
157    ///
158    /// let my_team = FootballTeam::new();
159    /// let name = my_team.name();
160    /// ```
161    fn name(&self) -> &str {
162        &self.name
163    }
164
165    /// Get the overall of the defense
166    ///
167    /// ### Example
168    /// ```
169    /// use fbsim_core::game::score::ScoreSimulatable;
170    /// use fbsim_core::team::FootballTeam;
171    ///
172    /// let my_team = FootballTeam::from_overalls("My Team", "TEAM", 25, 75).unwrap();
173    /// let defense_overall = my_team.defense_overall();
174    /// assert!(defense_overall == 75);
175    /// ```
176    fn defense_overall(&self) -> u32 {
177        self.defense.overall()
178    }
179
180    /// Get the overall of the offense
181    ///
182    /// ### Example
183    /// ```
184    /// use fbsim_core::game::score::ScoreSimulatable;
185    /// use fbsim_core::team::FootballTeam;
186    ///
187    /// let my_team = FootballTeam::from_overalls("My Team", "TEAM", 25, 75).unwrap();
188    /// let offense_overall = my_team.offense_overall();
189    /// assert!(offense_overall == 25);
190    /// ```
191    fn offense_overall(&self) -> u32 {
192        self.offense.overall()
193    }
194}
195
196impl FootballTeam {
197    /// Constructor for the `FootballTeam` struct in which each
198    /// overall is defaulted to 50_i32, and the name is defaulted
199    ///
200    /// ### Example
201    /// ```
202    /// use fbsim_core::team::FootballTeam;
203    ///
204    /// let my_team = FootballTeam::new();
205    /// ```
206    pub fn new() -> FootballTeam {
207        FootballTeam{
208            name: String::from(DEFAULT_TEAM_NAME),
209            short_name: String::from(DEFAULT_TEAM_SHORT_NAME),
210            coach: FootballTeamCoach::new(),
211            offense: FootballTeamOffense::new(),
212            defense: FootballTeamDefense::new()
213        }
214    }
215
216    /// Constructor for the `FootballTeam` struct in which an offense and defense
217    /// are constructed given their overalls
218    ///
219    /// ### Example
220    /// ```
221    /// use fbsim_core::team::FootballTeam;
222    ///
223    /// let my_team = FootballTeam::from_overalls("My Team", "TEAM", 25, 75);
224    /// ```
225    pub fn from_overalls(name: &str, short_name: &str, offense_overall: u32, defense_overall: u32) -> Result<FootballTeam, String> {
226        let offense = FootballTeamOffense::from_overall(offense_overall)?;
227        let defense = FootballTeamDefense::from_overall(defense_overall)?;
228        Ok(
229            FootballTeam{
230                name: String::from(name),
231                short_name: String::from(short_name),
232                coach: FootballTeamCoach::new(),
233                offense,
234                defense
235            }
236        )
237    }
238
239    /// Constructor for the `FootballTeam` struct in which each
240    /// property is given as an argument.
241    ///
242    /// ### Example
243    /// ```
244    /// use fbsim_core::team::FootballTeam;
245    /// use fbsim_core::team::coach::FootballTeamCoach;
246    /// use fbsim_core::team::offense::FootballTeamOffense;
247    /// use fbsim_core::team::defense::FootballTeamDefense;
248    ///
249    /// let my_coach = FootballTeamCoach::new();
250    /// let my_defense = FootballTeamDefense::new();
251    /// let my_offense = FootballTeamOffense::new();
252    /// let my_team = FootballTeam::from_properties("My Team", "TEAM", my_coach, my_offense, my_defense);
253    /// ```
254    pub fn from_properties(name: &str, short_name: &str, coach: FootballTeamCoach, offense: FootballTeamOffense, defense: FootballTeamDefense) -> FootballTeam {
255        FootballTeam{
256            name: String::from(name),
257            short_name: String::from(short_name),
258            coach,
259            offense,
260            defense
261        }
262    }
263
264    /// Get the football team's name
265    ///
266    /// ### Example
267    /// ```
268    /// use fbsim_core::team::FootballTeam;
269    ///
270    /// let my_team = FootballTeam::new();
271    /// let name = my_team.name();
272    /// ```
273    pub fn name(&self) -> &str {
274        &self.name
275    }
276
277    /// Get the football team's name mutably
278    ///
279    /// ### Example
280    /// ```
281    /// use fbsim_core::team::FootballTeam;
282    ///
283    /// let mut my_team = FootballTeam::new();
284    /// let mut name = my_team.name_mut();
285    /// ```
286    pub fn name_mut(&mut self) -> &mut String {
287        &mut self.name
288    }
289
290    /// Borrow the football team's short name / acronym
291    ///
292    /// ### Example
293    /// ```
294    /// use fbsim_core::team::FootballTeam;
295    ///
296    /// let my_team = FootballTeam::new();
297    /// let short_name = my_team.short_name();
298    /// ```
299    pub fn short_name(&self) -> &str {
300        &self.short_name
301    }
302
303    /// Borrow the football team's short name / acronym mutably
304    ///
305    /// ### Example
306    /// ```
307    /// use fbsim_core::team::FootballTeam;
308    ///
309    /// let mut my_team = FootballTeam::new();
310    /// let mut short_name = my_team.short_name_mut();
311    /// ```
312    pub fn short_name_mut(&mut self) -> &mut String {
313        &mut self.short_name
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use super::*;
320
321    #[test]
322    fn test_football_team_from_properties() {
323        // Test offense overall OOB high
324        let result_a = FootballTeam::from_overalls("Test Team", "TEST", 200, 50);
325        let expected_a: Result<FootballTeam, String> = Err(
326            String::from("Passing attribute is out of range [0, 100]: 200")
327        );
328        assert_eq!(
329            result_a,
330            expected_a
331        );
332
333        // Test defense overall OOB high
334        let result_b = FootballTeam::from_overalls("Test Team", "TEST", 50, 150);
335        let expected_b: Result<FootballTeam, String> = Err(
336            String::from("Blitzing attribute is out of range [0, 100]: 150")
337        );
338        assert_eq!(
339            result_b,
340            expected_b
341        );
342    }
343}