Introduce Cell
type in game_of_life
This commit is contained in:
parent
005ad6215a
commit
ee97887409
@ -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"
|
||||||
|
@ -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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user