life_backend/
boardrange.rs

1use num_traits::{One, Zero};
2use std::fmt;
3use std::iter::FromIterator;
4use std::ops::RangeInclusive;
5
6use crate::Position;
7
8/// A range on a board.
9///
10/// This range consists of four pieces of information: the minimum and maximum x-coordinate values and the minimum and maximum y-coordinate values.
11/// The type parameter `T` is used as the type of the x- and y-coordinate values.
12///
13/// # Examples
14///
15/// ```
16/// use life_backend::{BoardRange, Position};
17/// let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
18/// let range: BoardRange<_> = positions.iter().collect();
19/// let min_x = range.x().start();
20/// let max_x = range.x().end();
21/// let min_y = range.y().start();
22/// let max_y = range.y().end();
23/// assert_eq!(min_x, &0);
24/// assert_eq!(max_x, &2);
25/// assert_eq!(min_y, &0);
26/// assert_eq!(max_y, &1);
27/// ```
28///
29#[derive(Clone, PartialEq, Eq, Debug)]
30pub struct BoardRange<T>(RangeInclusive<T>, RangeInclusive<T>);
31
32// Inherent methods
33
34impl<T> BoardRange<T> {
35    /// Creates an empty range.
36    ///
37    /// # Examples
38    ///
39    /// ```
40    /// use life_backend::BoardRange;
41    /// let range = BoardRange::<i32>::new();
42    /// assert!(range.is_empty());
43    /// ```
44    ///
45    pub fn new() -> Self
46    where
47        T: Zero + One,
48    {
49        Self(T::one()..=T::zero(), T::one()..=T::zero())
50    }
51
52    // Implementation of public extend().
53    fn extend<U>(self, iter: U) -> Self
54    where
55        T: Copy + PartialOrd + Zero + One,
56        U: Iterator<Item = Position<T>>,
57    {
58        iter.fold(self, |acc, Position(x, y)| {
59            if acc.is_empty() {
60                Self(x..=x, y..=y)
61            } else {
62                let (range_x, range_y) = acc.into_inner();
63                let (x_start, x_end) = range_x.into_inner();
64                let (y_start, y_end) = range_y.into_inner();
65                Self(
66                    (if x_start < x { x_start } else { x })..=(if x_end > x { x_end } else { x }),
67                    (if y_start < y { y_start } else { y })..=(if y_end > y { y_end } else { y }),
68                )
69            }
70        })
71    }
72
73    // Implementation of public from_iter().
74    fn from_iter<U>(iter: U) -> Self
75    where
76        T: Copy + PartialOrd + Zero + One,
77        U: Iterator<Item = Position<T>>,
78    {
79        Self::new().extend(iter)
80    }
81
82    /// Returns the range on the x-coordinate.
83    ///
84    /// # Examples
85    ///
86    /// ```
87    /// use life_backend::{BoardRange, Position};
88    /// let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
89    /// let range: BoardRange<_> = positions.iter().collect();
90    /// assert_eq!(range.x(), &(0..=2));
91    /// ```
92    ///
93    #[inline]
94    pub const fn x(&self) -> &RangeInclusive<T> {
95        &self.0
96    }
97
98    /// Returns the range on the y-coordinate.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use life_backend::{BoardRange, Position};
104    /// let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
105    /// let range: BoardRange<_> = positions.iter().collect();
106    /// assert_eq!(range.y(), &(0..=1));
107    /// ```
108    ///
109    #[inline]
110    pub const fn y(&self) -> &RangeInclusive<T> {
111        &self.1
112    }
113
114    /// Destructures [`BoardRange`] into (range-x, range-y).
115    ///
116    /// # Examples
117    ///
118    /// ```
119    /// use life_backend::{BoardRange, Position};
120    /// let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
121    /// let range: BoardRange<_> = positions.iter().collect();
122    /// let (range_x, range_y) = range.into_inner();
123    /// assert_eq!(range_x, 0..=2);
124    /// assert_eq!(range_y, 0..=1);
125    /// ```
126    ///
127    #[inline]
128    pub fn into_inner(self) -> (RangeInclusive<T>, RangeInclusive<T>) {
129        (self.0, self.1)
130    }
131
132    /// Returns `true` if the range contains no area.
133    ///
134    /// If the range is empty, return values of methods are defined as the following:
135    ///
136    /// - `range.is_empty()` is `true`
137    /// - `range.x().is_empty()` and `range.y().is_empty()` are `true`
138    /// - `range.x().start()`, `range.x().end()`, `range.y().start()` and `range.y().end()` are unspecified
139    ///
140    /// # Examples
141    ///
142    /// ```
143    /// use life_backend::{BoardRange, Position};
144    /// let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
145    /// let range: BoardRange<_> = positions.iter().collect();
146    /// assert!(!range.is_empty());
147    /// ```
148    ///
149    #[inline]
150    pub fn is_empty(&self) -> bool
151    where
152        T: PartialOrd,
153    {
154        self.x().is_empty()
155    }
156}
157
158// Trait implementations
159
160impl<T> Default for BoardRange<T>
161where
162    T: Zero + One,
163{
164    /// Returns the default value of the type, same as the return value of [`new()`].
165    ///
166    /// [`new()`]: #method.new
167    ///
168    #[inline]
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174impl<T> fmt::Display for BoardRange<T>
175where
176    T: PartialOrd + fmt::Display,
177{
178    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
179        if self.is_empty() {
180            write!(f, "(empty)")?;
181        } else {
182            write!(
183                f,
184                "(x:[{}, {}], y:[{}, {}])",
185                self.x().start(),
186                self.x().end(),
187                self.y().start(),
188                self.y().end()
189            )?;
190        }
191        Ok(())
192    }
193}
194
195impl<'a, T> FromIterator<&'a Position<T>> for BoardRange<T>
196where
197    T: Copy + PartialOrd + Zero + One + 'a,
198{
199    /// Creates a value from a non-owning iterator over a series of [`&Position<T>`].
200    /// Each item in the series represents an immutable reference of a position to be contained to the range.
201    ///
202    /// [`&Position<T>`]: Position
203    ///
204    /// # Examples
205    ///
206    /// ```
207    /// use life_backend::{BoardRange, Position};
208    /// let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
209    /// let range: BoardRange<_> = positions.iter().collect();
210    /// assert!(!range.is_empty());
211    /// assert_eq!(range.x(), &(0..=2));
212    /// assert_eq!(range.y(), &(0..=1));
213    /// ```
214    ///
215    #[inline]
216    fn from_iter<U>(iter: U) -> Self
217    where
218        U: IntoIterator<Item = &'a Position<T>>,
219    {
220        Self::from_iter(iter.into_iter().copied())
221    }
222}
223
224impl<T> FromIterator<Position<T>> for BoardRange<T>
225where
226    T: Copy + PartialOrd + Zero + One,
227{
228    /// Creates a value from an owning iterator over a series of [`Position<T>`].
229    /// Each item in the series represents a moved position to be contained to the range.
230    ///
231    /// [`Position<T>`]: Position
232    ///
233    /// # Examples
234    ///
235    /// ```
236    /// use life_backend::{BoardRange, Position};
237    /// let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
238    /// let range: BoardRange<_> = positions.into_iter().collect();
239    /// assert!(!range.is_empty());
240    /// assert_eq!(range.x(), &(0..=2));
241    /// assert_eq!(range.y(), &(0..=1));
242    /// ```
243    ///
244    #[inline]
245    fn from_iter<U>(iter: U) -> Self
246    where
247        U: IntoIterator<Item = Position<T>>,
248    {
249        Self::from_iter(iter.into_iter())
250    }
251}
252
253impl<'a, T> Extend<&'a Position<T>> for BoardRange<T>
254where
255    T: Copy + PartialOrd + Zero + One + 'a,
256{
257    /// Extends the range with the contents of the specified non-owning iterator over the series of [`&Position<T>`].
258    /// Each item in the series represents an immutable reference of a position.
259    ///
260    /// [`&Position<T>`]: Position
261    ///
262    /// # Examples
263    ///
264    /// ```
265    /// use life_backend::{BoardRange, Position};
266    /// let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
267    /// let mut range = BoardRange::new();
268    /// range.extend(positions.iter());
269    /// assert!(!range.is_empty());
270    /// assert_eq!(range.x(), &(0..=2));
271    /// assert_eq!(range.y(), &(0..=1));
272    /// ```
273    ///
274    fn extend<U>(&mut self, iter: U)
275    where
276        U: IntoIterator<Item = &'a Position<T>>,
277    {
278        *self = self.clone().extend(iter.into_iter().copied())
279    }
280}
281
282impl<T> Extend<Position<T>> for BoardRange<T>
283where
284    T: Copy + PartialOrd + Zero + One,
285{
286    /// Extends the range with the contents of the specified owning iterator over the series of [`Position<T>`].
287    /// Each item in the series represents a moved position.
288    ///
289    /// [`Position<T>`]: Position
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// use life_backend::{BoardRange, Position};
295    /// let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
296    /// let mut range = BoardRange::new();
297    /// range.extend(positions.into_iter());
298    /// assert!(!range.is_empty());
299    /// assert_eq!(range.x(), &(0..=2));
300    /// assert_eq!(range.y(), &(0..=1));
301    /// ```
302    ///
303    fn extend<U>(&mut self, iter: U)
304    where
305        U: IntoIterator<Item = Position<T>>,
306    {
307        *self = self.clone().extend(iter.into_iter())
308    }
309}
310
311// Unit tests
312
313#[cfg(test)]
314mod tests {
315    use super::*;
316    #[test]
317    fn default() {
318        let target = BoardRange::<i32>::default();
319        let expected = BoardRange::<i32>::new();
320        assert_eq!(target, expected);
321    }
322    #[test]
323    fn display_empty() {
324        let target = BoardRange::<i32>::new();
325        assert_eq!(format!("{target}"), "(empty)".to_string());
326    }
327    #[test]
328    fn display_notempty() {
329        let positions = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
330        let target: BoardRange<_> = positions.iter().collect();
331        assert_eq!(format!("{target}"), "(x:[0, 2], y:[0, 1])".to_string());
332    }
333}