use crate::component::grid::Grid; use crate::governor::Governor; use crate::Slide; use eframe::egui::style::Margin; use eframe::egui::{Color32, Frame, Ui}; use rand::Rng; /// Conway's Game of Life, etc. pub struct Automata { /// Persistent state of the grid. life: Grid, /// For knowing when to advance the simulation. governor: Governor, } impl Default for Automata { fn default() -> Self { let mut life = Grid::default(); // Unique identifier for the grid. life.name = "life"; // Fill grid with random cells. for y in 0..life.size_cells { for x in 0..life.size_cells { if rand::thread_rng().gen_bool(0.5) { life.set_fill(x, y, Color32::BLACK); } } } // This would replace the grid with a single oscillator. /* life.clear_fill(); life.set_fill(5, 5, Color32::BLACK); life.set_fill(6, 5, Color32::BLACK); life.set_fill(7, 5, Color32::BLACK); */ Self { life, governor: Governor::default(), } } } impl Slide for Automata { fn show(&mut self, ui: &mut Ui) { Frame::none().margin(Margin::same(20.0)).show(ui, |ui| { ui.vertical_centered(|ui| { ui.heading("Cellular Automata"); }); }); // Need to continuously animate the grid, or at least poll the governor. ui.ctx().request_repaint(); if self.governor.ready(ui.ctx().input().time, 0.2) { self.life = conways_game_of_life(&self.life); } self.life.show(ui.ctx()); } } /// Run one iteration of Conway's Game of Life on the grid, producing a new grid. fn conways_game_of_life(grid: &Grid) -> Grid { let mut new = grid.clone(); new.clear_fill(); for cy in 0..grid.size_cells { for cx in 0..grid.size_cells { let mut neighbors = 0; for dy in -1..=1 { for dx in -1..=1 { let (x, y) = match (cx.checked_add_signed(dx), cy.checked_add_signed(dy)) { (Some(x), Some(y)) if x < grid.size_cells && y < grid.size_cells => (x, y), _ => continue, }; if x == cx && y == cy { // Don't consider self. continue; } if grid.fill(x, y).a() > 0 { neighbors += 1; } } } let mut alive = grid.fill(cx, cy).a() > 0; alive = match (alive, neighbors) { (false, 3) => true, (false, _) => false, (true, 2) | (true, 3) => true, (true, _) => false, }; new.set_fill( cx, cy, if alive { Color32::BLACK } else { Color32::TRANSPARENT }, ); } } new }