|
|
|
@ -24,6 +24,7 @@ fn main() {
|
|
|
|
}))
|
|
|
|
}))
|
|
|
|
.init_resource::<Coordinates>()
|
|
|
|
.init_resource::<Coordinates>()
|
|
|
|
.init_state::<SimulationState>()
|
|
|
|
.init_state::<SimulationState>()
|
|
|
|
|
|
|
|
.add_message::<Lifecycle>()
|
|
|
|
.add_systems(Startup, setup)
|
|
|
|
.add_systems(Startup, setup)
|
|
|
|
.add_systems(
|
|
|
|
.add_systems(
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
@ -42,6 +43,7 @@ fn main() {
|
|
|
|
Update,
|
|
|
|
Update,
|
|
|
|
simulation_step.run_if(input_just_pressed(KeyCode::Space)),
|
|
|
|
simulation_step.run_if(input_just_pressed(KeyCode::Space)),
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
|
|
|
.add_systems(Update, cell_lifecycle.run_if(on_message::<Lifecycle>))
|
|
|
|
.add_observer(on_add_ui_button)
|
|
|
|
.add_observer(on_add_ui_button)
|
|
|
|
.add_observer(on_add_coordinates)
|
|
|
|
.add_observer(on_add_coordinates)
|
|
|
|
.run();
|
|
|
|
.run();
|
|
|
|
@ -71,7 +73,6 @@ impl UiButton {
|
|
|
|
Self::ZoomIn => "zoomIn.png",
|
|
|
|
Self::ZoomIn => "zoomIn.png",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn on_add_ui_button(
|
|
|
|
fn on_add_ui_button(
|
|
|
|
@ -118,13 +119,16 @@ fn setup(mut commands: Commands) {
|
|
|
|
));
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
commands
|
|
|
|
commands
|
|
|
|
.spawn((Node {
|
|
|
|
.spawn((
|
|
|
|
top: Val::Px(0.0),
|
|
|
|
Node {
|
|
|
|
left: Val::Px(0.0),
|
|
|
|
top: Val::Px(0.0),
|
|
|
|
height: Val::Px(40.0),
|
|
|
|
left: Val::Px(0.0),
|
|
|
|
width: Val::Percent(100.0),
|
|
|
|
height: Val::Px(40.0),
|
|
|
|
..default()
|
|
|
|
width: Val::Percent(100.0),
|
|
|
|
}, Transform::default()))
|
|
|
|
..default()
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
Transform::default(),
|
|
|
|
|
|
|
|
))
|
|
|
|
.with_children(|parent| {
|
|
|
|
.with_children(|parent| {
|
|
|
|
parent.spawn(UiButton::Power);
|
|
|
|
parent.spawn(UiButton::Power);
|
|
|
|
parent.spawn(UiButton::Pause);
|
|
|
|
parent.spawn(UiButton::Pause);
|
|
|
|
@ -192,18 +196,14 @@ impl Coordinates {
|
|
|
|
.into_iter()
|
|
|
|
.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 {
|
|
|
|
fn as_translation(&self) -> Vec3 {
|
|
|
|
(Vec2::new(self.x as f32, self.y as f32) * SCALE).extend(0.0)
|
|
|
|
(Vec2::new(self.x as f32, self.y as f32) * SCALE).extend(0.0)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn on_add_coordinates(trigger: On<Add, Coordinates>, coordinates: Query<&Coordinates>,
|
|
|
|
fn on_add_coordinates(
|
|
|
|
|
|
|
|
trigger: On<Add, Coordinates>,
|
|
|
|
|
|
|
|
coordinates: Query<&Coordinates>,
|
|
|
|
cells: Query<&Cell>,
|
|
|
|
cells: Query<&Cell>,
|
|
|
|
mut commands: Commands,
|
|
|
|
mut commands: Commands,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
mut meshes: ResMut<Assets<Mesh>>,
|
|
|
|
@ -307,24 +307,34 @@ enum SimulationState {
|
|
|
|
/// 4. Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.
|
|
|
|
/// 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
|
|
|
|
/// TODO: There is a lot of optimizations to be had here
|
|
|
|
fn simulation_step(q: Query<(Entity, &Coordinates), With<Cell>>, mut commands: Commands) {
|
|
|
|
fn simulation_step(
|
|
|
|
fn is_cell_alive(coordinates: &Coordinates, query: &Query<(Entity, &Coordinates), With<Cell>>) -> bool {
|
|
|
|
q: Query<(Entity, &Coordinates), With<Cell>>,
|
|
|
|
query
|
|
|
|
mut writer: MessageWriter<Lifecycle>,
|
|
|
|
.iter()
|
|
|
|
) {
|
|
|
|
.any(|(_e, c)| *c == *coordinates)
|
|
|
|
fn is_cell_alive(
|
|
|
|
|
|
|
|
coordinates: &Coordinates,
|
|
|
|
|
|
|
|
query: &Query<(Entity, &Coordinates), With<Cell>>,
|
|
|
|
|
|
|
|
) -> bool {
|
|
|
|
|
|
|
|
query.iter().any(|(_e, c)| *c == *coordinates)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn living_neighbors(c: &Coordinates, query: &Query<(Entity, &Coordinates), With<Cell>>) -> usize {
|
|
|
|
fn living_neighbors(
|
|
|
|
|
|
|
|
c: &Coordinates,
|
|
|
|
|
|
|
|
query: &Query<(Entity, &Coordinates), With<Cell>>,
|
|
|
|
|
|
|
|
) -> usize {
|
|
|
|
c.neighbors()
|
|
|
|
c.neighbors()
|
|
|
|
.filter(|neighbor| {
|
|
|
|
.filter(|neighbor| {
|
|
|
|
query.iter().any(
|
|
|
|
query
|
|
|
|
|(_e, coordinates)| { *neighbor == *coordinates },
|
|
|
|
.iter()
|
|
|
|
)
|
|
|
|
.any(|(_e, coordinates)| *neighbor == *coordinates)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
.count()
|
|
|
|
.count()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn cell_entity(coordinates: &Coordinates, query: Query<(Entity, &Coordinates), With<Cell>>) -> Option<Entity> {
|
|
|
|
fn cell_entity(
|
|
|
|
|
|
|
|
coordinates: &Coordinates,
|
|
|
|
|
|
|
|
query: Query<(Entity, &Coordinates), With<Cell>>,
|
|
|
|
|
|
|
|
) -> Option<Entity> {
|
|
|
|
query
|
|
|
|
query
|
|
|
|
.iter()
|
|
|
|
.iter()
|
|
|
|
.find_map(|(e, c)| (*c == *coordinates).then_some(e))
|
|
|
|
.find_map(|(e, c)| (*c == *coordinates).then_some(e))
|
|
|
|
@ -353,18 +363,14 @@ fn simulation_step(q: Query<(Entity, &Coordinates), With<Cell>>, mut commands: C
|
|
|
|
|
|
|
|
|
|
|
|
match num_living_neighbors {
|
|
|
|
match num_living_neighbors {
|
|
|
|
..2 | 4.. => {
|
|
|
|
..2 | 4.. => {
|
|
|
|
if let Some(e) = cell_entity(c, q) {
|
|
|
|
if is_living {
|
|
|
|
debug!("killing {:?}", c);
|
|
|
|
writer.write(Lifecycle::Dead(cell_entity(c, q).unwrap()));
|
|
|
|
commands.entity(e).despawn();
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
debug!("failed to kill {:?}", c);
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
2 => debug!("continue"),
|
|
|
|
2 => debug!("continue"),
|
|
|
|
3 => {
|
|
|
|
3 => {
|
|
|
|
if !is_living {
|
|
|
|
if !is_living {
|
|
|
|
debug!("creating {:?}", c);
|
|
|
|
writer.write(Lifecycle::Alive(c.clone()));
|
|
|
|
commands.spawn((c.clone(), Cell));
|
|
|
|
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
debug!("continue")
|
|
|
|
debug!("continue")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -382,3 +388,27 @@ fn responsive_button_out(trigger: On<Pointer<Out>>, mut bg: Query<&mut Backgroun
|
|
|
|
let mut background_color = bg.get_mut(trigger.entity).unwrap();
|
|
|
|
let mut background_color = bg.get_mut(trigger.entity).unwrap();
|
|
|
|
background_color.0 = GRAY.into();
|
|
|
|
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<Lifecycle>,
|
|
|
|
|
|
|
|
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();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
}
|
|
|
|
|