mirror of
https://github.com/yewstack/yew.git
synced 2025-12-08 21:26:25 +00:00
grid logic 2 conway.rs (#3598)
Co-authored-by: Mattuwu <syan4@ualberta.ca>
This commit is contained in:
parent
0f05f02bf5
commit
741849d892
@ -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
|
||||
}
|
||||
}
|
||||
69
examples/game_of_life/src/conway.rs
Normal file
69
examples/game_of_life/src/conway.rs
Normal 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
|
||||
}
|
||||
@ -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...");
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user