]> git.huck.website - cellseq.git/commitdiff
refactor: life + state + map into one struct
authorHuck Boles <huck@huck.website>
Tue, 20 Jun 2023 16:57:58 +0000 (11:57 -0500)
committerHuck Boles <huck@huck.website>
Tue, 20 Jun 2023 16:57:58 +0000 (11:57 -0500)
src/lib.rs
src/life.rs [deleted file]
src/map.rs
src/state.rs [deleted file]

index 03d57cf696a187c4ebad049c5a18dce2927bda1c..ddad6c2d06fa4ddbdf6ecc7140fa761abd7258f1 100644 (file)
@@ -3,15 +3,17 @@ use iced::theme::{self, Theme};
 use iced::time;
 use iced::widget::{button, column, container, row, slider, text};
 use iced::{Alignment, Application, Command, Element, Length, Point, Subscription};
+
+use rustc_hash::{FxHashMap, FxHashSet};
 use std::time::{Duration, Instant};
 
-mod life;
 mod map;
 mod mask;
-mod state;
 
-use life::*;
 use map::*;
+use mask::*;
+
+pub type CellMap = FxHashSet<Cell>;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
 pub struct Cell {
@@ -49,16 +51,15 @@ impl Cell {
 #[derive(Default)]
 pub struct CellSeq {
     map: Map,
+    mask: Mask,
     is_playing: bool,
-    queued_ticks: usize,
     bpm: usize,
-    next_speed: Option<usize>,
-    version: usize,
 }
 
 #[derive(Debug, Clone)]
 pub enum Message {
-    Grid(map::Message, usize),
+    Map(map::Message),
+    Mask(mask::Message),
     Tick(Instant),
     TogglePlayback,
     Randomize,
@@ -85,41 +86,23 @@ impl Application for CellSeq {
     }
 
     fn title(&self) -> String {
-        String::from("Game of Life - Iced")
+        String::from("cellseq")
     }
 
     fn update(&mut self, message: Message) -> Command<Message> {
         match message {
-            Message::Grid(message, version) => {
-                if version == self.version {
-                    self.map.update(message);
-                }
-            }
+            Message::Map(message) => self.map.update(message),
+            Message::Mask(message) => self.mask.update(message),
             Message::Tick(_) => {
-                self.queued_ticks = (self.queued_ticks + 1).min(self.bpm);
-
-                if let Some(task) = self.map.tick(self.queued_ticks) {
-                    if let Some(speed) = self.next_speed.take() {
-                        self.bpm = speed;
-                    }
-
-                    self.queued_ticks = 0;
-
-                    let version = self.version;
-
-                    return Command::perform(task, move |message| Message::Grid(message, version));
-                }
+                return Command::perform(self.map.tick(), Message::Map);
             }
             Message::TogglePlayback => {
                 self.is_playing = !self.is_playing;
             }
-            Message::Clear => {
-                self.map.clear();
-                self.version += 1;
-            }
             Message::SpeedChanged(bpm) => {
                 self.bpm = bpm;
             }
+            Message::Clear => self.map.clear(),
             Message::Randomize => self.map.randomize(),
             Message::Reset => self.map.reset(),
             Message::Save => self.map.save(),
@@ -137,10 +120,9 @@ impl Application for CellSeq {
     }
 
     fn view(&self) -> Element<Message> {
-        let version = self.version;
-        let selected_speed = self.next_speed.unwrap_or(self.bpm);
-        let controls = view_controls(self.is_playing, selected_speed);
-        let map = self.map.view().map(move |m| Message::Grid(m, version));
+        let bpm = self.bpm;
+        let controls = view_controls(self.is_playing, bpm);
+        let map = self.map.view().map(Message::Map);
 
         let content = column![controls, map,];
 
diff --git a/src/life.rs b/src/life.rs
deleted file mode 100644 (file)
index f7489f4..0000000
+++ /dev/null
@@ -1,95 +0,0 @@
-use super::*;
-
-use itertools::Itertools;
-use rand::random;
-use rustc_hash::{FxHashMap, FxHashSet};
-
-#[derive(Clone, Default)]
-pub struct Life {
-    seed: FxHashSet<Cell>,
-    cells: FxHashSet<Cell>,
-}
-
-impl Life {
-    pub fn contains(&self, cell: &Cell) -> bool {
-        self.cells.contains(cell)
-    }
-
-    pub fn populate(&mut self, cell: Cell) {
-        self.cells.insert(cell);
-    }
-
-    pub fn unpopulate(&mut self, cell: &Cell) {
-        self.cells.remove(cell);
-    }
-
-    pub fn clear(&mut self) {
-        self.cells = FxHashSet::default();
-    }
-
-    pub fn reset(&mut self) {
-        self.cells = self.seed.clone();
-    }
-
-    pub fn save_state(&mut self) {
-        self.seed = self.cells.clone();
-    }
-
-    pub fn randomize(&mut self) {
-        self.cells.clear();
-        for (i, j) in (-32..=32).cartesian_product(-32..=32) {
-            if random::<f32>() > 0.5 {
-                self.populate(Cell { i, j })
-            }
-        }
-        self.seed = self.cells.clone();
-    }
-
-    pub fn tick(&mut self) {
-        let mut adjacent_life = FxHashMap::default();
-
-        for cell in &self.cells {
-            adjacent_life.entry(*cell).or_insert(0);
-
-            for neighbor in Cell::neighbors(*cell) {
-                let amount = adjacent_life.entry(neighbor).or_insert(0);
-
-                *amount += 1;
-            }
-        }
-
-        for (cell, amount) in adjacent_life.iter() {
-            match amount {
-                2 => {}
-                3 => {
-                    self.cells.insert(*cell);
-                }
-                _ => {
-                    self.cells.remove(cell);
-                }
-            }
-        }
-    }
-
-    pub fn iter(&self) -> impl Iterator<Item = &Cell> {
-        self.cells.iter()
-    }
-}
-
-impl std::iter::FromIterator<Cell> for Life {
-    fn from_iter<I: IntoIterator<Item = Cell>>(iter: I) -> Self {
-        let cells: FxHashSet<Cell> = iter.into_iter().collect();
-        Life {
-            seed: cells.clone(),
-            cells,
-        }
-    }
-}
-
-impl std::fmt::Debug for Life {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        f.debug_struct("Life")
-            .field("cells", &self.cells.len())
-            .finish()
-    }
-}
index 6e62a5f80d0ba961d82a74521b991cb4794eb9e0..9ace573af5291f62f09fa24cf98ddf0da33bd81c 100644 (file)
@@ -1,15 +1,20 @@
-use iced::mouse;
+use iced::mouse::{self, Button::Left, Event::ButtonPressed};
 use iced::widget::canvas;
 use iced::widget::canvas::event::{self, Event};
 use iced::widget::canvas::{Cache, Canvas, Cursor, Geometry, Path};
 use iced::{Color, Element, Length, Point, Rectangle, Size, Theme};
-use itertools::Itertools;
-use std::future::Future;
 
-use crate::{state::State, Cell, Life};
+use super::*;
+
+use itertools::Itertools;
+use rand::random;
+use rustc_hash::FxHashMap;
+use std::{fmt::Debug, future::Future};
 
+#[derive(Default, Debug)]
 pub struct Map {
-    state: State,
+    seed: CellMap,
+    cells: CellMap,
     life_cache: Cache,
 }
 
@@ -17,37 +22,55 @@ pub struct Map {
 pub enum Message {
     Populate(Cell),
     Unpopulate(Cell),
-    Ticked(Life),
-}
-
-impl Default for Map {
-    fn default() -> Self {
-        Self {
-            state: State::with_life(Life::default()),
-            life_cache: Cache::default(),
-        }
-    }
+    Ticked(CellMap),
 }
 
 impl Map {
-    pub fn tick(&mut self, amount: usize) -> Option<impl Future<Output = Message>> {
-        let tick = self.state.tick(amount)?;
+    pub fn tick(&self) -> impl Future<Output = Message> {
+        let mut life = self.cells.clone();
+        let mut counts = FxHashMap::default();
+
+        let tick = tokio::task::spawn_blocking(move || {
+            for cell in &life {
+                counts.entry(*cell).or_insert(0);
+
+                for neighbor in Cell::neighbors(*cell) {
+                    let amount = counts.entry(neighbor).or_insert(0);
+
+                    *amount += 1;
+                }
+            }
+
+            for (cell, amount) in counts.iter() {
+                match amount {
+                    2 => {}
+                    3 => {
+                        life.insert(*cell);
+                    }
+                    _ => {
+                        life.remove(cell);
+                    }
+                }
+            }
 
-        Some(async move { Message::Ticked(tick.await) })
+            life
+        });
+
+        async move { Message::Ticked(tick.await.unwrap()) }
     }
 
     pub fn update(&mut self, message: Message) {
         match message {
             Message::Populate(cell) => {
-                self.state.populate(cell);
+                self.cells.insert(cell);
                 self.life_cache.clear();
             }
             Message::Unpopulate(cell) => {
-                self.state.unpopulate(&cell);
+                self.cells.remove(&cell);
                 self.life_cache.clear();
             }
             Message::Ticked(life) => {
-                self.state.update(life);
+                self.cells = life;
                 self.life_cache.clear();
             }
         }
@@ -61,21 +84,32 @@ impl Map {
     }
 
     pub fn clear(&mut self) {
-        self.state.clear();
+        self.cells.clear();
         self.life_cache.clear();
     }
 
     pub fn reset(&mut self) {
-        self.state.reset();
+        self.cells = self.seed.clone();
+        self.life_cache.clear();
     }
 
     pub fn save(&mut self) {
-        self.state.save();
+        self.seed = self.cells.clone();
     }
 
     pub fn randomize(&mut self) {
+        self.cells.clear();
+        for (i, j) in (-32..=32).cartesian_product(-32..=32) {
+            if random::<f32>() > 0.5 {
+                self.cells.insert(Cell { i, j });
+            }
+        }
+        self.seed = self.cells.clone();
         self.life_cache.clear();
-        self.state.randomize();
+    }
+
+    pub fn contains(&self, cell: &Cell) -> bool {
+        self.cells.contains(cell)
     }
 }
 
@@ -89,14 +123,12 @@ impl canvas::Program<Message> for Map {
         bounds: Rectangle,
         cursor: Cursor,
     ) -> (event::Status, Option<Message>) {
-        if let Some(pos) = cursor.position_in(&bounds) {
-            if let Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) = event {
-                let location = Point { x: pos.x, y: pos.y };
-
-                let cell = Cell::at(location);
+        if let Some(position) = cursor.position_in(&bounds) {
+            if let Event::Mouse(ButtonPressed(Left)) = event {
+                let cell = Cell::at(position);
                 return (
                     event::Status::Captured,
-                    if self.state.contains(&cell) {
+                    if self.contains(&cell) {
                         Some(Message::Unpopulate(cell))
                     } else {
                         Some(Message::Populate(cell))
@@ -124,10 +156,10 @@ impl canvas::Program<Message> for Map {
 
                 (0..24)
                     .cartesian_product(0..24)
-                    .filter(|(i, j)| self.state.contains(&Cell { i: *i, j: *j }))
-                    .for_each(|(i, j)| {
+                    .filter(|x| self.contains(&Cell { i: x.1, j: x.0 }))
+                    .for_each(|x| {
                         frame.fill_rectangle(
-                            Point::new(j as f32, i as f32),
+                            Point::new(x.0 as f32, x.1 as f32),
                             Size::UNIT,
                             Color::WHITE,
                         );
diff --git a/src/state.rs b/src/state.rs
deleted file mode 100644 (file)
index dcb2375..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-use crate::{life::Life, Cell};
-
-use rustc_hash::FxHashSet;
-use std::future::Future;
-
-#[derive(Default)]
-pub struct State {
-    life: Life,
-    births: FxHashSet<Cell>,
-    is_ticking: bool,
-}
-
-impl State {
-    pub fn with_life(life: Life) -> Self {
-        Self {
-            life,
-            ..Self::default()
-        }
-    }
-
-    pub fn contains(&self, cell: &Cell) -> bool {
-        self.life.contains(cell) || self.births.contains(cell)
-    }
-
-    pub fn randomize(&mut self) {
-        self.life.randomize()
-    }
-
-    pub fn clear(&mut self) {
-        self.life.clear();
-    }
-
-    pub fn save(&mut self) {
-        self.life.save_state();
-    }
-
-    pub fn reset(&mut self) {
-        self.life.clear();
-        self.life.reset();
-    }
-
-    pub fn populate(&mut self, cell: Cell) {
-        if self.is_ticking {
-            self.births.insert(cell);
-        } else {
-            self.life.populate(cell);
-        }
-    }
-
-    pub fn unpopulate(&mut self, cell: &Cell) {
-        if self.is_ticking {
-            let _ = self.births.remove(cell);
-        } else {
-            self.life.unpopulate(cell);
-        }
-    }
-
-    pub fn update(&mut self, mut life: Life) {
-        // self.births.drain().for_each(|cell| life.populate(cell));
-
-        self.life = life;
-        self.is_ticking = false;
-    }
-
-    pub fn tick(&mut self, amount: usize) -> Option<impl Future<Output = Life>> {
-        if self.is_ticking {
-            return None;
-        }
-
-        self.is_ticking = true;
-
-        let mut life = self.life.clone();
-
-        Some(async move {
-            tokio::task::spawn_blocking(move || {
-                for _ in 0..amount {
-                    life.tick();
-                }
-                life
-            })
-            .await
-            .unwrap()
-        })
-    }
-}