1use rustframe::matrix::{BoolMatrix, BoolOps, IntMatrix, Matrix};
9use rustframe::random::{rng, Rng};
10use std::{thread, time};
11
12const BOARD_SIZE: usize = 20; const MAX_FRAMES: u32 = 1000;
14
15const TICK_DURATION_MS: u64 = 0; const SKIP_FRAMES: u32 = 1;
17const PRINT_BOARD: bool = true; fn main() {
20 let args = std::env::args().collect::<Vec<String>>();
21 let debug_mode = args.contains(&"--debug".to_string());
22 let print_mode = if debug_mode { false } else { PRINT_BOARD };
23
24 let mut current_board =
25 BoolMatrix::from_vec(vec![false; BOARD_SIZE * BOARD_SIZE], BOARD_SIZE, BOARD_SIZE);
26
27 let primes = generate_primes((BOARD_SIZE * BOARD_SIZE) as i32);
28
29 add_simulated_activity(&mut current_board, BOARD_SIZE);
30
31 let mut generation_count: u32 = 0;
32 let mut previous_board_state: Option<BoolMatrix> = None;
33 let mut board_hashes = Vec::new();
34 let mut print_bool_int = 0;
35
36 loop {
37 if print_bool_int % SKIP_FRAMES == 0 {
38 print_board(¤t_board, generation_count, print_mode);
39
40 print_bool_int = 0;
41 } else {
42 print_bool_int += 1;
43 }
44 board_hashes.push(hash_board(¤t_board, primes.clone()));
45 if detect_stable_state(¤t_board, &previous_board_state) {
46 println!(
47 "\nStable state detected at generation {}.",
48 generation_count
49 );
50 add_simulated_activity(&mut current_board, BOARD_SIZE);
51 }
52 if detect_repeating_state(&mut board_hashes) {
53 println!(
54 "\nRepeating state detected at generation {}.",
55 generation_count
56 );
57 add_simulated_activity(&mut current_board, BOARD_SIZE);
58 }
59 if !¤t_board.any() {
60 println!("\nExtinction at generation {}.", generation_count);
61 add_simulated_activity(&mut current_board, BOARD_SIZE);
62 }
63
64 previous_board_state = Some(current_board.clone());
65
66 let next_board = game_of_life_next_frame(¤t_board);
67 current_board = next_board;
68
69 generation_count += 1;
70 thread::sleep(time::Duration::from_millis(TICK_DURATION_MS));
71
72 if (MAX_FRAMES > 0) && (generation_count > MAX_FRAMES) {
73 println!("\nReached generation limit.");
74 break;
75 }
76 }
77}
78
79fn print_board(board: &BoolMatrix, generation_count: u32, print_mode: bool) {
84 if !print_mode {
85 return;
86 }
87
88 print!("{}[2J", 27 as char);
89 println!("Conway's Game of Life - Generation: {}", generation_count);
90 let mut print_str = String::new();
91 print_str.push_str("+");
92 for _ in 0..board.cols() {
93 print_str.push_str("--");
94 }
95 print_str.push_str("+\n");
96 for r in 0..board.rows() {
97 print_str.push_str("| ");
98 for c in 0..board.cols() {
99 if board[(r, c)] {
100 print_str.push_str("██");
101 } else {
102 print_str.push_str(" ");
103 }
104 }
105 print_str.push_str(" |\n");
106 }
107 print_str.push_str("+");
108 for _ in 0..board.cols() {
109 print_str.push_str("--");
110 }
111 print_str.push_str("+\n\n");
112 print!("{}", print_str);
113
114 println!("Alive cells: {}", board.count());
115}
116
117fn get_shifted_neighbor_layer(game: &BoolMatrix, dr: isize, dc: isize) -> IntMatrix {
128 let rows = game.rows();
129 let cols = game.cols();
130
131 if rows == 0 || cols == 0 {
132 return IntMatrix::from_vec(vec![], 0, 0);
134 }
135
136 let mut shifted_layer = IntMatrix::from_vec(vec![0i32; rows * cols], rows, cols);
139
140 for r_target in 0..rows {
141 for c_target in 0..cols {
143 let r_source = r_target as isize - dr;
145 let c_source = c_target as isize - dc;
146
147 if r_source >= 0
149 && r_source < rows as isize
150 && c_source >= 0
151 && c_source < cols as isize
152 {
153 if game[(r_source as usize, c_source as usize)] {
155 shifted_layer[(r_target, c_target)] = 1; }
159 }
160 }
162 }
163 shifted_layer }
165
166pub fn game_of_life_next_frame(current_game: &BoolMatrix) -> BoolMatrix {
175 let rows = current_game.rows();
176 let cols = current_game.cols();
177
178 if rows == 0 && cols == 0 {
179 return BoolMatrix::from_vec(vec![], 0, 0); }
181 let neighbor_offsets: [(isize, isize); 8] = [
183 (-1, -1),
184 (-1, 0),
185 (-1, 1),
186 (0, -1),
187 (0, 1),
188 (1, -1),
189 (1, 0),
190 (1, 1),
191 ];
192
193 let (first_dr, first_dc) = neighbor_offsets[0];
194 let mut neighbor_counts = get_shifted_neighbor_layer(current_game, first_dr, first_dc);
195
196 for i in 1..neighbor_offsets.len() {
197 let (dr, dc) = neighbor_offsets[i];
198 let next_neighbor_layer = get_shifted_neighbor_layer(current_game, dr, dc);
199 neighbor_counts = neighbor_counts + next_neighbor_layer;
200 }
201
202 let has_2_neighbors = neighbor_counts.eq_elem(2);
203 let has_3_neighbors = neighbor_counts.eq_elem(3);
204
205 let has_2_or_3_neighbors = has_2_neighbors | has_3_neighbors.clone();
206
207 let survives = current_game & &has_2_or_3_neighbors;
208
209 let is_dead = !current_game;
210
211 let births = is_dead & &has_3_neighbors;
212
213 let next_frame_game = survives | births;
214
215 next_frame_game
216}
217
218pub fn generate_glider(board: &mut BoolMatrix, board_size: usize) {
219 let mut rng = rng();
223 let r_offset = rng.random_range(0..(board_size - 3));
224 let c_offset = rng.random_range(0..(board_size - 3));
225 if board.rows() >= r_offset + 3 && board.cols() >= c_offset + 3 {
226 board[(r_offset + 0, c_offset + 1)] = true;
227 board[(r_offset + 1, c_offset + 2)] = true;
228 board[(r_offset + 2, c_offset + 0)] = true;
229 board[(r_offset + 2, c_offset + 1)] = true;
230 board[(r_offset + 2, c_offset + 2)] = true;
231 }
232}
233
234pub fn generate_pulsar(board: &mut BoolMatrix, board_size: usize) {
235 let mut rng = rng();
239 let r_offset = rng.random_range(0..(board_size - 17));
240 let c_offset = rng.random_range(0..(board_size - 17));
241 if board.rows() >= r_offset + 17 && board.cols() >= c_offset + 17 {
242 let pulsar_coords = [
243 (2, 4),
244 (2, 5),
245 (2, 6),
246 (2, 10),
247 (2, 11),
248 (2, 12),
249 (4, 2),
250 (4, 7),
251 (4, 9),
252 (4, 14),
253 (5, 2),
254 (5, 7),
255 (5, 9),
256 (5, 14),
257 (6, 2),
258 (6, 7),
259 (6, 9),
260 (6, 14),
261 (7, 4),
262 (7, 5),
263 (7, 6),
264 (7, 10),
265 (7, 11),
266 (7, 12),
267 ];
268 for &(dr, dc) in pulsar_coords.iter() {
269 board[(r_offset + dr, c_offset + dc)] = true;
270 }
271 }
272}
273
274pub fn detect_stable_state(
275 current_board: &BoolMatrix,
276 previous_board_state: &Option<BoolMatrix>,
277) -> bool {
278 if let Some(ref prev_board) = previous_board_state {
279 return *prev_board == *current_board;
281 }
282 false
283}
284
285pub fn hash_board(board: &BoolMatrix, primes: Vec<i32>) -> usize {
286 let board_ints_vec = board
287 .data()
288 .iter()
289 .map(|&cell| if cell { 1 } else { 0 })
290 .collect::<Vec<i32>>();
291
292 let ints_board = Matrix::from_vec(board_ints_vec, board.rows(), board.cols());
293
294 let primes_board = Matrix::from_vec(primes, ints_board.rows(), ints_board.cols());
295
296 let result = ints_board * primes_board;
297 let result: i32 = result.data().iter().sum();
298 result as usize
299}
300
301pub fn detect_repeating_state(board_hashes: &mut Vec<usize>) -> bool {
302 if board_hashes.len() < 4 {
304 return false;
305 }
306 let mut result = false;
307 if (board_hashes[0] == board_hashes[2]) && (board_hashes[0] == board_hashes[2]) {
308 result = true;
309 }
310 board_hashes.remove(0);
312 result
313}
314
315pub fn add_simulated_activity(current_board: &mut BoolMatrix, board_size: usize) {
316 for _ in 0..20 {
317 generate_glider(current_board, board_size);
318 }
319
320 for _ in 0..10 {
322 generate_pulsar(current_board, board_size);
323 }
324}
325
326pub fn generate_primes(n: i32) -> Vec<i32> {
328 let mut primes = Vec::new();
330 let mut count = 0;
331 let mut num = 2; while count < n {
333 let mut is_prime = true;
334 for i in 2..=((num as f64).sqrt() as i32) {
335 if num % i == 0 {
336 is_prime = false;
337 break;
338 }
339 }
340 if is_prime {
341 primes.push(num);
342 count += 1;
343 }
344 num += 1;
345 }
346 primes
347}
348
349#[cfg(test)]
351mod tests {
352 use super::*;
353 use rustframe::matrix::{BoolMatrix, BoolOps}; #[test]
356 fn test_blinker_oscillator() {
357 let initial_data = vec![false, true, false, false, true, false, false, true, false];
358 let game1 = BoolMatrix::from_vec(initial_data.clone(), 3, 3);
359 let expected_frame2_data = vec![false, false, false, true, true, true, false, false, false];
360 let expected_game2 = BoolMatrix::from_vec(expected_frame2_data, 3, 3);
361 let game2 = game_of_life_next_frame(&game1);
362 assert_eq!(
363 game2.data(),
364 expected_game2.data(),
365 "Frame 1 to Frame 2 failed for blinker"
366 );
367 let expected_game3 = BoolMatrix::from_vec(initial_data, 3, 3);
368 let game3 = game_of_life_next_frame(&game2);
369 assert_eq!(
370 game3.data(),
371 expected_game3.data(),
372 "Frame 2 to Frame 3 failed for blinker"
373 );
374 }
375
376 #[test]
377 fn test_empty_board_remains_empty() {
378 let board_3x3_all_false = BoolMatrix::from_vec(vec![false; 9], 3, 3);
379 let next_frame = game_of_life_next_frame(&board_3x3_all_false);
380 assert_eq!(
381 next_frame.count(),
382 0,
383 "All-false board should result in all-false"
384 );
385 }
386
387 #[test]
388 fn test_zero_size_board() {
389 let board_0x0 = BoolMatrix::from_vec(vec![], 0, 0);
390 let next_frame = game_of_life_next_frame(&board_0x0);
391 assert_eq!(next_frame.rows(), 0);
392 assert_eq!(next_frame.cols(), 0);
393 assert!(
394 next_frame.data().is_empty(),
395 "0x0 board should result in 0x0 board"
396 );
397 }
398
399 #[test]
400 fn test_still_life_block() {
401 let block_data = vec![
402 true, true, false, false, true, true, false, false, false, false, false, false, false,
403 false, false, false,
404 ];
405 let game_block = BoolMatrix::from_vec(block_data.clone(), 4, 4);
406 let next_frame_block = game_of_life_next_frame(&game_block);
407 assert_eq!(
408 next_frame_block.data(),
409 game_block.data(),
410 "Block still life should remain unchanged"
411 );
412 }
413}