cardinal_values/
lib.rs

1//! A tiny library providing support for `Cardinal`, an enum of the four cardinal directions,
2//! and `CardinalValues`, which is a struct indexed by `Cardinal` with a value at each direction.
3
4#![deny(rust_2018_idioms)]
5#![allow(clippy::bool_comparison)]
6#![warn(clippy::dbg_macro)]
7#![warn(clippy::print_stdout)]
8#![warn(clippy::todo)]
9#![warn(clippy::undocumented_unsafe_blocks)]
10#![warn(missing_docs)]
11
12use core::ops;
13
14/// An enumerator for the simple cardinal directions.
15#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord)]
16#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
17pub enum Cardinal {
18    /// East, or (1, 0)
19    East,
20    /// North, or (0, 1)
21    North,
22    /// West, or (-1, 0)
23    West,
24    /// South, or (0, -1)
25    South,
26}
27
28impl Cardinal {
29    /// Rotates a cardinal.
30    #[must_use = "this returns the result of the operation, \
31    without modifying the original"]
32    pub fn rotate(self, amount: i32) -> Self {
33        let mut v = match self {
34            Cardinal::East => 0,
35            Cardinal::North => 1,
36            Cardinal::West => 2,
37            Cardinal::South => 3,
38        };
39
40        v += amount;
41        v = v.rem_euclid(4);
42
43        match v {
44            0 => Cardinal::East,
45            1 => Cardinal::North,
46            2 => Cardinal::West,
47            3 => Cardinal::South,
48            _ => unreachable!(),
49        }
50    }
51
52    /// Gives an iterator over the four cardinals
53    pub fn iter_values() -> impl Iterator<Item = Self> {
54        [
55            Cardinal::East,
56            Cardinal::North,
57            Cardinal::West,
58            Cardinal::South,
59        ]
60        .into_iter()
61    }
62
63    /// Converts to a simple tuple int form.
64    /// This assumes that north is up.
65    pub fn to_ivec2(self) -> (i32, i32) {
66        match self {
67            Cardinal::East => (1, 0),
68            Cardinal::North => (0, 1),
69            Cardinal::West => (-1, 0),
70            Cardinal::South => (0, -1),
71        }
72    }
73
74    /// Returns an angle representing the Cardinal
75    pub fn to_angle(self) -> f32 {
76        match self {
77            Cardinal::East => 0.0,
78            Cardinal::North => 90.0,
79            Cardinal::West => 180.0,
80            Cardinal::South => 270.0,
81        }
82    }
83
84    /// This returns a cardinal as a best guess for floats. If you give an exactly
85    /// divisible by 90 degrees, it'll work just the way you expect.
86    pub fn from_angle(angle: f32) -> Cardinal {
87        let angle = angle.rem_euclid(360.0);
88
89        if (45.0..135.0).contains(&angle) {
90            Cardinal::North
91        } else if (135.0..225.0).contains(&angle) {
92            Cardinal::West
93        } else if (225.0..315.0).contains(&angle) {
94            Cardinal::South
95        } else {
96            Cardinal::East
97        }
98    }
99
100    /// Is either West or East
101    pub fn is_horizontal(self) -> bool {
102        matches!(self, Self::East | Self::West)
103    }
104
105    /// Is either North or South
106    pub fn is_vertical(self) -> bool {
107        matches!(self, Self::North | Self::South)
108    }
109}
110
111impl core::fmt::Display for Cardinal {
112    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
113        let word = match self {
114            Cardinal::East => "east",
115            Cardinal::North => "north",
116            Cardinal::West => "west",
117            Cardinal::South => "south",
118        };
119
120        f.pad(word)
121    }
122}
123
124/// A struct which a value assigned to each cardinal. This can be used as a shorthand for
125/// accessing arrays.
126#[derive(Debug, PartialEq, Eq, Clone, Copy, PartialOrd, Ord, Default, Hash)]
127#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
128pub struct CardinalValues<T> {
129    /// The value assigned to east.
130    pub east: T,
131    /// The value assigned to north.
132    pub north: T,
133    /// The value assigned to west.
134    pub west: T,
135    /// The value assigned to south.
136    pub south: T,
137}
138
139impl<T> CardinalValues<T> {
140    /// Converts a [CardinalValues] from one type to another.
141    pub fn map<B, F>(self, mut f: F) -> CardinalValues<B>
142    where
143        F: FnMut(T) -> B,
144    {
145        CardinalValues {
146            east: f(self.east),
147            north: f(self.north),
148            west: f(self.west),
149            south: f(self.south),
150        }
151    }
152}
153
154impl<T> ops::Index<Cardinal> for CardinalValues<T> {
155    type Output = T;
156
157    fn index(&self, index: Cardinal) -> &Self::Output {
158        match index {
159            Cardinal::East => &self.east,
160            Cardinal::North => &self.north,
161            Cardinal::West => &self.west,
162            Cardinal::South => &self.south,
163        }
164    }
165}
166
167impl<T: Copy> IntoIterator for CardinalValues<T> {
168    type Item = T;
169
170    type IntoIter = CardinalIterator<T>;
171
172    fn into_iter(self) -> Self::IntoIter {
173        CardinalIterator(self, 0)
174    }
175}
176
177/// An iterator over a CardinalValues.
178pub struct CardinalIterator<T>(CardinalValues<T>, usize);
179
180impl<T> CardinalIterator<T> {
181    /// Converts this iterator into an Enumerated one, where each value has its Cardinal given.
182    pub fn enumerate(self) -> CardinalEnumeratedIterator<T> {
183        CardinalEnumeratedIterator(self.0, self.1)
184    }
185}
186
187impl<T: Copy> Iterator for CardinalIterator<T> {
188    type Item = T;
189
190    fn next(&mut self) -> Option<Self::Item> {
191        let found = match self.1 {
192            0 => Some(self.0.east),
193            1 => Some(self.0.west),
194            2 => Some(self.0.north),
195            3 => Some(self.0.south),
196            _ => return None,
197        };
198
199        self.1 += 1;
200
201        found
202    }
203}
204
205/// An enumerated iterator for [CardinalValues]. This should be constructed with the `enumerate` method
206/// on [CardinalIterator].
207pub struct CardinalEnumeratedIterator<T>(CardinalValues<T>, usize);
208impl<T: Copy> Iterator for CardinalEnumeratedIterator<T> {
209    type Item = (Cardinal, T);
210
211    fn next(&mut self) -> Option<Self::Item> {
212        let found = match self.1 {
213            0 => Some((Cardinal::North, self.0.north)),
214            1 => Some((Cardinal::West, self.0.west)),
215            2 => Some((Cardinal::South, self.0.south)),
216            3 => Some((Cardinal::East, self.0.east)),
217            _ => return None,
218        };
219
220        self.1 += 1;
221
222        found
223    }
224}