Introduce Cell type in game_of_life

This commit is contained in:
Héctor Ramón Jiménez 2020-05-01 00:50:40 +02:00
parent 005ad6215a
commit ee97887409
2 changed files with 97 additions and 85 deletions

View File

@ -6,5 +6,5 @@ edition = "2018"
publish = false publish = false
[dependencies] [dependencies]
iced = { path = "../..", features = ["canvas", "tokio"] } iced = { path = "../..", features = ["canvas", "tokio", "debug"] }
itertools = "0.9" itertools = "0.9"

View File

@ -161,11 +161,9 @@ mod grid {
}; };
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
const CELL_SIZE: usize = 20;
#[derive(Default)] #[derive(Default)]
pub struct Grid { pub struct Grid {
alive_cells: HashSet<(isize, isize)>, life: HashSet<Cell>,
interaction: Option<Interaction>, interaction: Option<Interaction>,
cache: canvas::Cache, cache: canvas::Cache,
translation: Vector, translation: Vector,
@ -173,7 +171,7 @@ mod grid {
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Message { pub enum Message {
Populate { cell: (isize, isize) }, Populate(Cell),
} }
enum Interaction { enum Interaction {
@ -182,41 +180,26 @@ mod grid {
} }
impl Grid { impl Grid {
fn with_neighbors(
i: isize,
j: isize,
) -> impl Iterator<Item = (isize, isize)> {
use itertools::Itertools;
let rows = i.saturating_sub(1)..=i.saturating_add(1);
let columns = j.saturating_sub(1)..=j.saturating_add(1);
rows.cartesian_product(columns)
}
pub fn tick(&mut self) { pub fn tick(&mut self) {
use itertools::Itertools; use itertools::Itertools;
let populated_neighbors: HashMap<(isize, isize), usize> = self let populated_neighbors: HashMap<Cell, usize> = self
.alive_cells .life
.iter() .iter()
.flat_map(|&(i, j)| Self::with_neighbors(i, j)) .flat_map(Cell::cluster)
.unique() .unique()
.map(|(i, j)| ((i, j), self.populated_neighbors(i, j))) .map(|cell| (cell, self.count_adjacent_life(cell)))
.collect(); .collect();
for (&(i, j), amount) in populated_neighbors.iter() { for (cell, amount) in populated_neighbors.iter() {
let is_populated = self.alive_cells.contains(&(i, j));
match amount { match amount {
2 | 3 if is_populated => {} 2 => {}
3 => { 3 => {
let _ = self.alive_cells.insert((i, j)); let _ = self.life.insert(*cell);
} }
_ if is_populated => { _ => {
let _ = self.alive_cells.remove(&(i, j)); let _ = self.life.remove(cell);
} }
_ => {}
} }
} }
@ -225,8 +208,8 @@ mod grid {
pub fn update(&mut self, message: Message) { pub fn update(&mut self, message: Message) {
match message { match message {
Message::Populate { cell } => { Message::Populate(cell) => {
self.alive_cells.insert(cell); self.life.insert(cell);
self.cache.clear() self.cache.clear()
} }
} }
@ -239,24 +222,16 @@ mod grid {
.into() .into()
} }
fn populated_neighbors(&self, row: isize, column: isize) -> usize { fn count_adjacent_life(&self, cell: Cell) -> usize {
let with_neighbors = Self::with_neighbors(row, column); let cluster = Cell::cluster(&cell);
let is_neighbor = |i: isize, j: isize| i != row || j != column; let is_neighbor = |candidate| candidate != cell;
let is_populated = let is_populated = |cell| self.life.contains(&cell);
|i: isize, j: isize| self.alive_cells.contains(&(i, j));
with_neighbors cluster
.filter(|&(i, j)| is_neighbor(i, j) && is_populated(i, j)) .filter(|&cell| is_neighbor(cell) && is_populated(cell))
.count() .count()
} }
fn cell_at(&self, position: Point) -> Option<(isize, isize)> {
let i = (position.y / CELL_SIZE as f32).ceil() as isize;
let j = (position.x / CELL_SIZE as f32).ceil() as isize;
Some((i.saturating_sub(1), j.saturating_sub(1)))
}
} }
impl<'a> canvas::Program<Message> for Grid { impl<'a> canvas::Program<Message> for Grid {
@ -271,12 +246,12 @@ mod grid {
} }
let cursor_position = cursor.position_in(&bounds)?; let cursor_position = cursor.position_in(&bounds)?;
let cell = self.cell_at(cursor_position - self.translation)?; let cell = Cell::at(cursor_position - self.translation);
let populate = if self.alive_cells.contains(&cell) { let populate = if self.life.contains(&cell) {
None None
} else { } else {
Some(Message::Populate { cell }) Some(Message::Populate(cell))
}; };
match event { match event {
@ -333,31 +308,24 @@ mod grid {
), ),
); );
let first_row =
(-self.translation.y / CELL_SIZE as f32).floor() as isize;
let first_column =
(-self.translation.x / CELL_SIZE as f32).floor() as isize;
let visible_rows =
(frame.height() / CELL_SIZE as f32).ceil() as isize;
let visible_columns =
(frame.width() / CELL_SIZE as f32).ceil() as isize;
frame.with_save(|frame| { frame.with_save(|frame| {
frame.translate(self.translation); frame.translate(self.translation);
frame.scale(CELL_SIZE as f32); frame.scale(Cell::SIZE as f32);
let cells = Path::new(|p| { let cells = Path::new(|p| {
for i in first_row..=(first_row + visible_rows) { let region = Rectangle {
for j in x: -self.translation.x,
first_column..=(first_column + visible_columns) y: -self.translation.y,
{ width: frame.width(),
if self.alive_cells.contains(&(i, j)) { height: frame.height(),
p.rectangle( };
Point::new(j as f32, i as f32),
cell_size, for cell in Cell::all_visible_in(region) {
); if self.life.contains(&cell) {
} p.rectangle(
Point::new(cell.j as f32, cell.i as f32),
cell_size,
);
} }
} }
}); });
@ -369,25 +337,23 @@ mod grid {
let mut frame = Frame::new(bounds.size()); let mut frame = Frame::new(bounds.size());
frame.translate(self.translation); frame.translate(self.translation);
frame.scale(CELL_SIZE as f32); frame.scale(Cell::SIZE as f32);
if let Some(cursor_position) = cursor.position_in(&bounds) { if let Some(cursor_position) = cursor.position_in(&bounds) {
if let Some((i, j)) = let cell = Cell::at(cursor_position - self.translation);
self.cell_at(cursor_position - self.translation)
{
let interaction = Path::rectangle(
Point::new(j as f32, i as f32),
cell_size,
);
frame.fill( let interaction = Path::rectangle(
&interaction, Point::new(cell.j as f32, cell.i as f32),
Color { cell_size,
a: 0.5, );
..Color::BLACK
}, frame.fill(
); &interaction,
} Color {
a: 0.5,
..Color::BLACK
},
);
} }
frame.into_geometry() frame.into_geometry()
@ -413,4 +379,50 @@ mod grid {
} }
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Cell {
i: isize,
j: isize,
}
impl Cell {
const SIZE: usize = 20;
fn at(position: Point) -> Cell {
let i = (position.y / Cell::SIZE as f32).ceil() as isize;
let j = (position.x / Cell::SIZE as f32).ceil() as isize;
Cell {
i: i.saturating_sub(1),
j: j.saturating_sub(1),
}
}
fn cluster(cell: &Cell) -> impl Iterator<Item = Cell> {
use itertools::Itertools;
let rows = cell.i.saturating_sub(1)..=cell.i.saturating_add(1);
let columns = cell.j.saturating_sub(1)..=cell.j.saturating_add(1);
rows.cartesian_product(columns).map(|(i, j)| Cell { i, j })
}
fn all_visible_in(region: Rectangle) -> impl Iterator<Item = Cell> {
use itertools::Itertools;
let first_row = (region.y / Cell::SIZE as f32).floor() as isize;
let first_column = (region.x / Cell::SIZE as f32).floor() as isize;
let visible_rows =
(region.height / Cell::SIZE as f32).ceil() as isize;
let visible_columns =
(region.width / Cell::SIZE as f32).ceil() as isize;
let rows = first_row..=first_row + visible_rows;
let columns = first_column..=first_column + visible_columns;
rows.cartesian_product(columns).map(|(i, j)| Cell { i, j })
}
}
} }