life_backend/
position.rs

1use num_iter::range_inclusive;
2use num_traits::{Bounded, One, ToPrimitive};
3use std::convert::TryFrom;
4use std::fmt;
5use std::hash::Hash;
6use std::ops::{Add, Sub};
7
8/// A position of a cell.
9///
10/// `Position<T>` is a tuple `(T, T)`.
11/// The first field is the x-coordinate value of the position and the second field is the y-coordinate value of the potition.
12/// The type parameter `T` is used as the type of the x- and y-coordinate values of positions.
13///
14/// # Examples
15///
16/// ```
17/// use life_backend::Position;
18/// let pos = Position(2, 3);
19/// let pos_x = pos.0;
20/// let pos_y = pos.1;
21/// assert_eq!(pos_x, 2);
22/// assert_eq!(pos_y, 3);
23/// ```
24///
25#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
26pub struct Position<T>(pub T, pub T);
27
28impl<T> Position<T> {
29    /// Attempts to convert from `Position<U>` to `Position<T>`.
30    ///
31    /// This operation converts the type of the x- and y-coordinate values of the position from `U` to `T`.
32    /// If an error occurs in converting from `U` to `T`, returns that error.
33    ///
34    /// # Examples
35    ///
36    /// ```
37    /// use life_backend::Position;
38    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
39    /// let base: Position<usize> = Position(0, 0);
40    /// let pos = Position::<i16>::try_from(base)?;
41    /// # Ok(())
42    /// # }
43    /// ```
44    ///
45    pub fn try_from<U>(value: Position<U>) -> Result<Position<T>, T::Error>
46    where
47        T: TryFrom<U>,
48    {
49        Ok(Position(T::try_from(value.0)?, T::try_from(value.1)?))
50    }
51
52    /// Attempts to convert from `Position<T>` to `Position<U>`.
53    ///
54    /// `base.try_into::<U>()` is the same as `Position::<U>::try_from(base)`, see [`try_from()`].
55    ///
56    /// [`try_from()`]: #method.try_from
57    ///
58    /// # Examples
59    ///
60    /// ```
61    /// use life_backend::Position;
62    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
63    /// let base: Position<usize> = Position(0, 0);
64    /// let pos: Position<i16> = base.try_into()?;
65    /// # Ok(())
66    /// # }
67    /// ```
68    ///
69    #[inline]
70    pub fn try_into<U>(self) -> Result<Position<U>, U::Error>
71    where
72        U: TryFrom<T>,
73    {
74        Position::<U>::try_from(self)
75    }
76
77    /// Creates an owning iterator over neighbour positions of the self position in arbitrary order.
78    /// The neighbour positions are defined in [Moore neighbourhood](https://conwaylife.com/wiki/Moore_neighbourhood).
79    ///
80    /// # Examples
81    ///
82    /// ```
83    /// use std::collections::HashSet;
84    /// use life_backend::Position;
85    /// let pos = Position(2, 3);
86    /// let result: HashSet<_> = pos
87    ///     .moore_neighborhood_positions()
88    ///     .collect();
89    /// let expected: HashSet<_> = [(1, 2), (2, 2), (3, 2), (1, 3), (3, 3), (1, 4), (2, 4), (3, 4)]
90    ///     .iter()
91    ///     .copied()
92    ///     .map(|(x, y)| Position(x, y))
93    ///     .collect();
94    /// assert_eq!(result, expected);
95    /// ```
96    ///
97    pub fn moore_neighborhood_positions(&self) -> impl Iterator<Item = Self>
98    where
99        T: Copy + PartialOrd + Add<Output = T> + Sub<Output = T> + One + Bounded + ToPrimitive,
100    {
101        let Position(x, y) = *self;
102        let min = T::min_value();
103        let max = T::max_value();
104        let one = T::one();
105        let x_start = if x > min { x - one } else { x };
106        let x_stop = if x < max { x + one } else { x };
107        let y_start = if y > min { y - one } else { y };
108        let y_stop = if y < max { y + one } else { y };
109        range_inclusive(y_start, y_stop)
110            .flat_map(move |v| range_inclusive(x_start, x_stop).map(move |u| Position(u, v)))
111            .filter(move |&pos| pos != Position(x, y))
112    }
113}
114
115impl<T> fmt::Display for Position<T>
116where
117    T: fmt::Display,
118{
119    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120        write!(f, "({}, {})", self.0, self.1)?;
121        Ok(())
122    }
123}
124
125#[cfg(test)]
126mod tests {
127    use super::*;
128    use i32 as I;
129    use std::collections::HashSet;
130    #[test]
131    fn display() {
132        let target = Position(1, 2);
133        assert_eq!(format!("{target}"), "(1, 2)".to_string());
134    }
135    #[test]
136    fn try_from_infallible() {
137        let base: Position<i8> = Position(0, 0);
138        let target = Position::<i16>::try_from(base);
139        assert!(target.is_ok());
140    }
141    #[test]
142    fn try_from_fallible_no_error() {
143        let base: Position<i16> = Position(0, 0);
144        let target = Position::<i8>::try_from(base);
145        assert!(target.is_ok());
146    }
147    #[test]
148    fn try_from_fallible_with_error() {
149        let base: Position<i8> = Position(-1, 0);
150        let target = Position::<u8>::try_from(base);
151        assert!(target.is_err());
152    }
153    #[test]
154    fn try_into_infallible() {
155        let base: Position<i8> = Position(0, 0);
156        let target = base.try_into::<i16>();
157        assert!(target.is_ok());
158    }
159    #[test]
160    fn try_into_fallible_no_error() {
161        let base: Position<i16> = Position(0, 0);
162        let target = base.try_into::<i8>();
163        assert!(target.is_ok());
164    }
165    #[test]
166    fn try_into_fallible_with_error() {
167        let base: Position<i8> = Position(-1, 0);
168        let target = base.try_into::<u8>();
169        assert!(target.is_err());
170    }
171    #[test]
172    fn moore_neighborhood_positions_basic() {
173        let target: Position<I> = Position(0, 0);
174        let result: HashSet<_> = target.moore_neighborhood_positions().collect();
175        assert_eq!(
176            result,
177            [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)]
178                .iter()
179                .copied()
180                .map(|(x, y)| Position(x, y))
181                .collect::<HashSet<_>>()
182        );
183    }
184    #[test]
185    fn moore_neighborhood_positions_bounds() {
186        let min = I::min_value();
187        let max = I::max_value();
188        let zero: I = 0;
189        for (pos_tuple, expected_count) in [
190            ((min, min), 3),
191            ((min, zero), 5),
192            ((min, max), 3),
193            ((zero, min), 5),
194            ((zero, zero), 8),
195            ((zero, max), 5),
196            ((max, min), 3),
197            ((max, zero), 5),
198            ((max, max), 3),
199        ] {
200            let pos = Position(pos_tuple.0, pos_tuple.1);
201            assert_eq!(pos.moore_neighborhood_positions().count(), expected_count);
202        }
203    }
204}