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}