use gloss_hecs::{Entity, PreparedQuery, With, World};
use rand::{thread_rng, Rng};
use std::io;
#[derive(Debug)]
struct Position {
x: i32,
y: i32,
}
#[derive(Debug)]
struct Health(i32);
#[derive(Debug)]
struct Speed(i32);
#[derive(Debug)]
struct Damage(i32);
#[derive(Debug)]
struct KillCount(i32);
fn manhattan_dist(x0: i32, x1: i32, y0: i32, y1: i32) -> i32 {
let dx = (x0 - x1).abs();
let dy = (y0 - y1).abs();
dx + dy
}
fn batch_spawn_entities(world: &mut World, n: usize) {
let mut rng = thread_rng();
let to_spawn = (0..n).map(|_| {
let pos = Position {
x: rng.gen_range(-10..10),
y: rng.gen_range(-10..10),
};
let s = Speed(rng.gen_range(1..5));
let hp = Health(rng.gen_range(30..50));
let dmg = Damage(rng.gen_range(1..10));
let kc = KillCount(0);
(pos, s, hp, dmg, kc)
});
world.spawn_batch(to_spawn);
}
#[allow(clippy::uninlined_format_args)]
fn system_integrate_motion(world: &mut World, query: &mut PreparedQuery<(&mut Position, &Speed)>) {
let mut rng = thread_rng();
for (id, (mut pos, s)) in query.query_mut(world) {
let change = (rng.gen_range(-s.0..s.0), rng.gen_range(-s.0..s.0));
pos.x += change.0;
pos.y += change.1;
println!("Unit {:?} moved to {:?}", id, pos);
}
}
#[allow(clippy::uninlined_format_args)]
fn system_fire_at_closest(world: &mut World) {
for (id0, (pos0, dmg0, mut kc0)) in &mut world.query::<With<(&Position, &Damage, &mut KillCount), &Health>>() {
let closest = world
.query::<With<&Position, &Health>>()
.iter()
.filter(|(id1, _)| *id1 != id0)
.min_by_key(|(_, pos1)| manhattan_dist(pos0.x, pos1.x, pos0.y, pos1.y))
.map(|(entity, _pos)| entity);
let Some(closest) = closest else {
println!("{:?} is the last survivor!", id0);
return;
};
let mut hp1 = world.get::<&mut Health>(closest).unwrap();
if hp1.0 > 0 {
hp1.0 -= dmg0.0;
println!("Unit {:?} was damaged by {:?} for {:?} HP", closest, id0, dmg0.0);
if hp1.0 <= 0 {
kc0.0 += 1;
println!("Unit {:?} was killed by unit {:?}!", closest, id0);
}
}
}
}
fn system_remove_dead(world: &mut World) {
let mut to_remove: Vec<Entity> = Vec::new();
for (id, hp) in &mut world.query::<&Health>() {
if hp.0 <= 0 {
to_remove.push(id);
}
}
for entity in to_remove {
world.despawn(entity).unwrap();
}
}
#[allow(clippy::uninlined_format_args)]
fn print_world_state(world: &mut World) {
println!("\nEntity stats:");
for (id, (hp, pos, dmg, kc)) in &mut world.query::<(&Health, &Position, &Damage, &KillCount)>() {
println!("ID: {:?}, {:?}, {:?}, {:?}, {:?}", id, hp, dmg, pos, kc);
}
}
fn main() {
let mut world = World::new();
batch_spawn_entities(&mut world, 5);
let mut motion_query = PreparedQuery::<(&mut Position, &Speed)>::default();
loop {
println!("\n'Enter' to continue simulation, '?' for entity list, 'q' to quit");
let mut input = String::new();
io::stdin().read_line(&mut input).unwrap();
match input.trim() {
"" => {
system_integrate_motion(&mut world, &mut motion_query);
system_fire_at_closest(&mut world);
system_remove_dead(&mut world);
}
"q" => break,
"?" => {
print_world_state(&mut world);
}
_ => {}
}
}
}