diff --git a/engine/src/lib.rs b/engine/src/lib.rs index a35f745..046bb24 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -1 +1,4 @@ -pub use bevy::{color::palettes::basic::*, input::common_conditions::*, prelude::*, platform::collections::HashSet}; +pub use bevy::{ + color::palettes::basic::*, input::common_conditions::*, platform::collections::HashSet, + prelude::*, +}; diff --git a/prototypes/src/bin/life.rs b/prototypes/src/bin/life.rs index 1c19993..337054e 100644 --- a/prototypes/src/bin/life.rs +++ b/prototypes/src/bin/life.rs @@ -24,6 +24,7 @@ fn main() { })) .init_resource::() .init_state::() + .add_message::() .add_systems(Startup, setup) .add_systems( Update, @@ -42,6 +43,7 @@ fn main() { Update, simulation_step.run_if(input_just_pressed(KeyCode::Space)), ) + .add_systems(Update, cell_lifecycle.run_if(on_message::)) .add_observer(on_add_ui_button) .add_observer(on_add_coordinates) .run(); @@ -71,7 +73,6 @@ impl UiButton { Self::ZoomIn => "zoomIn.png", } } - } fn on_add_ui_button( @@ -118,13 +119,16 @@ fn setup(mut commands: Commands) { )); commands - .spawn((Node { - top: Val::Px(0.0), - left: Val::Px(0.0), - height: Val::Px(40.0), - width: Val::Percent(100.0), - ..default() - }, Transform::default())) + .spawn(( + Node { + top: Val::Px(0.0), + left: Val::Px(0.0), + height: Val::Px(40.0), + width: Val::Percent(100.0), + ..default() + }, + Transform::default(), + )) .with_children(|parent| { parent.spawn(UiButton::Power); parent.spawn(UiButton::Pause); @@ -192,18 +196,14 @@ impl Coordinates { .into_iter() } - fn is_neighbor_of(&self, other: &Coordinates) -> bool { - let (a1, b1) = (self.x, self.y); - let (a2, b2) = (other.x, other.y); - (a1 == a2 + 1 || a1 == a2 - 1) && (b1 == b2 + 1 || b1 == b2 - 1) - } - fn as_translation(&self) -> Vec3 { (Vec2::new(self.x as f32, self.y as f32) * SCALE).extend(0.0) } } -fn on_add_coordinates(trigger: On, coordinates: Query<&Coordinates>, +fn on_add_coordinates( + trigger: On, + coordinates: Query<&Coordinates>, cells: Query<&Cell>, mut commands: Commands, mut meshes: ResMut>, @@ -307,24 +307,34 @@ enum SimulationState { /// 4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction. /// /// TODO: There is a lot of optimizations to be had here -fn simulation_step(q: Query<(Entity, &Coordinates), With>, mut commands: Commands) { - fn is_cell_alive(coordinates: &Coordinates, query: &Query<(Entity, &Coordinates), With>) -> bool { - query - .iter() - .any(|(_e, c)| *c == *coordinates) +fn simulation_step( + q: Query<(Entity, &Coordinates), With>, + mut writer: MessageWriter, +) { + fn is_cell_alive( + coordinates: &Coordinates, + query: &Query<(Entity, &Coordinates), With>, + ) -> bool { + query.iter().any(|(_e, c)| *c == *coordinates) } - fn living_neighbors(c: &Coordinates, query: &Query<(Entity, &Coordinates), With>) -> usize { + fn living_neighbors( + c: &Coordinates, + query: &Query<(Entity, &Coordinates), With>, + ) -> usize { c.neighbors() .filter(|neighbor| { - query.iter().any( - |(_e, coordinates)| { *neighbor == *coordinates }, - ) + query + .iter() + .any(|(_e, coordinates)| *neighbor == *coordinates) }) .count() } - fn cell_entity(coordinates: &Coordinates, query: Query<(Entity, &Coordinates), With>) -> Option { + fn cell_entity( + coordinates: &Coordinates, + query: Query<(Entity, &Coordinates), With>, + ) -> Option { query .iter() .find_map(|(e, c)| (*c == *coordinates).then_some(e)) @@ -353,18 +363,14 @@ fn simulation_step(q: Query<(Entity, &Coordinates), With>, mut commands: C match num_living_neighbors { ..2 | 4.. => { - if let Some(e) = cell_entity(c, q) { - debug!("killing {:?}", c); - commands.entity(e).despawn(); - } else { - debug!("failed to kill {:?}", c); + if is_living { + writer.write(Lifecycle::Dead(cell_entity(c, q).unwrap())); } } 2 => debug!("continue"), 3 => { if !is_living { - debug!("creating {:?}", c); - commands.spawn((c.clone(), Cell)); + writer.write(Lifecycle::Alive(c.clone())); } else { debug!("continue") } @@ -382,3 +388,27 @@ fn responsive_button_out(trigger: On>, mut bg: Query<&mut Backgroun let mut background_color = bg.get_mut(trigger.entity).unwrap(); background_color.0 = GRAY.into(); } + +/// Bring a cell to life, or kill it +#[derive(Message)] +enum Lifecycle { + Alive(Coordinates), + Dead(Entity), +} + +fn cell_lifecycle( + mut reader: MessageReader, + mut commands: Commands, +) { + reader.read().for_each(|msg| match msg { + Lifecycle::Alive(c) => { + debug!("creating {:?}", c); + // TODO: Safeguard: ensure no other cell there? + commands.spawn((c.clone(), Cell)); + } + Lifecycle::Dead(e) => { + // TODO: Maybe include the entity instead of the coordinates for Dead + commands.entity(*e).despawn(); + } + }) +}