diff --git a/examples/tui/main.rs b/examples/tui/main.rs index d591a11..7d6361b 100644 --- a/examples/tui/main.rs +++ b/examples/tui/main.rs @@ -73,6 +73,7 @@ impl App { KeyCode::Char('r') => self.run_simulation(), KeyCode::Char('s') => self.stop_simulation(), KeyCode::Char('n') => self.cycle(), + KeyCode::Char('p') => self.perturb(), KeyCode::Char('?') => self.random_grid(), _ => {} } @@ -96,6 +97,10 @@ impl App { self.grid.update_states(); self.cycles = 0; } + + fn perturb(&mut self) { + self.grid.perturb_grid(0.05); + } } impl Widget for &App { @@ -112,6 +117,8 @@ impl Widget for &App { "".blue().bold(), " Regenerate".into(), "".blue().bold(), + " Perturb".into(), + "

".blue().bold(), " Population: ".into(), format!("{}", self.population).red().bold(), " Cycles: ".into(), @@ -146,20 +153,21 @@ mod tests { #[test] fn render() { let app = App::default(); - let mut buf = Buffer::empty(Rect::new(0, 0, 100, 4)); + let mut buf = Buffer::empty(Rect::new(0, 0, 111, 4)); app.render(buf.area, &mut buf); let mut expected = Buffer::with_lines(vec![ - "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Game of Life ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓", - "┃ ┃", - "┃ ┃", - "┗━━━━━━━━━ Quit Run Stop Single Cycle Regenerate Population: 0 Cycles: 0 ━━━━━━━━━┛", + "┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Game of Life ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓", + "┃ ┃", + "┃ ┃", + "┗━━━━━━━━━ Quit Run Stop Single Cycle Regenerate Perturb

Population: 0 Cycles: 0 ━━━━━━━━━┛", ]); let title_style = Style::new().bold(); let counter_style = Style::new().red().bold(); let key_style = Style::new().blue().bold(); + // Game of Life - expected.set_style(Rect::new(43, 0, 14, 1), title_style); + expected.set_style(Rect::new(48, 0, 14, 1), title_style); // expected.set_style(Rect::new(16, 3, 4, 1), key_style); // @@ -168,12 +176,14 @@ mod tests { expected.set_style(Rect::new(32, 3, 3, 1), key_style); // expected.set_style(Rect::new(48, 3, 3, 1), key_style); - // + //

expected.set_style(Rect::new(62, 3, 3, 1), key_style); + // + expected.set_style(Rect::new(73, 3, 3, 1), key_style); // 0 - expected.set_style(Rect::new(78, 3, 1, 1), counter_style); + expected.set_style(Rect::new(89, 3, 1, 1), counter_style); // 0 - expected.set_style(Rect::new(88, 3, 2, 1), counter_style); + expected.set_style(Rect::new(99, 3, 2, 1), counter_style); assert_eq!(buf, expected); } diff --git a/src/lib.rs b/src/lib.rs index cb98c6d..b8574fa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -270,6 +270,22 @@ impl Grid { } NeighbourState { alive, dead } } + /// Randomly flips one cell + pub fn rand_flip(&mut self) { + let i = fastrand::usize(0..self.cells.len()); + self.cells[i] = match self.cells[i] { + CellState::Alive(_) => CellState::Dead(self.dead_glyph), + CellState::Dead(_) => CellState::Alive(self.alive_glyph), + }; + } + /// Randomly flips p*cell.len() cells. + /// Can be use to keep the simulation interesting. + pub fn perturb_grid(&mut self, p: f32) { + let p = p.clamp(0.0, 1.0); + for _ in 0..((p * self.cells.len() as f32).floor() as i32) { + self.rand_flip(); + } + } } impl Default for Grid { @@ -396,4 +412,60 @@ mod tests { .collect(); assert_eq!(unexpected_states.len(), 0); } + + #[test] + fn test_rand_flip() { + // Flip the only cell + let mut g = Grid::new_empty(1, 1); + g.rand_flip(); + assert!( + g.try_get(Point { x: 0, y: 0 }) + == Some(&CellState::Alive(g.alive_glyph)) + ); + + // Flipping one cell should change the population by 1 + let mut g = Grid::new_random(2, 1); + let flip0 = g.calculate_population(); + g.rand_flip(); + let flip1 = g.calculate_population(); + assert!( + flip0.saturating_sub(flip1) == 1 + || flip1.saturating_sub(flip0) == 1 + ); + g.rand_flip(); + let flip2 = g.calculate_population(); + assert!( + flip2.saturating_sub(flip1) == 1 + || flip1.saturating_sub(flip2) == 1 + ); + } + + #[test] + fn test_perturb_grid() { + // Flip the only cell + let mut g = Grid::new_empty(1, 1); + g.perturb_grid(1.0); + assert!( + g.try_get(Point { x: 0, y: 0 }) + == Some(&CellState::Alive(g.alive_glyph)) + ); + + // Flip one cell + let mut g = Grid::new_empty(10, 10); + let perturb0 = g.calculate_population(); + g.perturb_grid(0.01); + let perturb1 = g.calculate_population(); + assert!( + perturb0.saturating_sub(perturb1) == 1 + || perturb1.saturating_sub(perturb0) == 1 + ); + + // Flip one cell + g.perturb_grid(0.01); + let perturb2 = g.calculate_population(); + assert!( + perturb1.saturating_sub(perturb2) == 1 + || perturb2.saturating_sub(perturb1) == 1 + ); + } }