use hecs::*;
use rand::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 = rand::rng();
let to_spawn = (0..n).map(|_| {
let pos = Position {
x: rng.random_range(-10..10),
y: rng.random_range(-10..10),
};
let s = Speed(rng.random_range(1..5));
let hp = Health(rng.random_range(30..50));
let dmg = Damage(rng.random_range(1..10));
let kc = KillCount(0);
(pos, s, hp, dmg, kc)
});
world.spawn_batch(to_spawn);
}
fn system_integrate_motion(
world: &mut World,
query: &mut PreparedQuery<(Entity, &mut Position, &Speed)>,
) {
let mut rng = rand::rng();
for (id, pos, s) in query.query_mut(world) {
let change = (rng.random_range(-s.0..s.0), rng.random_range(-s.0..s.0));
pos.x += change.0;
pos.y += change.1;
println!("Unit {id:?} moved to {pos:?}");
}
}
fn system_fire_at_closest(world: &mut World) {
for (id0, pos0, dmg0, kc0) in
&mut world.query::<With<(Entity, &Position, &Damage, &mut KillCount), &Health>>()
{
let closest = world
.query::<With<(Entity, &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 closest = match closest {
Some(entity) => entity,
None => {
println!("{id0:?} is the last survivor!");
return;
}
};
let mut hp1 = world.get::<&mut Health>(closest).unwrap();
if hp1.0 > 0 {
hp1.0 -= dmg0.0;
println!(
"Unit {closest:?} was damaged by {id0:?} for {:?} HP",
dmg0.0
);
if hp1.0 <= 0 {
kc0.0 += 1;
println!("Unit {closest:?} was killed by unit {id0:?}!");
}
}
}
}
fn system_remove_dead(world: &mut World) {
let mut to_remove: Vec<Entity> = Vec::new();
for (id, hp) in &mut world.query::<(Entity, &Health)>() {
if hp.0 <= 0 {
to_remove.push(id);
}
}
for entity in to_remove {
world.despawn(entity).unwrap();
}
}
fn print_world_state(world: &mut World) {
println!("\nEntity stats:");
for (id, hp, pos, dmg, kc) in
&mut world.query::<(Entity, &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::<(Entity, &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);
}
_ => {}
}
}
}