dilemma_tactix_lib/models/
game_options.rs

1// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
2// * Copyright (c) 2023-2024
3// *
4// * This project is dual-licensed under the MIT and Apache licenses.
5// *
6// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
7// ** APACHE 2.0 LICENSE
8// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
9// *
10// * Licensed under the Apache License, Version 2.0 (the "License");
11// * you may not use this file except in compliance with the License.
12// * You may obtain a copy of the License at
13// *
14// * http://www.apache.org/licenses/LICENSE-2.0
15// *
16// * Unless required by applicable law or agreed to in writing, software
17// * distributed under the License is distributed on an "AS IS" BASIS,
18// * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19// * See the License for the specific language governing permissions and
20// * limitations under the License.
21// *
22// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
23// * * MIT LICENSE
24// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
25// *
26// * Permission is hereby granted, free of charge, to any person obtaining a copy
27// * of this software and associated documentation files (the "Software"), to deal
28// * in the Software without restriction, including without limitation the rights
29// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
30// * copies of the Software, and to permit persons to whom the Software is
31// * furnished to do so, subject to the following conditions:
32// *
33// * The above copyright notice and this permission notice shall be included in all
34// * copies or substantial portions of the Software.
35// *
36// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
37// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
38// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
39// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
40// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
41// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
42// * SOFTWARE.
43// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
44
45use std::fmt::Display;
46
47#[cfg(test)]
48use crate::RANDOM_SEED;
49use crate::{
50    ChoiceNameOptions,
51    GameOptionsBuilder,
52    NumberPair,
53};
54
55/// This is a struct that holds the options for a game.
56///
57/// This struct is used to encapsulate the parameters to be used for generating
58/// a single grid from the parameters that may be related to a tournament or a
59/// series of grids.
60///
61/// The two parameters are:
62///
63/// * `min_value` - The minimum value for that can be assigned to a choice.
64/// * `max_value` - The maximum value for that can be assigned to a choice.
65///
66/// # Example
67///
68/// ## Explicit Options
69///
70/// ```
71/// use dilemma_tactix_lib::GameOptions;
72///
73/// let game_options = GameOptions::new(1, 10);
74/// ```
75///
76/// ## Default Options
77///
78/// ```
79/// use dilemma_tactix_lib::GameOptions;
80///
81/// let game_options = GameOptions::default();
82/// ```
83///
84/// ## Builder
85///
86/// ```
87/// use dilemma_tactix_lib::GameOptions;
88///
89/// let game_options = GameOptions::builder("customized").build();
90/// ```
91///
92/// # Notes
93///
94/// The `GameOptions` struct implements the `Default` trait, and can be created
95/// with the `default()` method.
96///
97/// # See Also
98///
99/// * [`GameOptions::new()`](#method.new)
100/// * [`GameOptions::default()`](#method.default)
101/// * [`GameOptions::builder()`](#method.builder)
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub struct GameOptions {
104    /// The label for the first choice that can be made
105    pub choice_atlantis:   &'static str,
106    /// The label for the second choice that can be made
107    pub choice_olympus:    &'static str,
108    /// Score for Aleph-Atlantis and Beth-Atlantis
109    pub atlantis_atlantis: NumberPair,
110    /// Score for Aleph-Atlantis and Beth-Olympus
111    pub atlantis_olympus:  NumberPair,
112    /// Score for Aleph-Olympus and Beth-Atlantis
113    pub olympus_atlantis:  NumberPair,
114    /// Score for Aleph-Olympus and Beth-Olympus
115    pub olympus_olympus:   NumberPair,
116}
117
118impl GameOptions {
119    /// Creates a new `GameOptions` struct.
120    ///
121    /// This function creates a new `GameOptions` struct with the given
122    /// parameters.
123    ///
124    /// In the implementation, the new struct instance instantiates the
125    /// `ChoiceNameOptions` struct, and then uses it to get a random pair of
126    /// choices. It then uses the given `min_value` and `max_value` to generate
127    /// random scores for the four possible combinations of outcomes.
128    ///
129    /// # Arguments
130    ///
131    /// * `min_value` - The minimum score for that can be assigned to a choice.
132    /// * `max_value` - The maximum score for that can be assigned to a choice.
133    ///
134    /// # Panics
135    ///
136    /// Panics if `min_value` is greater than `max_value`.
137    ///
138    /// # See Also
139    ///
140    /// * [`GameOptions::default()`](#method.default)
141    #[must_use]
142    pub fn new(min_value: u32, max_value: u32) -> Self {
143        #[cfg(test)]
144        let (choice_atlantis, choice_olympus) =
145            ChoiceNameOptions::get_random_pair_seeded(RANDOM_SEED.0);
146
147        #[cfg(test)]
148        let (atlantis_atlantis, atlantis_olympus, olympus_atlantis, olympus_olympus) = (
149            NumberPair::random_seeded(min_value, max_value, RANDOM_SEED.0),
150            NumberPair::random_seeded(min_value, max_value, RANDOM_SEED.1),
151            NumberPair::random_seeded(min_value, max_value, RANDOM_SEED.2),
152            NumberPair::random_seeded(min_value, max_value, RANDOM_SEED.3),
153        );
154
155        #[cfg(not(test))]
156        let (choice_atlantis, choice_olympus) = ChoiceNameOptions::get_random_pair();
157
158        #[cfg(not(test))]
159        let (atlantis_atlantis, atlantis_olympus, olympus_atlantis, olympus_olympus) = (
160            NumberPair::random(min_value, max_value),
161            NumberPair::random(min_value, max_value),
162            NumberPair::random(min_value, max_value),
163            NumberPair::random(min_value, max_value),
164        );
165
166        Self {
167            choice_atlantis,
168            choice_olympus,
169            atlantis_atlantis,
170            atlantis_olympus,
171            olympus_atlantis,
172            olympus_olympus,
173        }
174    }
175
176    /// Returns the value of `choice_atlantis`.
177    ///
178    /// This function returns the value of `choice_atlantis`.
179    ///
180    /// # Returns
181    ///
182    /// The value of `choice_atlantis`.
183    ///
184    /// # See Also
185    ///
186    /// * [`GameOptions::min_value()`](#method.min_value)
187    /// * [`GameOptions::max_value()`](#method.max_value)
188    /// * [`GameOptions::choice_olympus()`](#method.choice_olympus)
189    #[must_use]
190    pub const fn choice_atlantis(&self) -> &str {
191        self.choice_atlantis
192    }
193
194    /// Returns the value of `choice_olympus`.
195    ///
196    /// This function returns the value of `choice_olympus`.
197    ///
198    /// # Returns
199    ///
200    /// The value of `choice_olympus`.
201    ///
202    /// # See Also
203    ///
204    /// * [`GameOptions::min_value()`](#method.min_value)
205    /// * [`GameOptions::max_value()`](#method.max_value)
206    /// * [`GameOptions::choice_atlantis()`](#method.choice_atlantis)
207    #[must_use]
208    pub const fn choice_olympus(&self) -> &str {
209        self.choice_olympus
210    }
211
212    /// Returns the value of `atlantis_atlantis`.
213    ///
214    /// This function returns the value of `atlantis_atlantis`, which is the
215    /// `NumberPair` containing the scores for the case when both Player Aleph
216    /// and Player Beth choose the Atlantis strategy.
217    ///
218    /// # Returns
219    ///
220    /// The value of `atlantis_atlantis` as a
221    /// [`NumberPair`](#struct.NumberPair).
222    ///
223    /// # See Also
224    ///
225    /// * [`GameOptions::atlantis_olympus()`](#method.atlantis_olympus)
226    /// * [`GameOptions::olympus_atlantis()`](#method.olympus_atlantis)
227    /// * [`GameOptions::olympus_olympus()`](#method.olympus_olympus)
228    #[must_use]
229    pub const fn atlantis_atlantis(&self) -> NumberPair {
230        self.atlantis_atlantis
231    }
232
233    /// Returns the value of `atlantis_olympus`.
234    ///
235    /// This function returns the value of `atlantis_olympus`, which is the
236    /// `NumberPair` containing the scores for the case when Player Aleph
237    /// chooses the Atlantis strategy and Player Beth chooses the Olympus
238    /// strategy.
239    ///
240    /// # Returns
241    ///
242    /// The value of `atlantis_olympus` as a [`NumberPair`](#struct.NumberPair).
243    ///
244    /// # See Also
245    ///
246    /// * [`GameOptions::atlantis_atlantis()`](#method.atlantis_atlantis)
247    /// * [`GameOptions::olympus_atlantis()`](#method.olympus_atlantis)
248    /// * [`GameOptions::olympus_olympus()`](#method.olympus_olympus)
249    #[must_use]
250    pub const fn atlantis_olympus(&self) -> NumberPair {
251        self.atlantis_olympus
252    }
253
254    /// Returns the value of `olympus_atlantis`.
255    ///
256    /// This function returns the value of `olympus_atlantis`, which is the
257    /// `NumberPair` containing the scores for the case when Player Aleph
258    /// chooses the Olympus strategy and Player Beth chooses the Atlantis
259    /// strategy.
260    ///
261    /// # Returns
262    ///
263    /// The value of `olympus_atlantis` as a [`NumberPair`](#struct.NumberPair).
264    ///
265    /// # See Also
266    ///
267    /// * [`GameOptions::atlantis_atlantis()`](#method.atlantis_atlantis)
268    /// * [`GameOptions::atlantis_olympus()`](#method.atlantis_olympus)
269    /// * [`GameOptions::olympus_olympus()`](#method.olympus_olympus)
270    #[must_use]
271    pub const fn olympus_atlantis(&self) -> NumberPair {
272        self.olympus_atlantis
273    }
274
275    /// Returns the value of `olympus_olympus`.
276    ///
277    /// This function returns the value of `olympus_olympus`, which is the
278    /// `NumberPair` containing the scores for the case when both Player Aleph
279    /// and Player Beth choose the Olympus strategy.
280    ///
281    /// # Returns
282    ///
283    /// The value of `olympus_olympus` as a [`NumberPair`](#struct.NumberPair).
284    ///
285    /// # See Also
286    ///
287    /// * [`GameOptions::atlantis_atlantis()`](#method.atlantis_atlantis)
288    /// * [`GameOptions::atlantis_olympus()`](#method.atlantis_olympus)
289    /// * [`GameOptions::olympus_atlantis()`](#method.olympus_atlantis)
290    #[must_use]
291    pub const fn olympus_olympus(&self) -> NumberPair {
292        self.olympus_olympus
293    }
294
295    /// Create a builder for a `GameOptions` struct.
296    ///
297    /// This function creates a builder for a `GameOptions` struct which allows
298    /// for step-by-step building of individual game options objects, bypassing
299    /// random generation of values if desired.
300    ///
301    /// # Arguments
302    ///
303    /// * `builder_type` - The type of builder to create. Valid values are
304    ///  `randomized`, `seeded`, and `customized`.
305    ///
306    /// # Example
307    ///
308    /// ```
309    /// use dilemma_tactix_lib::GameOptions;
310    ///
311    /// let game_options = GameOptions::builder("customized");
312    /// ```
313    ///
314    /// # Returns
315    ///
316    /// A new `GameOptionsBuilder` struct.
317    ///
318    /// # Notes
319    ///
320    /// Unlike the `default` method. a `default` builder is guaranteed to
321    /// be the same each time it is called.
322    ///
323    /// # See Also
324    ///
325    /// * [`GameOptionsBuilder`](#struct.GameOptionsBuilder)
326    /// * [`GameOptions::new()`](#method.new)
327    /// * [`GameOptions::default()`](#method.default)
328    /// * [`GameOptionsBuilder::build()`](#method.build)
329    #[must_use]
330    pub fn builder(builder_type: &str) -> GameOptionsBuilder {
331        match builder_type {
332            "seeded" | "Seeded" => {
333                GameOptionsBuilder::new(super::game_option_builder::GameOptionsBuilderTypes::Seeded)
334            }
335            "customized" | "Customized" => GameOptionsBuilder::new(
336                super::game_option_builder::GameOptionsBuilderTypes::Customized,
337            ),
338            _ => GameOptionsBuilder::new(
339                super::game_option_builder::GameOptionsBuilderTypes::Randomized,
340            ),
341        }
342    }
343}
344
345impl Default for GameOptions {
346    /// Creates a new `GameOptions` struct with default values.
347    ///
348    /// This function creates a new `GameOptions` struct with default values.
349    ///
350    /// The default values are:
351    ///
352    /// * `min_value` - 1
353    /// * `max_value` - 10
354    /// * `choice_atlantis` - "cooperate"
355    /// * `choice_olympus` - "defect"
356    ///
357    /// # Returns
358    ///
359    /// A new `GameOptions` struct with default values for the parameters.
360    ///
361    /// # Notes
362    ///
363    /// Unlike the default options in the `builder()` method, the default
364    /// options here are guaranteed to generate a new random grid each time.
365    ///
366    /// # See Also
367    ///
368    /// * [`GameOptions::new()`](#method.new)
369    /// * [`GameOptions::builder()`](#method.builder)
370
371    fn default() -> Self {
372        Self::new(1, 10)
373    }
374}
375
376impl Display for GameOptions {
377    /// Implements the Display trait for `GameOptions`.
378    ///
379    /// This function implements the Display trait for `GameOptions`.
380
381    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
382        write!(
383            f,
384            "choice_atlantis: {}, choice_olympus: {}, atlantis_atlantis: {}, atlantis_olympus: \
385             {}, olympus_atlantis: {}, olympus_olympus: {}",
386            self.choice_atlantis,
387            self.choice_olympus,
388            self.atlantis_atlantis,
389            self.atlantis_olympus,
390            self.olympus_atlantis,
391            self.olympus_olympus
392        )
393    }
394}
395
396#[cfg(test)]
397mod tests {
398
399    use rstest::{
400        fixture,
401        rstest,
402    };
403
404    use super::*;
405
406    #[fixture]
407    fn choice_atlantis_options() -> [&'static str; 17] {
408        ChoiceNameOptions::choice_atlantis_options()
409    }
410
411    #[fixture]
412    fn choice_olympus_options() -> [&'static str; 17] {
413        ChoiceNameOptions::choice_olympus_options()
414    }
415
416    #[rstest]
417    fn test_game_options_default(
418        choice_atlantis_options: [&'static str; 17],
419        choice_olympus_options: [&'static str; 17],
420    ) {
421        let game_options = GameOptions::default();
422
423        assert_eq!(game_options.atlantis_atlantis(), NumberPair::new(6, 9));
424
425        assert_eq!(game_options.atlantis_olympus(), NumberPair::new(3, 8));
426
427        assert_eq!(game_options.olympus_atlantis(), NumberPair::new(6, 7));
428
429        assert_eq!(game_options.olympus_olympus(), NumberPair::new(3, 6));
430
431        assert_eq!(game_options.choice_atlantis(), "discrete");
432
433        assert_eq!(game_options.choice_olympus(), "continuous");
434
435        assert!(choice_atlantis_options.contains(&game_options.choice_atlantis()));
436
437        assert!(choice_olympus_options.contains(&game_options.choice_olympus()));
438    }
439
440    #[test]
441    fn test_game_options_new() {
442        let game_options = GameOptions::new(1, 10);
443
444        assert_eq!(game_options.atlantis_atlantis(), NumberPair::new(6, 9));
445
446        assert_eq!(game_options.atlantis_olympus(), NumberPair::new(3, 8));
447
448        assert_eq!(game_options.olympus_atlantis(), NumberPair::new(6, 7));
449
450        assert_eq!(game_options.olympus_olympus(), NumberPair::new(3, 6));
451
452        assert_eq!(game_options.choice_atlantis(), "discrete");
453
454        assert_eq!(game_options.choice_olympus(), "continuous");
455    }
456
457    #[test]
458    fn test_game_options_display() {
459        let game_options = GameOptions::new(1, 10);
460
461        assert_eq!(
462            format!("{}", game_options),
463            "choice_atlantis: discrete, choice_olympus: continuous, atlantis_atlantis: (6, 9), \
464             atlantis_olympus: (3, 8), olympus_atlantis: (6, 7), olympus_olympus: (3, 6)"
465        );
466    }
467
468    #[test]
469    fn test_builder_randomized() {
470        let builder = GameOptions::builder("randomized");
471
472        assert!(builder.choice_atlantis.is_none());
473
474        assert!(builder.choice_olympus.is_none());
475
476        assert!(builder.atlantis_atlantis.is_none());
477
478        assert!(builder.atlantis_olympus.is_none());
479
480        assert!(builder.olympus_atlantis.is_none());
481
482        assert!(builder.olympus_olympus.is_none());
483    }
484
485    #[test]
486    fn test_builder_seeded() {
487        let builder = GameOptions::builder("seeded");
488
489        assert!(builder.choice_atlantis.is_none());
490
491        assert!(builder.choice_olympus.is_none());
492
493        assert!(builder.atlantis_atlantis.is_none());
494
495        assert!(builder.atlantis_olympus.is_none());
496
497        assert!(builder.olympus_atlantis.is_none());
498
499        assert!(builder.olympus_olympus.is_none());
500    }
501
502    #[test]
503    fn test_builder_customized() {
504        let builder = GameOptions::builder("customized");
505
506        assert!(builder.choice_atlantis.is_none());
507
508        assert!(builder.choice_olympus.is_none());
509
510        assert!(builder.atlantis_atlantis.is_none());
511
512        assert!(builder.atlantis_olympus.is_none());
513
514        assert!(builder.olympus_atlantis.is_none());
515
516        assert!(builder.olympus_olympus.is_none());
517    }
518}