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}