Skip to main content

sokoban_elements/
direction.rs

1use std::fmt::{Debug, Display};
2use std::iter::{IntoIterator, Iterator};
3
4use itertools::{self, Itertools};
5
6/// Direction of movement.
7///
8/// Not all directions are legal (or even supported) by all [`Tessellation`](crate::Tessellation)s.
9///
10/// For example, in [`Sokoban`](crate::Tessellation::Sokoban), it is completely illegal to make a
11/// move in north-west direction.
12///
13/// On the other hand, in [`Octoban`](crate::Tessellation::Octoban) it is allowed to try making a
14/// north-west move, but success will depend on concrete board position. If that concrete position
15/// happens to be square, then that move is not allowed.
16///
17/// Note though that [`Direction`] doesn't try to encode or resolve what is or isn't allowed, it
18/// just enumerates all possible directions.
19#[repr(u8)]
20#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub enum Direction {
23    Up        = 0,
24    NorthEast = 1,
25    Right     = 2,
26    SouthEast = 3,
27    Down      = 4,
28    SouthWest = 5,
29    Left      = 6,
30    NorthWest = 7,
31}
32
33impl Direction {
34    /// Number of all possible [`Direction`]s.
35    pub const COUNT: usize = 8;
36
37    /// All possible [`Direction`]s in clockwise order.
38    pub const ALL: [Direction; Direction::COUNT] = [
39        Direction::Up,
40        Direction::NorthEast,
41        Direction::Right,
42        Direction::SouthEast,
43        Direction::Down,
44        Direction::SouthWest,
45        Direction::Left,
46        Direction::NorthWest,
47    ];
48
49    #[expect(clippy::as_conversions)]
50    pub fn opposite(&self) -> Direction { OPPOSITES[usize::from(*self as u8)] }
51}
52
53impl Display for Direction {
54    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55        write!(f, "Direction::{self:?}")
56    }
57}
58
59const OPPOSITES: [Direction; Direction::COUNT] = [
60    Direction::Down,
61    Direction::SouthWest,
62    Direction::Left,
63    Direction::NorthWest,
64    Direction::Up,
65    Direction::NorthEast,
66    Direction::Right,
67    Direction::SouthEast,
68];
69
70const EMPTY: u8 = 0;
71
72const DIRECTIONS_FLAGS: [u8; Direction::COUNT] = [1, 2, 4, 8, 16, 32, 64, 128];
73
74const FLAGS_DIRECTIONS: [Direction; Direction::COUNT] = [
75    Direction::Up,
76    Direction::NorthEast,
77    Direction::Right,
78    Direction::SouthEast,
79    Direction::Down,
80    Direction::SouthWest,
81    Direction::Left,
82    Direction::NorthWest,
83];
84
85/// Immutable `Set` of [`Direction`] with small memory footprint.
86///
87/// Can be used as key representing some aggregated quality of game board position. For example, in
88/// puzzle:
89///
90/// ```txt
91/// #####
92/// #@  #
93/// #$  #
94/// #.  #
95/// #####
96/// ```
97///
98/// we can see that position `(0, 0)` is a wall that has neighbor wall on the right and below. Or,
99/// in terms of [`DirectionsSet`]:
100///
101/// ```rust
102/// use sokoban_elements::*;
103///
104/// let neighbor_walls: DirectionsSet = [Direction::Left, Direction::Up].into();
105///
106/// assert_eq!(neighbor_walls, [Direction::Left, Direction::Up]);
107///
108/// assert!(neighbor_walls.contains(Direction::Left));
109/// assert!(neighbor_walls.contains(Direction::Up));
110///
111/// assert!(!neighbor_walls.contains(Direction::Down));
112/// ```
113#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
114pub struct DirectionsSet(u8);
115
116impl DirectionsSet {
117    pub fn is_empty(&self) -> bool { self.0 == EMPTY }
118
119    #[expect(clippy::as_conversions)]
120    pub fn contains(&self, direction: Direction) -> bool {
121        self.0 & DIRECTIONS_FLAGS[usize::from(direction as u8)] != 0
122    }
123
124    pub fn iter(&self) -> DirectionsSetIter { DirectionsSetIter::new(*self) }
125}
126
127impl Display for DirectionsSet {
128    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
129        write!(f, "{{{}}}", self.iter().map(|d| format!("{d}")).join(", "))
130    }
131}
132
133impl Default for DirectionsSet {
134    fn default() -> Self { Self(EMPTY) }
135}
136
137/// Iterator over [`Direction`] in [`DirectionsSet`] in clockwise order.
138#[derive(Debug, Clone, Copy, PartialEq)]
139pub struct DirectionsSetIter {
140    src:     u8,
141    current: usize,
142}
143
144impl DirectionsSetIter {
145    fn new(src: DirectionsSet) -> DirectionsSetIter {
146        DirectionsSetIter {
147            src:     src.0,
148            current: 0,
149        }
150    }
151}
152
153impl Iterator for DirectionsSetIter {
154    type Item = Direction;
155
156    fn next(&mut self) -> Option<Direction> {
157        while self.current < Direction::COUNT {
158            let idx = self.current;
159            self.current += 1;
160            if self.src & DIRECTIONS_FLAGS[idx] != 0 {
161                return Some(FLAGS_DIRECTIONS[idx]);
162            }
163        }
164        None
165    }
166}
167
168impl IntoIterator for &DirectionsSet {
169    type Item = Direction;
170    type IntoIter = DirectionsSetIter;
171
172    fn into_iter(self) -> Self::IntoIter { DirectionsSetIter::new(*self) }
173}
174
175impl IntoIterator for DirectionsSet {
176    type Item = Direction;
177    type IntoIter = DirectionsSetIter;
178
179    fn into_iter(self) -> Self::IntoIter { DirectionsSetIter::new(self) }
180}
181
182impl<const N: usize> From<&[Direction; N]> for DirectionsSet {
183    fn from(data: &[Direction; N]) -> Self { Self::from_iter(data) }
184}
185
186impl<const N: usize> From<&mut [Direction; N]> for DirectionsSet {
187    fn from(data: &mut [Direction; N]) -> Self { Self::from_iter(data) }
188}
189
190impl<const N: usize> From<[Direction; N]> for DirectionsSet {
191    fn from(data: [Direction; N]) -> Self { Self::from_iter(data) }
192}
193
194impl From<&[Direction]> for DirectionsSet {
195    fn from(data: &[Direction]) -> Self { Self::from_iter(data) }
196}
197
198impl From<&mut [Direction]> for DirectionsSet {
199    fn from(data: &mut [Direction]) -> Self { Self::from_iter(data) }
200}
201
202impl FromIterator<Direction> for DirectionsSet {
203    #[expect(clippy::as_conversions)]
204    fn from_iter<I: IntoIterator<Item = Direction>>(iter: I) -> Self {
205        let mut retv = EMPTY;
206        for d in iter {
207            retv |= DIRECTIONS_FLAGS[usize::from(d as u8)];
208        }
209        Self(retv)
210    }
211}
212
213impl<'a> FromIterator<&'a Direction> for DirectionsSet {
214    fn from_iter<I: IntoIterator<Item = &'a Direction>>(iter: I) -> Self {
215        Self::from_iter(iter.into_iter().copied())
216    }
217}
218
219impl<'a> FromIterator<&'a mut Direction> for DirectionsSet {
220    fn from_iter<I: IntoIterator<Item = &'a mut Direction>>(iter: I) -> Self {
221        Self::from_iter(iter.into_iter().map(|d| *d))
222    }
223}
224
225impl<const N: usize> PartialEq<[Direction; N]> for DirectionsSet {
226    fn eq(&self, other: &[Direction; N]) -> bool { self.0 == DirectionsSet::from(other).0 }
227}
228
229impl<const N: usize> PartialEq<&[Direction; N]> for DirectionsSet {
230    fn eq(&self, other: &&[Direction; N]) -> bool { self.0 == DirectionsSet::from(*other).0 }
231}
232
233impl<const N: usize> PartialEq<&mut [Direction; N]> for DirectionsSet {
234    fn eq(&self, other: &&mut [Direction; N]) -> bool {
235        self.0 == DirectionsSet::from_iter(other.iter()).0
236    }
237}
238
239impl PartialEq<[Direction]> for DirectionsSet {
240    fn eq(&self, other: &[Direction]) -> bool { self.0 == DirectionsSet::from(other).0 }
241}
242
243impl PartialEq<&[Direction]> for DirectionsSet {
244    fn eq(&self, other: &&[Direction]) -> bool { self.0 == DirectionsSet::from(*other).0 }
245}
246
247impl PartialEq<&mut [Direction]> for DirectionsSet {
248    fn eq(&self, other: &&mut [Direction]) -> bool {
249        self.0 == DirectionsSet::from_iter(other.iter()).0
250    }
251}
252
253impl<const N: usize> PartialEq<DirectionsSet> for [Direction; N] {
254    fn eq(&self, other: &DirectionsSet) -> bool { DirectionsSet::from(self).0 == other.0 }
255}
256
257impl<const N: usize> PartialEq<DirectionsSet> for &[Direction; N] {
258    fn eq(&self, other: &DirectionsSet) -> bool {
259        DirectionsSet::from_iter(self.iter()).0 == other.0
260    }
261}
262
263impl<const N: usize> PartialEq<DirectionsSet> for &mut [Direction; N] {
264    fn eq(&self, other: &DirectionsSet) -> bool {
265        DirectionsSet::from_iter(self.iter()).0 == other.0
266    }
267}
268
269impl PartialEq<DirectionsSet> for [Direction] {
270    fn eq(&self, other: &DirectionsSet) -> bool { DirectionsSet::from(self).0 == other.0 }
271}
272
273impl PartialEq<DirectionsSet> for &[Direction] {
274    fn eq(&self, other: &DirectionsSet) -> bool {
275        DirectionsSet::from_iter(self.iter()).0 == other.0
276    }
277}
278
279impl PartialEq<DirectionsSet> for &mut [Direction] {
280    fn eq(&self, other: &DirectionsSet) -> bool {
281        DirectionsSet::from_iter(self.iter()).0 == other.0
282    }
283}
284
285#[cfg(test)]
286mod tests {
287    use super::*;
288
289    #[test]
290    fn it_calculates_opposite_direction() {
291        assert_eq!(Direction::Up.opposite(), Direction::Down);
292        assert_eq!(Direction::Down.opposite(), Direction::Up);
293        assert_eq!(Direction::Left.opposite(), Direction::Right);
294        assert_eq!(Direction::Right.opposite(), Direction::Left);
295        assert_eq!(Direction::NorthWest.opposite(), Direction::SouthEast);
296        assert_eq!(Direction::SouthEast.opposite(), Direction::NorthWest);
297        assert_eq!(Direction::NorthEast.opposite(), Direction::SouthWest);
298        assert_eq!(Direction::SouthWest.opposite(), Direction::NorthEast);
299    }
300
301    #[test]
302    fn it_is_empty_by_default() {
303        let neighbors = DirectionsSet::default();
304        assert!(neighbors.is_empty());
305    }
306
307    #[test]
308    fn it_can_be_constructed_from_and_compared_to_slice() {
309        let neighbors: DirectionsSet = [Direction::Up, Direction::Down].into();
310        assert_eq!(neighbors, [Direction::Up, Direction::Down]);
311    }
312
313    #[test]
314    fn test_order_in_source_slice_doesnt_matter() {
315        let neighbors1: DirectionsSet = [Direction::Up, Direction::Down].into();
316        let neighbors2: DirectionsSet = [Direction::Down, Direction::Up].into();
317        assert_eq!(neighbors1, neighbors2);
318    }
319
320    #[test]
321    fn it_can_be_iterated() {
322        let neighbors: DirectionsSet = [Direction::Up, Direction::Down].into();
323        let mut v: Vec<Direction> = Vec::new();
324        for d in neighbors {
325            v.push(d);
326        }
327        assert_eq!(v.len(), 2);
328        assert_eq!(v[0], Direction::Up);
329        assert_eq!(v[1], Direction::Down);
330    }
331
332    #[test]
333    fn it_iterates_in_clockwise_direction() {
334        let neighbors: DirectionsSet =
335            [Direction::NorthWest, Direction::Down, Direction::Up].into();
336        let mut v: Vec<Direction> = Vec::new();
337        for d in neighbors {
338            v.push(d);
339        }
340        assert_eq!(v.len(), 3);
341        assert_eq!(v[0], Direction::Up);
342        assert_eq!(v[1], Direction::Down);
343        assert_eq!(v[2], Direction::NorthWest);
344    }
345}