life_backend/
game.rs

1use num_traits::{Bounded, One, ToPrimitive, Zero};
2use std::fmt;
3use std::hash::Hash;
4use std::mem;
5use std::ops::{Add, Sub};
6
7use crate::{Board, Position, Rule};
8
9/// A representation of a game.
10///
11/// The type parameter `T` is used as the type of the x- and y-coordinate values for each cell.
12///
13/// The following operations are supported:
14///
15/// - Constructing from [`Rule`] and [`Board`]
16/// - Advancing a generation
17/// - Returning the current state information
18///
19/// # Examples
20///
21/// ```
22/// use life_backend::{Board, Game, Position, Rule};
23/// let rule = Rule::conways_life();
24/// let board: Board<_> = [(1, 0), (2, 1), (0, 2), (1, 2), (2, 2)] // Glider pattern
25///     .iter()
26///     .copied()
27///     .map(|(x, y)| Position(x, y))
28///     .collect();
29/// let mut game = Game::new(rule, board);
30/// game.advance();
31/// let bbox = game.board().bounding_box();
32/// assert_eq!(bbox.x(), &(0..=2));
33/// assert_eq!(bbox.y(), &(1..=3));
34/// ```
35///
36#[derive(Clone, PartialEq, Eq, Debug)]
37pub struct Game<T>
38where
39    T: Eq + Hash,
40{
41    rule: Rule,
42    curr_board: Board<T>,
43    prev_board: Board<T>,
44}
45
46// Inherent methods
47
48impl<T> Game<T>
49where
50    T: Eq + Hash,
51{
52    /// Creates from the specified rule and the board.
53    ///
54    /// # Examples
55    ///
56    /// ```
57    /// use life_backend::{Board, Game, Position, Rule};
58    /// let rule = Rule::conways_life();
59    /// let board: Board<_> = [Position(1, 0), Position(0, 1)].iter().collect();
60    /// let game = Game::new(rule, board);
61    /// ```
62    ///
63    pub fn new(rule: Rule, board: Board<T>) -> Self {
64        Self {
65            rule,
66            curr_board: board,
67            prev_board: Board::new(),
68        }
69    }
70
71    /// Returns the rule.
72    ///
73    /// # Examples
74    ///
75    /// ```
76    /// use life_backend::{Board, Game, Position, Rule};
77    /// let rule = Rule::conways_life();
78    /// let board: Board<_> = [Position(1, 0), Position(0, 1)].iter().collect();
79    /// let game = Game::new(rule.clone(), board);
80    /// assert_eq!(game.rule(), &rule);
81    /// ```
82    ///
83    #[inline]
84    pub const fn rule(&self) -> &Rule {
85        &self.rule
86    }
87
88    /// Returns the board.
89    ///
90    /// # Examples
91    ///
92    /// ```
93    /// use life_backend::{Board, Game, Position, Rule};
94    /// let rule = Rule::conways_life();
95    /// let board: Board<_> = [Position(1, 0), Position(0, 1)].iter().collect();
96    /// let game = Game::new(rule, board);
97    /// let board = game.board();
98    /// let bbox = board.bounding_box();
99    /// assert_eq!(bbox.x(), &(0..=1));
100    /// assert_eq!(bbox.y(), &(0..=1));
101    /// assert_eq!(board.contains(&Position(0, 0)), false);
102    /// assert_eq!(board.contains(&Position(1, 0)), true);
103    /// assert_eq!(board.contains(&Position(0, 1)), true);
104    /// assert_eq!(board.contains(&Position(1, 1)), false);
105    /// ```
106    ///
107    #[inline]
108    pub const fn board(&self) -> &Board<T> {
109        &self.curr_board
110    }
111
112    // Returns the count of live neighbours of the specified position.
113    fn live_neighbour_count(board: &Board<T>, position: &Position<T>) -> usize
114    where
115        T: Copy + PartialOrd + Add<Output = T> + Sub<Output = T> + One + Bounded + ToPrimitive,
116    {
117        position.moore_neighborhood_positions().filter(|pos| board.contains(pos)).count()
118    }
119
120    /// Advance the game by one generation.
121    ///
122    /// # Examples
123    ///
124    /// ```
125    /// use life_backend::{Board, Game, Position, Rule};
126    /// let rule = Rule::conways_life();
127    /// let board: Board<_> = [Position(0, 1), Position(1, 1), Position(2, 1)].iter().collect(); // Blinker pattern
128    /// let mut game = Game::new(rule, board);
129    /// game.advance();
130    /// let board = game.board();
131    /// let bbox = board.bounding_box();
132    /// assert_eq!(bbox.x(), &(1..=1));
133    /// assert_eq!(bbox.y(), &(0..=2));
134    /// assert_eq!(board.contains(&Position(1, 0)), true);
135    /// assert_eq!(board.contains(&Position(1, 1)), true);
136    /// assert_eq!(board.contains(&Position(1, 2)), true);
137    /// ```
138    ///
139    pub fn advance(&mut self)
140    where
141        T: Copy + PartialOrd + Add<Output = T> + Sub<Output = T> + One + Bounded + ToPrimitive,
142    {
143        mem::swap(&mut self.curr_board, &mut self.prev_board);
144        let prev_board = &self.prev_board;
145        let rule = &self.rule;
146        self.curr_board.clear();
147        self.curr_board.extend(
148            self.prev_board
149                .iter()
150                .flat_map(|pos| pos.moore_neighborhood_positions())
151                .filter(|pos| !prev_board.contains(pos)),
152        );
153        self.curr_board.retain(|pos| {
154            let count = Self::live_neighbour_count(prev_board, pos);
155            rule.is_born(count)
156        });
157        self.curr_board.extend(self.prev_board.iter().copied().filter(|pos| {
158            let count = Self::live_neighbour_count(prev_board, pos);
159            rule.is_survive(count)
160        }));
161    }
162}
163
164// Trait implementations
165
166impl<T> fmt::Display for Game<T>
167where
168    T: Eq + Hash + Copy + PartialOrd + Zero + One + ToPrimitive,
169{
170    #[inline]
171    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
172        self.board().fmt(f)
173    }
174}
175
176// Unit tests
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    #[test]
182    fn display() {
183        let rule = Rule::conways_life();
184        let board: Board<_> = [Position(1, 0), Position(0, 1)].iter().collect();
185        let target = Game::new(rule, board);
186        println!("{target}");
187    }
188}