Skip to main content

games/rps/
weapons.rs

1use crate::errors::{BASE_ROCK_PAPER_SCISSORS_ERROR_CODE, ErrorCode};
2use core::{
3    cmp::{Ord, Ordering, PartialOrd},
4    fmt,
5};
6use rand::RngExt;
7use std::str::FromStr;
8
9/// Weapons for Rock paper scissors
10#[repr(C)]
11#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, serde::Serialize, serde::Deserialize)]
12pub enum Weapon {
13    /// A rock, it beats scissors
14    Rock,
15    /// Paper, it beats a rock
16    Paper,
17    /// Scissors, it beats paper
18    Scissors,
19}
20
21/// Error parsing weapon
22#[repr(C)]
23#[derive(
24    Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq, Ord, PartialOrd,
25)]
26
27pub enum WeaponParseError {
28    /// Weapon attempted to be parsed was not valid (4001)
29    InvalidWeaponError,
30}
31
32impl fmt::Display for WeaponParseError {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        match *self {
35            WeaponParseError::InvalidWeaponError => f.write_str("Invalid Weapon"),
36        }
37    }
38}
39
40impl ErrorCode for WeaponParseError {
41    fn error_code(&self) -> i32 {
42        BASE_ROCK_PAPER_SCISSORS_ERROR_CODE
43            + match *self {
44                WeaponParseError::InvalidWeaponError => 1,
45            }
46    }
47}
48
49impl fmt::Display for Weapon {
50    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
51        write!(
52            f,
53            "{}",
54            match self {
55                Weapon::Rock => "Rock",
56                Weapon::Paper => "Paper",
57                Weapon::Scissors => "Scissors",
58            }
59        )
60    }
61}
62
63impl FromStr for Weapon {
64    type Err = WeaponParseError;
65    fn from_str(s: &str) -> Result<Self, Self::Err> {
66        s.chars()
67            .next()
68            .map(|c| c.to_ascii_lowercase())
69            .and_then(|c| match c {
70                'r' => Some(Weapon::Rock),
71                'p' => Some(Weapon::Paper),
72                's' => Some(Weapon::Scissors),
73                _ => None,
74            })
75            .ok_or(WeaponParseError::InvalidWeaponError)
76    }
77}
78
79impl PartialOrd for Weapon {
80    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
81        Some(self.cmp(other))
82    }
83}
84
85impl Ord for Weapon {
86    fn cmp(&self, other: &Self) -> Ordering {
87        match self {
88            // Rocks are
89            Weapon::Rock => match other {
90                // Equal to rocks
91                Weapon::Rock => Ordering::Equal,
92                // Weaker than papers
93                Weapon::Paper => Ordering::Less,
94                // Stronger than scissors
95                Weapon::Scissors => Ordering::Greater,
96            },
97            // Papers are
98            Weapon::Paper => match other {
99                // Stronger than rocks
100                Weapon::Rock => Ordering::Greater,
101                // equal to papers
102                Weapon::Paper => Ordering::Equal,
103                // weaker than scissors
104                Weapon::Scissors => Ordering::Less,
105            },
106            // Scissors are
107            Weapon::Scissors => match other {
108                // Weaker than rocks
109                Weapon::Rock => Ordering::Less,
110                // Stronger than paper
111                Weapon::Paper => Ordering::Greater,
112                // equal to scissors
113                Weapon::Scissors => Ordering::Equal,
114            },
115        }
116    }
117}
118
119impl Weapon {
120    /// Randomly choose a weapon
121    pub fn rand() -> Self {
122        "rps"
123            .chars()
124            .nth(crate::get_rng().random_range(0..3))
125            .and_then(|p| Weapon::from_str(&p.to_string()).ok())
126            .expect("Rand should always return weapon")
127    }
128}