life_backend/
board.rs

1use fnv::FnvBuildHasher;
2use num_iter::range_inclusive;
3use num_traits::{One, ToPrimitive, Zero};
4use std::collections::hash_set;
5use std::collections::HashSet;
6use std::fmt;
7use std::hash::Hash;
8use std::iter::FromIterator;
9
10use crate::{BoardRange, Position};
11
12/// A two-dimensional orthogonal grid map of live/dead cells.
13///
14/// The type parameter `T` is used as the type of the x- and y-coordinate values for each cell.
15///
16/// # Examples
17///
18/// ```
19/// use life_backend::{Board, Position};
20/// let pattern = [Position(0, 0), Position(1, 0), Position(2, 0), Position(1, 1)];
21/// let board: Board<i16> = pattern.iter().collect();
22/// assert_eq!(board.contains(&Position(0, 0)), true);
23/// assert_eq!(board.contains(&Position(0, 1)), false);
24/// assert_eq!(board.iter().count(), 4);
25/// ```
26///
27#[derive(Clone, PartialEq, Eq, Debug)]
28pub struct Board<T>(HashSet<Position<T>, FnvBuildHasher>)
29where
30    T: Eq + Hash;
31
32// Inherent methods
33
34impl<T> Board<T>
35where
36    T: Eq + Hash,
37{
38    /// Creates an empty board.
39    ///
40    /// # Examples
41    ///
42    /// ```
43    /// use life_backend::Board;
44    /// let board = Board::<i16>::new();
45    /// assert_eq!(board.iter().count(), 0);
46    /// ```
47    ///
48    #[inline]
49    pub fn new() -> Self {
50        Self(HashSet::default())
51    }
52
53    /// Returns `true` if the board contains the specified position.
54    ///
55    /// # Examples
56    ///
57    /// ```
58    /// use life_backend::{Board, Position};
59    /// let board = Board::<i16>::new();
60    /// assert_eq!(board.contains(&Position(0, 0)), false);
61    /// ```
62    ///
63    #[inline]
64    pub fn contains(&self, position: &Position<T>) -> bool {
65        self.0.contains(position)
66    }
67
68    /// Adds the specified position to the board.
69    ///
70    /// Returns whether the position was newly inserted, like as [`insert()`] of [`HashSet`].
71    ///
72    /// [`insert()`]: std::collections::HashSet::insert
73    /// [`HashSet`]: std::collections::HashSet
74    ///
75    /// # Examples
76    ///
77    /// ```
78    /// use life_backend::{Board, Position};
79    /// let mut board = Board::<i16>::new();
80    /// assert_eq!(board.insert(Position(0, 0)), true);
81    /// assert_eq!(board.contains(&Position(0, 0)), true);
82    /// ```
83    ///
84    #[inline]
85    pub fn insert(&mut self, position: Position<T>) -> bool {
86        self.0.insert(position)
87    }
88
89    /// Removes the specified position from the board.
90    ///
91    /// Returns whether the position was contained in the board, like as [`remove()`] of [`HashSet`].
92    ///
93    /// [`remove()`]: std::collections::HashSet::remove
94    /// [`HashSet`]: std::collections::HashSet
95    ///
96    /// # Examples
97    ///
98    /// ```
99    /// use life_backend::{Board, Position};
100    /// let mut board = Board::<i16>::new();
101    /// assert_eq!(board.insert(Position(0, 0)), true);
102    /// assert_eq!(board.remove(&Position(0, 0)), true);
103    /// assert_eq!(board.contains(&Position(0, 0)), false);
104    /// ```
105    ///
106    #[inline]
107    pub fn remove(&mut self, position: &Position<T>) -> bool {
108        self.0.remove(position)
109    }
110
111    /// Returns the minimum bounding box of all live cells on the board.
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// use life_backend::{Board, Position};
117    /// let mut board = Board::new();
118    /// board.insert(Position(-1, 2));
119    /// board.insert(Position(3, -2));
120    /// let bbox = board.bounding_box();
121    /// assert_eq!(bbox.x(), &(-1..=3));
122    /// assert_eq!(bbox.y(), &(-2..=2));
123    /// ```
124    ///
125    #[inline]
126    pub fn bounding_box(&self) -> BoardRange<T>
127    where
128        T: Copy + PartialOrd + Zero + One,
129    {
130        self.0.iter().collect::<BoardRange<_>>()
131    }
132
133    /// Removes all live cells in the board.
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use life_backend::{Board, Position};
139    /// let mut board = Board::<i16>::new();
140    /// board.insert(Position(0, 0));
141    /// board.clear();
142    /// assert_eq!(board.contains(&Position(0, 0)), false);
143    /// ```
144    ///
145    #[inline]
146    pub fn clear(&mut self) {
147        self.0.clear();
148    }
149
150    /// Retains only the live cell positions specified by the predicate, similar as [`retain()`] of [`HashSet`].
151    ///
152    /// [`retain()`]: std::collections::HashSet::retain
153    /// [`HashSet`]: std::collections::HashSet
154    ///
155    /// # Examples
156    ///
157    /// ```
158    /// use life_backend::{Board, Position};
159    /// let mut board = Board::<i16>::new();
160    /// board.insert(Position(0, 0));
161    /// board.insert(Position(1, 0));
162    /// board.insert(Position(0, 1));
163    /// board.retain(|&pos| pos.0 == pos.1);
164    /// assert_eq!(board.contains(&Position(0, 0)), true);
165    /// assert_eq!(board.contains(&Position(1, 0)), false);
166    /// assert_eq!(board.contains(&Position(0, 1)), false);
167    /// ```
168    ///
169    #[inline]
170    pub fn retain<F>(&mut self, pred: F)
171    where
172        F: FnMut(&Position<T>) -> bool,
173    {
174        self.0.retain(pred);
175    }
176}
177
178impl<'a, T> Board<T>
179where
180    T: Eq + Hash,
181{
182    /// Creates a non-owning iterator over the series of immutable live cell positions on the board in arbitrary order.
183    ///
184    /// # Examples
185    ///
186    /// ```
187    /// use std::collections::HashSet;
188    /// use life_backend::{Board, Position};
189    /// let mut board = Board::<i16>::new();
190    /// board.insert(Position(1, 0));
191    /// board.insert(Position(0, 1));
192    /// let result: HashSet<_> = board.iter().collect();
193    /// assert_eq!(result.len(), 2);
194    /// assert!(result.contains(&Position(1, 0)));
195    /// assert!(result.contains(&Position(0, 1)));
196    /// ```
197    ///
198    #[inline]
199    pub fn iter(&'a self) -> hash_set::Iter<'a, Position<T>> {
200        self.into_iter()
201    }
202}
203
204// Trait implementations
205
206impl<T> Default for Board<T>
207where
208    T: Eq + Hash,
209{
210    /// Returns the default value of the type, same as the return value of [`new()`].
211    ///
212    /// [`new()`]: #method.new
213    ///
214    #[inline]
215    fn default() -> Self {
216        Self::new()
217    }
218}
219
220impl<T> fmt::Display for Board<T>
221where
222    T: Eq + Hash + Copy + PartialOrd + Zero + One + ToPrimitive,
223{
224    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225        let bbox = self.bounding_box();
226        for y in range_inclusive(*bbox.y().start(), *bbox.y().end()) {
227            let line: String = range_inclusive(*bbox.x().start(), *bbox.x().end())
228                .map(|x| if self.contains(&Position(x, y)) { 'O' } else { '.' })
229                .collect();
230            writeln!(f, "{line}")?;
231        }
232        Ok(())
233    }
234}
235
236impl<'a, T> IntoIterator for &'a Board<T>
237where
238    T: Eq + Hash,
239{
240    type Item = &'a Position<T>;
241    type IntoIter = hash_set::Iter<'a, Position<T>>;
242
243    /// Creates a non-owning iterator over the series of immutable live cell positions on the board in arbitrary order.
244    ///
245    /// # Examples
246    ///
247    /// ```
248    /// use std::collections::HashSet;
249    /// use life_backend::{Board, Position};
250    /// let pattern = [Position(1, 0), Position(0, 1)];
251    /// let board: Board<i16> = pattern.iter().collect();
252    /// let result: HashSet<_> = (&board).into_iter().collect();
253    /// let expected: HashSet<_> = pattern.iter().collect();
254    /// assert_eq!(result, expected);
255    /// ```
256    ///
257    #[inline]
258    fn into_iter(self) -> Self::IntoIter {
259        self.0.iter()
260    }
261}
262
263impl<T> IntoIterator for Board<T>
264where
265    T: Eq + Hash,
266{
267    type Item = Position<T>;
268    type IntoIter = hash_set::IntoIter<Self::Item>;
269
270    /// Creates an owning iterator over the series of moved live cell positions on the board in arbitrary order.
271    ///
272    /// # Examples
273    ///
274    /// ```
275    /// use std::collections::HashSet;
276    /// use life_backend::{Board, Position};
277    /// let pattern = [Position(1, 0), Position(0, 1)];
278    /// let board: Board<i16> = pattern.iter().collect();
279    /// let result: HashSet<_> = board.into_iter().collect();
280    /// let expected: HashSet<_> = pattern.iter().copied().collect();
281    /// assert_eq!(result, expected);
282    /// ```
283    ///
284    #[inline]
285    fn into_iter(self) -> Self::IntoIter {
286        self.0.into_iter()
287    }
288}
289
290impl<'a, T> FromIterator<&'a Position<T>> for Board<T>
291where
292    T: Eq + Hash + Copy + 'a,
293{
294    /// Creates a value from a non-owning iterator over a series of [`&Position<T>`].
295    /// Each item in the series represents an immutable reference of a live cell position.
296    ///
297    /// [`&Position<T>`]: Position
298    ///
299    /// # Examples
300    ///
301    /// ```
302    /// use life_backend::{Board, Position};
303    /// let pattern = [Position(1, 0), Position(0, 1)];
304    /// let board: Board<i16> = pattern.iter().collect();
305    /// assert_eq!(board.contains(&Position(0, 0)), false);
306    /// assert_eq!(board.contains(&Position(1, 0)), true);
307    /// assert_eq!(board.contains(&Position(0, 1)), true);
308    /// assert_eq!(board.contains(&Position(1, 1)), false);
309    /// ```
310    ///
311    #[inline]
312    fn from_iter<U>(iter: U) -> Self
313    where
314        U: IntoIterator<Item = &'a Position<T>>,
315    {
316        Self::from_iter(iter.into_iter().copied())
317    }
318}
319
320impl<T> FromIterator<Position<T>> for Board<T>
321where
322    T: Eq + Hash,
323{
324    /// Creates a value from an owning iterator over a series of [`Position<T>`].
325    /// Each item in the series represents a moved live cell position.
326    ///
327    /// [`Position<T>`]: Position
328    ///
329    /// # Examples
330    ///
331    /// ```
332    /// use life_backend::{Board, Position};
333    /// let mut pattern = [Position(1, 0), Position(0, 1)];
334    /// let board: Board<i16> = pattern.into_iter().collect();
335    /// assert_eq!(board.contains(&Position(0, 0)), false);
336    /// assert_eq!(board.contains(&Position(1, 0)), true);
337    /// assert_eq!(board.contains(&Position(0, 1)), true);
338    /// assert_eq!(board.contains(&Position(1, 1)), false);
339    /// ```
340    ///
341    #[inline]
342    fn from_iter<U>(iter: U) -> Self
343    where
344        U: IntoIterator<Item = Position<T>>,
345    {
346        Self(HashSet::<Position<T>, _>::from_iter(iter))
347    }
348}
349
350impl<'a, T> Extend<&'a Position<T>> for Board<T>
351where
352    T: Eq + Hash + Copy + 'a,
353{
354    /// Extends the board with the contents of the specified non-owning iterator over the series of [`&Position<T>`].
355    /// Each item in the series represents an immutable reference of a live cell position.
356    ///
357    /// [`&Position<T>`]: Position
358    ///
359    /// # Examples
360    ///
361    /// ```
362    /// use life_backend::{Board, Position};
363    /// let mut board = Board::<i16>::new();
364    /// let pattern = [Position(1, 0), Position(0, 1)];
365    /// board.extend(pattern.iter());
366    /// assert_eq!(board.contains(&Position(0, 0)), false);
367    /// assert_eq!(board.contains(&Position(1, 0)), true);
368    /// assert_eq!(board.contains(&Position(0, 1)), true);
369    /// assert_eq!(board.contains(&Position(1, 1)), false);
370    /// ```
371    ///
372    #[inline]
373    fn extend<U>(&mut self, iter: U)
374    where
375        U: IntoIterator<Item = &'a Position<T>>,
376    {
377        self.0.extend(iter);
378    }
379}
380
381impl<T> Extend<Position<T>> for Board<T>
382where
383    T: Eq + Hash,
384{
385    /// Extends the board with the contents of the specified owning iterator over the series of [`Position<T>`].
386    /// Each item in the series represents a moved live cell position.
387    ///
388    /// [`Position<T>`]: Position
389    ///
390    /// # Examples
391    ///
392    /// ```
393    /// use life_backend::{Board, Position};
394    /// let mut board = Board::<i16>::new();
395    /// let pattern = [Position(1, 0), Position(0, 1)];
396    /// board.extend(pattern.into_iter());
397    /// assert_eq!(board.contains(&Position(0, 0)), false);
398    /// assert_eq!(board.contains(&Position(1, 0)), true);
399    /// assert_eq!(board.contains(&Position(0, 1)), true);
400    /// assert_eq!(board.contains(&Position(1, 1)), false);
401    /// ```
402    ///
403    #[inline]
404    fn extend<U>(&mut self, iter: U)
405    where
406        U: IntoIterator<Item = Position<T>>,
407    {
408        self.0.extend(iter);
409    }
410}
411
412// Unit tests
413
414#[cfg(test)]
415mod tests {
416    use super::*;
417    #[test]
418    fn default() {
419        let target = Board::<i16>::default();
420        let expected = Board::<i16>::new();
421        assert_eq!(target, expected);
422    }
423}