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}