Skip to main content

pickems/datatypes/
seed.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3/// Initial tournament seed of a team.
4///
5/// Seeds are one-based and valid in the inclusive range `1..=16`.
6#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq)]
7pub struct Seed(u16);
8
9impl Seed {
10    pub fn try_new(seed: u16) -> anyhow::Result<Self> {
11        if !(1..=16).contains(&seed) {
12            anyhow::bail!("invalid seed: must be between 1 and 16");
13        }
14
15        Ok(Self(seed))
16    }
17
18    #[must_use]
19    pub fn new(seed: u16) -> Self {
20        Self::try_new(seed).unwrap()
21    }
22
23    /// Iterate through all valid initial seeds in ascending order.
24    pub fn iter_all() -> impl Iterator<Item = Self> {
25        (1..=16).map(|i| Self::try_new(i).unwrap())
26    }
27}
28
29impl std::fmt::Display for Seed {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        self.0.fmt(f)
32    }
33}
34
35impl Serialize for Seed {
36    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
37        serializer.serialize_u16(self.0)
38    }
39}
40
41impl<'de> Deserialize<'de> for Seed {
42    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
43        Self::try_new(u16::deserialize(deserializer)?).map_err(serde::de::Error::custom)
44    }
45}
46
47/// Zero-based index into arrays sorted by ascending initial seed.
48///
49/// `Index` is the simulation-facing companion to [`Seed`]: seed `1` maps to
50/// index `0`, and seed `16` maps to index `15`.
51#[derive(Debug, Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)]
52#[repr(transparent)]
53pub struct Index(u16);
54
55impl Index {
56    /// Construct an index from a compile-time constant.
57    ///
58    /// This fails to compile when `N >= 16`.
59    #[inline]
60    #[must_use]
61    pub const fn new<const N: u16>() -> Self {
62        const { assert!(N < 16, "invalid index value (must be less than 16)") };
63        Self(N)
64    }
65
66    /// Construct an index from a runtime value.
67    #[inline]
68    pub fn try_new(n: u16) -> anyhow::Result<Self> {
69        if n < 16 {
70            Ok(Self(n))
71        } else {
72            Err(anyhow::anyhow!("invalid team index: {n}"))
73        }
74    }
75
76    /// Return this index as a raw `u16`.
77    #[inline]
78    #[must_use]
79    pub const fn to_u16(self) -> u16 {
80        self.0
81    }
82
83    /// Return this index as a `usize` for array indexing.
84    #[inline]
85    #[must_use]
86    pub const fn to_usize(self) -> usize {
87        self.0 as usize
88    }
89
90    /// Convert this zero-based index into its one-based tournament seed.
91    #[inline]
92    #[must_use]
93    pub fn to_seed(self) -> Seed {
94        Seed::try_new(self.0 + 1).unwrap()
95    }
96
97    /// Return a bit mask selecting this index in a 16-bit [`Set`](crate::datatypes::Set).
98    #[inline]
99    #[must_use]
100    pub const fn bit_select(self) -> u16 {
101        1 << self.0
102    }
103
104    /// # Safety
105    /// Must ensure that `n` < 16. This type is used to index 16 element arrays.
106    #[inline]
107    #[must_use]
108    pub const unsafe fn from_u16(n: u16) -> Self {
109        Self(n)
110    }
111
112    /// # Safety
113    /// Must ensure that `n` < 16. This type is used to index 16 element arrays.
114    #[inline]
115    #[must_use]
116    pub const unsafe fn from_u32(n: u32) -> Self {
117        Self(n as u16)
118    }
119
120    /// # Safety
121    /// Must ensure that `n` < 16. This type is used to index 16 element arrays.
122    #[inline]
123    #[must_use]
124    pub const unsafe fn from_usize(n: usize) -> Self {
125        Self(n as u16)
126    }
127
128    /// Iterate through every valid zero-based index.
129    pub fn iter_all() -> impl Iterator<Item = Self> {
130        (0..16).map(|i| unsafe { Self::from_u16(i) })
131    }
132}
133
134impl std::fmt::Display for Index {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        write!(f, "{}", self.0)
137    }
138}
139
140impl From<Index> for Seed {
141    fn from(index: Index) -> Self {
142        // `Index` is guaranteed to be in 0..16, so adding one preserves the
143        // `Seed` invariant of 1..=16.
144        Self(index.0 + 1)
145    }
146}
147
148impl From<Seed> for Index {
149    fn from(seed: Seed) -> Self {
150        // `Seed` is guaranteed to be in 1..=16, so subtracting one preserves the
151        // `Index` invariant of 0..16.
152        Self(seed.0 - 1)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[derive(Deserialize)]
161    struct Input {
162        seed: Seed,
163    }
164
165    #[test]
166    fn validates_seeds() {
167        assert!(Seed::try_new(1).is_ok());
168        assert!(Seed::try_new(16).is_ok());
169        assert!(Seed::try_new(0).is_err());
170        assert!(Seed::try_new(17).is_err());
171    }
172
173    #[test]
174    fn rejects_invalid_deserialized_seeds() {
175        let input: Input = toml::from_str("seed = 16").unwrap();
176
177        assert_eq!(input.seed.to_string(), "16");
178        assert!(toml::from_str::<Input>("seed = 0").is_err());
179        assert!(toml::from_str::<Input>("seed = 17").is_err());
180    }
181}