grid logic 2 conway.rs (#3598)

Co-authored-by: Mattuwu <syan4@ualberta.ca>
This commit is contained in:
Jesper Olsen 2025-08-21 03:09:40 -06:00 committed by GitHub
parent 0f05f02bf5
commit 741849d892
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 108 additions and 180 deletions

View File

@ -1,52 +0,0 @@
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum State {
Alive,
Dead,
}
#[derive(Clone, Copy)]
pub struct Cellule {
pub state: State,
}
impl Cellule {
pub fn new_dead() -> Self {
Self { state: State::Dead }
}
pub fn set_alive(&mut self) {
self.state = State::Alive;
}
pub fn set_dead(&mut self) {
self.state = State::Dead;
}
pub fn is_alive(self) -> bool {
self.state == State::Alive
}
pub fn toggle(&mut self) {
if self.is_alive() {
self.set_dead()
} else {
self.set_alive()
}
}
pub fn count_alive_neighbors(neighbors: &[Self]) -> usize {
neighbors.iter().filter(|n| n.is_alive()).count()
}
pub fn alone(neighbors: &[Self]) -> bool {
Self::count_alive_neighbors(neighbors) < 2
}
pub fn overpopulated(neighbors: &[Self]) -> bool {
Self::count_alive_neighbors(neighbors) > 3
}
pub fn can_be_revived(neighbors: &[Self]) -> bool {
Self::count_alive_neighbors(neighbors) == 3
}
}

View File

@ -0,0 +1,69 @@
use rand::Rng;
pub struct Conway {
pub cellules: Vec<bool>,
pub width: usize,
pub height: usize,
}
impl Conway {
pub fn new(width: usize, height: usize) -> Self {
Self {
cellules: vec![false; width * height],
width,
height,
}
}
pub fn alive(&self, row: usize, col: usize) -> bool {
self.cellules[row * self.width + col]
}
pub fn toggle(&mut self, row: usize, col: usize) {
let i = row * self.width + col;
self.cellules[i] = !self.cellules[i];
}
pub fn random_mutate(&mut self) {
let mut rng = rand::rng();
self.cellules.iter_mut().for_each(|c| *c = rng.random());
}
pub fn reset(&mut self) {
self.cellules.iter_mut().for_each(|c| *c = false);
}
pub fn step(&mut self) {
let mut to_toggle = Vec::new();
for row in 0..self.height {
for col in 0..self.width {
let n = self.live_neighbours(row as isize, col as isize);
if (self.alive(row, col) && (n <= 1 || n > 3)) || (!self.alive(row, col) && n == 3)
{
to_toggle.push((row, col));
}
}
}
to_toggle
.iter()
.for_each(|(row, col)| self.toggle(*row, *col));
}
fn live_neighbours(&self, row: isize, col: isize) -> usize {
(-1..=1)
.flat_map(|r| (-1..=1).map(move |c| (r, c)))
.filter(|&(r, c)| (r, c) != (0, 0))
.filter(|&(r, c)| self.cellules[self.row_col_as_idx(row + r, col + c)])
.count()
}
fn row_col_as_idx(&self, row: isize, col: isize) -> usize {
let row = wrap(row, self.height as isize);
let col = wrap(col, self.width as isize);
row * self.width + col
}
}
fn wrap(idx: isize, range: isize) -> usize {
((idx % range + range) % range) as usize // because % has sign of dividend
}

View File

@ -1,10 +1,8 @@
use cell::Cellule;
use gloo::timers::callback::Interval;
use rand::Rng;
use yew::html::Scope;
use yew::{classes, html, Component, Context, Html};
mod cell;
mod conway;
pub enum Msg {
Random,
@ -12,174 +10,98 @@ pub enum Msg {
Step,
Reset,
Stop,
ToggleCellule(usize),
ToggleCellule((usize, usize)),
Tick,
}
pub struct App {
active: bool,
cellules: Vec<Cellule>,
cellules_width: usize,
cellules_height: usize,
conway: conway::Conway,
_interval: Interval,
}
impl App {
pub fn random_mutate(&mut self) {
for cellule in self.cellules.iter_mut() {
if rand::rng().random() {
cellule.set_alive();
} else {
cellule.set_dead();
}
}
}
fn reset(&mut self) {
for cellule in self.cellules.iter_mut() {
cellule.set_dead();
}
}
fn step(&mut self) {
let mut to_dead = Vec::new();
let mut to_live = Vec::new();
for row in 0..self.cellules_height {
for col in 0..self.cellules_width {
let neighbors = self.neighbors(row as isize, col as isize);
let current_idx = self.row_col_as_idx(row as isize, col as isize);
if self.cellules[current_idx].is_alive() {
if Cellule::alone(&neighbors) || Cellule::overpopulated(&neighbors) {
to_dead.push(current_idx);
}
} else if Cellule::can_be_revived(&neighbors) {
to_live.push(current_idx);
}
}
}
to_dead
.iter()
.for_each(|idx| self.cellules[*idx].set_dead());
to_live
.iter()
.for_each(|idx| self.cellules[*idx].set_alive());
}
fn neighbors(&self, row: isize, col: isize) -> [Cellule; 8] {
[
self.cellules[self.row_col_as_idx(row + 1, col)],
self.cellules[self.row_col_as_idx(row + 1, col + 1)],
self.cellules[self.row_col_as_idx(row + 1, col - 1)],
self.cellules[self.row_col_as_idx(row - 1, col)],
self.cellules[self.row_col_as_idx(row - 1, col + 1)],
self.cellules[self.row_col_as_idx(row - 1, col - 1)],
self.cellules[self.row_col_as_idx(row, col - 1)],
self.cellules[self.row_col_as_idx(row, col + 1)],
]
}
fn row_col_as_idx(&self, row: isize, col: isize) -> usize {
let row = wrap(row, self.cellules_height as isize);
let col = wrap(col, self.cellules_width as isize);
row * self.cellules_width + col
}
fn view_cellule(&self, idx: usize, cellule: &Cellule, link: &Scope<Self>) -> Html {
let cellule_status = {
if cellule.is_alive() {
"cellule-live"
} else {
"cellule-dead"
}
fn view_cellule(&self, row: usize, col: usize, link: &Scope<Self>) -> Html {
let status = if self.conway.alive(row, col) {
"cellule-live"
} else {
"cellule-dead"
};
html! {
<div key={idx} class={classes!("game-cellule", cellule_status)}
onclick={link.callback(move |_| Msg::ToggleCellule(idx))}>
<div class={classes!("game-cellule", status)}
onclick={link.callback(move |_| Msg::ToggleCellule((row,col)))}>
</div>
}
}
}
impl Component for App {
type Message = Msg;
type Properties = ();
fn create(ctx: &Context<Self>) -> Self {
let callback = ctx.link().callback(|_| Msg::Tick);
let interval = Interval::new(200, move || callback.emit(()));
let (cellules_width, cellules_height) = (53, 40);
Self {
active: false,
cellules: vec![Cellule::new_dead(); cellules_width * cellules_height],
cellules_width,
cellules_height,
_interval: interval,
conway: conway::Conway::new(53, 40),
_interval: Interval::new(200, move || callback.emit(())),
}
}
fn update(&mut self, _ctx: &Context<Self>, msg: Self::Message) -> bool {
let mut render = true;
match msg {
Msg::Random => {
self.random_mutate();
self.conway.random_mutate();
log::info!("Random");
true
}
Msg::Start => {
self.active = true;
log::info!("Start");
false
render = false;
}
Msg::Step => {
self.step();
true
self.conway.step();
}
Msg::Reset => {
self.reset();
self.conway.reset();
log::info!("Reset");
true
}
Msg::Stop => {
self.active = false;
log::info!("Stop");
false
}
Msg::ToggleCellule(idx) => {
let cellule = self.cellules.get_mut(idx).unwrap();
cellule.toggle();
true
render = false;
}
Msg::ToggleCellule((row, col)) => self.conway.toggle(row, col),
Msg::Tick => {
if self.active {
self.step();
true
self.conway.step();
} else {
false
render = false;
}
}
}
render
}
fn view(&self, ctx: &Context<Self>) -> Html {
let cell_rows =
self.cellules
.chunks(self.cellules_width)
.enumerate()
.map(|(y, cellules)| {
let idx_offset = y * self.cellules_width;
let cells = cellules
.iter()
.enumerate()
.map(|(x, cell)| self.view_cellule(idx_offset + x, cell, ctx.link()));
html! {
<div key={y} class="game-row">
{ for cells }
</div>
}
});
let cell_rows = self
.conway
.cellules
.chunks(self.conway.width)
.enumerate()
.map(|(row, cellules)| {
let cells = cellules
.iter()
.enumerate()
.map(|(col, _)| self.view_cellule(row, col, ctx.link()));
html! {
<div class="game-row">
{ for cells }
</div>
}
});
html! {
<div>
@ -212,17 +134,6 @@ impl Component for App {
}
}
fn wrap(coord: isize, range: isize) -> usize {
let result = if coord < 0 {
coord + range
} else if coord >= range {
coord - range
} else {
coord
};
result as usize
}
fn main() {
wasm_logger::init(wasm_logger::Config::default());
log::trace!("Initializing yew...");