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 {
#[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,
}
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(),
}
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,];
+++ /dev/null
-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()
- }
-}
-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,
}
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();
}
}
}
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)
}
}
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))
(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,
);
+++ /dev/null
-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()
- })
- }
-}