diff --git a/examples/game_of_life/src/cell.rs b/examples/game_of_life/src/cell.rs deleted file mode 100644 index a30b3de1523..00000000000 --- a/examples/game_of_life/src/cell.rs +++ /dev/null @@ -1,52 +0,0 @@ -#[derive(Clone, Copy, PartialEq, Eq)] -pub enum State { - Alive, - Dead, -} - -#[derive(Clone, Copy)] -pub struct Cellule { - pub state: State, -} - -impl Cellule { - pub fn new_dead() -> Self { - Self { state: State::Dead } - } - - pub fn set_alive(&mut self) { - self.state = State::Alive; - } - - pub fn set_dead(&mut self) { - self.state = State::Dead; - } - - pub fn is_alive(self) -> bool { - self.state == State::Alive - } - - pub fn toggle(&mut self) { - if self.is_alive() { - self.set_dead() - } else { - self.set_alive() - } - } - - pub fn count_alive_neighbors(neighbors: &[Self]) -> usize { - neighbors.iter().filter(|n| n.is_alive()).count() - } - - pub fn alone(neighbors: &[Self]) -> bool { - Self::count_alive_neighbors(neighbors) < 2 - } - - pub fn overpopulated(neighbors: &[Self]) -> bool { - Self::count_alive_neighbors(neighbors) > 3 - } - - pub fn can_be_revived(neighbors: &[Self]) -> bool { - Self::count_alive_neighbors(neighbors) == 3 - } -} diff --git a/examples/game_of_life/src/conway.rs b/examples/game_of_life/src/conway.rs new file mode 100644 index 00000000000..cd69249899d --- /dev/null +++ b/examples/game_of_life/src/conway.rs @@ -0,0 +1,69 @@ +use rand::Rng; + +pub struct Conway { + pub cellules: Vec, + pub width: usize, + pub height: usize, +} + +impl Conway { + pub fn new(width: usize, height: usize) -> Self { + Self { + cellules: vec![false; width * height], + width, + height, + } + } + + pub fn alive(&self, row: usize, col: usize) -> bool { + self.cellules[row * self.width + col] + } + + pub fn toggle(&mut self, row: usize, col: usize) { + let i = row * self.width + col; + self.cellules[i] = !self.cellules[i]; + } + + pub fn random_mutate(&mut self) { + let mut rng = rand::thread_rng(); + self.cellules.iter_mut().for_each(|c| *c = rng.gen()); + } + + pub fn reset(&mut self) { + self.cellules.iter_mut().for_each(|c| *c = false); + } + + pub fn step(&mut self) { + let mut to_toggle = Vec::new(); + for row in 0..self.height { + for col in 0..self.width { + let n = self.live_neighbours(row as isize, col as isize); + if (self.alive(row, col) && (n <= 1 || n > 3)) || (!self.alive(row, col) && n == 3) + { + to_toggle.push((row, col)); + } + } + } + to_toggle + .iter() + .for_each(|(row, col)| self.toggle(*row, *col)); + } + + fn live_neighbours(&self, row: isize, col: isize) -> usize { + (-1..=1) + .flat_map(|r| (-1..=1).map(move |c| (r, c))) + .filter(|&(r, c)| (r, c) != (0, 0)) + .filter(|&(r, c)| self.cellules[self.row_col_as_idx(row + r, col + c)]) + .count() + } + + fn row_col_as_idx(&self, row: isize, col: isize) -> usize { + let row = wrap(row, self.height as isize); + let col = wrap(col, self.width as isize); + row * self.width + col + } +} + +fn wrap(idx: isize, range: isize) -> usize { + ((idx % range + range) % range) as usize // because % has sign of dividend +} diff --git a/examples/game_of_life/src/main.rs b/examples/game_of_life/src/main.rs index 25eb8bcfab5..64a778bf531 100644 --- a/examples/game_of_life/src/main.rs +++ b/examples/game_of_life/src/main.rs @@ -1,10 +1,8 @@ -use cell::Cellule; use gloo::timers::callback::Interval; -use rand::Rng; use yew::html::Scope; use yew::{classes, html, Component, Context, Html}; -mod cell; +mod conway; pub enum Msg { Random, @@ -12,174 +10,98 @@ pub enum Msg { Step, Reset, Stop, - ToggleCellule(usize), + ToggleCellule((usize, usize)), Tick, } pub struct App { active: bool, - cellules: Vec, - cellules_width: usize, - cellules_height: usize, + conway: conway::Conway, _interval: Interval, } impl App { - pub fn random_mutate(&mut self) { - for cellule in self.cellules.iter_mut() { - if rand::thread_rng().gen() { - cellule.set_alive(); - } else { - cellule.set_dead(); - } - } - } - - fn reset(&mut self) { - for cellule in self.cellules.iter_mut() { - cellule.set_dead(); - } - } - - fn step(&mut self) { - let mut to_dead = Vec::new(); - let mut to_live = Vec::new(); - for row in 0..self.cellules_height { - for col in 0..self.cellules_width { - let neighbors = self.neighbors(row as isize, col as isize); - - let current_idx = self.row_col_as_idx(row as isize, col as isize); - if self.cellules[current_idx].is_alive() { - if Cellule::alone(&neighbors) || Cellule::overpopulated(&neighbors) { - to_dead.push(current_idx); - } - } else if Cellule::can_be_revived(&neighbors) { - to_live.push(current_idx); - } - } - } - to_dead - .iter() - .for_each(|idx| self.cellules[*idx].set_dead()); - to_live - .iter() - .for_each(|idx| self.cellules[*idx].set_alive()); - } - - fn neighbors(&self, row: isize, col: isize) -> [Cellule; 8] { - [ - self.cellules[self.row_col_as_idx(row + 1, col)], - self.cellules[self.row_col_as_idx(row + 1, col + 1)], - self.cellules[self.row_col_as_idx(row + 1, col - 1)], - self.cellules[self.row_col_as_idx(row - 1, col)], - self.cellules[self.row_col_as_idx(row - 1, col + 1)], - self.cellules[self.row_col_as_idx(row - 1, col - 1)], - self.cellules[self.row_col_as_idx(row, col - 1)], - self.cellules[self.row_col_as_idx(row, col + 1)], - ] - } - - fn row_col_as_idx(&self, row: isize, col: isize) -> usize { - let row = wrap(row, self.cellules_height as isize); - let col = wrap(col, self.cellules_width as isize); - - row * self.cellules_width + col - } - - fn view_cellule(&self, idx: usize, cellule: &Cellule, link: &Scope) -> Html { - let cellule_status = { - if cellule.is_alive() { - "cellule-live" - } else { - "cellule-dead" - } + fn view_cellule(&self, row: usize, col: usize, link: &Scope) -> Html { + let status = if self.conway.alive(row, col) { + "cellule-live" + } else { + "cellule-dead" }; html! { -
+
} } } + impl Component for App { type Message = Msg; type Properties = (); fn create(ctx: &Context) -> Self { let callback = ctx.link().callback(|_| Msg::Tick); - let interval = Interval::new(200, move || callback.emit(())); - - let (cellules_width, cellules_height) = (53, 40); Self { active: false, - cellules: vec![Cellule::new_dead(); cellules_width * cellules_height], - cellules_width, - cellules_height, - _interval: interval, + conway: conway::Conway::new(53, 40), + _interval: Interval::new(200, move || callback.emit(())), } } fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { + let mut render = true; match msg { Msg::Random => { - self.random_mutate(); + self.conway.random_mutate(); log::info!("Random"); - true } Msg::Start => { self.active = true; log::info!("Start"); - false + render = false; } Msg::Step => { - self.step(); - true + self.conway.step(); } Msg::Reset => { - self.reset(); + self.conway.reset(); log::info!("Reset"); - true } Msg::Stop => { self.active = false; log::info!("Stop"); - false - } - Msg::ToggleCellule(idx) => { - let cellule = self.cellules.get_mut(idx).unwrap(); - cellule.toggle(); - true + render = false; } + Msg::ToggleCellule((row, col)) => self.conway.toggle(row, col), Msg::Tick => { if self.active { - self.step(); - true + self.conway.step(); } else { - false + render = false; } } } + render } fn view(&self, ctx: &Context) -> Html { - let cell_rows = - self.cellules - .chunks(self.cellules_width) - .enumerate() - .map(|(y, cellules)| { - let idx_offset = y * self.cellules_width; - - let cells = cellules - .iter() - .enumerate() - .map(|(x, cell)| self.view_cellule(idx_offset + x, cell, ctx.link())); - html! { -
- { for cells } -
- } - }); + let cell_rows = self + .conway + .cellules + .chunks(self.conway.width) + .enumerate() + .map(|(row, cellules)| { + let cells = cellules + .iter() + .enumerate() + .map(|(col, _)| self.view_cellule(row, col, ctx.link())); + html! { +
+ { for cells } +
+ } + }); html! {
@@ -212,17 +134,6 @@ impl Component for App { } } -fn wrap(coord: isize, range: isize) -> usize { - let result = if coord < 0 { - coord + range - } else if coord >= range { - coord - range - } else { - coord - }; - result as usize -} - fn main() { wasm_logger::init(wasm_logger::Config::default()); log::trace!("Initializing yew...");