Use sparse grid representation in game_of_life

This commit is contained in:
Héctor Ramón Jiménez 2020-04-29 23:50:15 +02:00
parent 38c4dd5fdb
commit 5e014a70e8

View File

@ -14,10 +14,7 @@ use iced::{
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
pub fn main() { pub fn main() {
GameOfLife::run(Settings { GameOfLife::run(Settings::default())
antialiasing: true,
..Settings::default()
})
} }
#[derive(Default)] #[derive(Default)]
@ -164,36 +161,55 @@ mod grid {
mouse, ButtonState, Color, Element, Length, MouseCursor, Point, mouse, ButtonState, Color, Element, Length, MouseCursor, Point,
Rectangle, Size, Vector, Rectangle, Size, Vector,
}; };
use std::collections::{HashMap, HashSet};
const SIZE: usize = 32; const CELL_SIZE: usize = 20;
#[derive(Default)] #[derive(Default)]
pub struct Grid { pub struct Grid {
cells: [[Cell; SIZE]; SIZE], alive_cells: HashSet<(usize, usize)>,
mouse_pressed: bool, mouse_pressed: bool,
cache: canvas::Cache, cache: canvas::Cache,
} }
impl Grid { impl Grid {
fn with_neighbors(
i: usize,
j: usize,
) -> impl Iterator<Item = (usize, usize)> {
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) {
let mut populated_neighbors: [[usize; SIZE]; SIZE] = use itertools::Itertools;
[[0; SIZE]; SIZE];
for (i, row) in self.cells.iter().enumerate() { let populated_neighbors: HashMap<(usize, usize), usize> = self
for (j, _) in row.iter().enumerate() { .alive_cells
populated_neighbors[i][j] = self.populated_neighbors(i, j); .iter()
} .flat_map(|&(i, j)| Self::with_neighbors(i, j))
} .unique()
.map(|(i, j)| ((i, j), self.populated_neighbors(i, j)))
.collect();
for (i, row) in populated_neighbors.iter().enumerate() { for (&(i, j), amount) in populated_neighbors.iter() {
for (j, amount) in row.iter().enumerate() { let is_populated = self.alive_cells.contains(&(i, j));
let is_populated = self.cells[i][j] == Cell::Populated;
self.cells[i][j] = match amount { match amount {
2 if is_populated => Cell::Populated, 2 if is_populated => {}
3 => Cell::Populated, 3 => {
_ => Cell::Unpopulated, if !is_populated {
}; self.alive_cells.insert((i, j));
}
}
_ if is_populated => {
self.alive_cells.remove(&(i, j));
}
_ => {}
} }
} }
@ -202,8 +218,8 @@ mod grid {
pub fn update(&mut self, message: Message) { pub fn update(&mut self, message: Message) {
match message { match message {
Message::Populate { i, j } => { Message::Populate { cell } => {
self.cells[i][j] = Cell::Populated; self.alive_cells.insert(cell);
self.cache.clear() self.cache.clear()
} }
} }
@ -217,34 +233,28 @@ mod grid {
} }
fn populated_neighbors(&self, row: usize, column: usize) -> usize { fn populated_neighbors(&self, row: usize, column: usize) -> usize {
use itertools::Itertools; let with_neighbors = Self::with_neighbors(row, column);
let rows = row.saturating_sub(1)..=row + 1;
let columns = column.saturating_sub(1)..=column + 1;
let is_inside_bounds = |i: usize, j: usize| i < SIZE && j < SIZE;
let is_neighbor = |i: usize, j: usize| i != row || j != column; let is_neighbor = |i: usize, j: usize| i != row || j != column;
let is_populated = let is_populated =
|i: usize, j: usize| self.cells[i][j] == Cell::Populated; |i: usize, j: usize| self.alive_cells.contains(&(i, j));
rows.cartesian_product(columns) with_neighbors
.filter(|&(i, j)| { .filter(|&(i, j)| is_neighbor(i, j) && is_populated(i, j))
is_inside_bounds(i, j)
&& is_neighbor(i, j)
&& is_populated(i, j)
})
.count() .count()
} }
fn region(&self, size: Size) -> Rectangle { fn region(&self, size: Size) -> Rectangle {
let side = size.width.min(size.height); let width =
(size.width / CELL_SIZE as f32).floor() * CELL_SIZE as f32;
let height =
(size.height / CELL_SIZE as f32).floor() * CELL_SIZE as f32;
Rectangle { Rectangle {
x: (size.width - side) / 2.0, x: (size.width - width) / 2.0,
y: (size.height - side) / 2.0, y: (size.height - height) / 2.0,
width: side, width,
height: side, height,
} }
} }
@ -254,10 +264,10 @@ mod grid {
position: Point, position: Point,
) -> Option<(usize, usize)> { ) -> Option<(usize, usize)> {
if region.contains(position) { if region.contains(position) {
let cell_size = region.width / SIZE as f32; let i = ((position.y - region.y) / CELL_SIZE as f32).ceil()
as usize;
let i = ((position.y - region.y) / cell_size).ceil() as usize; let j = ((position.x - region.x) / CELL_SIZE as f32).ceil()
let j = ((position.x - region.x) / cell_size).ceil() as usize; as usize;
Some((i.saturating_sub(1), j.saturating_sub(1))) Some((i.saturating_sub(1), j.saturating_sub(1)))
} else { } else {
@ -266,21 +276,9 @@ mod grid {
} }
} }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Cell {
Unpopulated,
Populated,
}
impl Default for Cell {
fn default() -> Cell {
Cell::Unpopulated
}
}
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
pub enum Message { pub enum Message {
Populate { i: usize, j: usize }, Populate { cell: (usize, usize) },
} }
impl<'a> canvas::Program<Message> for Grid { impl<'a> canvas::Program<Message> for Grid {
@ -301,12 +299,12 @@ mod grid {
let cursor_position = cursor.position_in(&bounds)?; let cursor_position = cursor.position_in(&bounds)?;
let region = self.region(bounds.size()); let region = self.region(bounds.size());
let (i, j) = self.cell_at(region, cursor_position)?; let cell = self.cell_at(region, cursor_position)?;
let populate = if self.cells[i][j] != Cell::Populated { let populate = if self.alive_cells.contains(&cell) {
Some(Message::Populate { i, j })
} else {
None None
} else {
Some(Message::Populate { cell })
}; };
match event { match event {
@ -339,14 +337,17 @@ mod grid {
), ),
); );
let visible_rows = region.height as usize / CELL_SIZE;
let visible_columns = region.width as usize / CELL_SIZE;
frame.with_save(|frame| { frame.with_save(|frame| {
frame.translate(Vector::new(region.x, region.y)); frame.translate(Vector::new(region.x, region.y));
frame.scale(region.width / SIZE as f32); frame.scale(CELL_SIZE as f32);
let cells = Path::new(|p| { let cells = Path::new(|p| {
for (i, row) in self.cells.iter().enumerate() { for i in 0..visible_rows {
for (j, cell) in row.iter().enumerate() { for j in 0..visible_columns {
if *cell == Cell::Populated { if self.alive_cells.contains(&(i, j)) {
p.rectangle( p.rectangle(
Point::new(j as f32, i as f32), Point::new(j as f32, i as f32),
cell_size, cell_size,
@ -363,7 +364,7 @@ mod grid {
let mut frame = Frame::new(bounds.size()); let mut frame = Frame::new(bounds.size());
frame.translate(Vector::new(region.x, region.y)); frame.translate(Vector::new(region.x, region.y));
frame.scale(region.width / 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)) = self.cell_at(region, cursor_position) if let Some((i, j)) = self.cell_at(region, cursor_position)