use anput::{
commands::{CommandBuffer, DespawnCommand, SpawnCommand},
component::Component,
entity::Entity,
query::{Include, Query},
scheduler::{GraphScheduler, GraphSchedulerPlugin},
systems::{SystemContext, Systems},
universe::{Res, Universe},
world::World,
};
use moirai::jobs::Jobs;
use rand::{Rng, rng};
use std::{
error::Error,
io::{Write, stdout},
thread::sleep,
time::Duration,
};
const INITIAL_POPULATION: usize = 5;
const LIFETIME: usize = 20;
const REPRODUCTION: usize = 5;
trait Kind: Component + Default {
fn symbol(repro: &Reproduction) -> char;
}
#[derive(Debug, Default)]
struct Bunny;
impl Kind for Bunny {
fn symbol(repro: &Reproduction) -> char {
if repro.0 > 0 { 'b' } else { 'B' }
}
}
#[derive(Debug, Default)]
struct Fox;
impl Kind for Fox {
fn symbol(repro: &Reproduction) -> char {
if repro.0 > 0 { 'f' } else { 'F' }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Position(usize, usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Lifetime(usize);
impl Default for Lifetime {
fn default() -> Self {
Self(LIFETIME)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Reproduction(usize);
impl Default for Reproduction {
fn default() -> Self {
Self(REPRODUCTION)
}
}
fn main() -> Result<(), Box<dyn Error>> {
let mut universe = Universe::default().with_plugin(
GraphSchedulerPlugin::<true>::default()
.resource(CommandBuffer::default())
.resource(ScreenGrid::new(10, 6))
.resource(Contacts::default())
.plugin_setup(|plugin| {
plugin
.system_setup(movement, |system| system.name("movement"))
.system_setup(contacts, |system| system.name("contacts"))
.system_setup(reproduction::<Bunny>, |system| {
system.name("reproduction:bunny")
})
.system_setup(reproduction::<Fox>, |system| {
system.name("reproduction:fox")
})
.system_setup(kill::<Fox, Bunny>, |system| system.name("kill:fox:bunny"))
.system_setup(death, |system| system.name("death"))
})
.plugin_setup(|plugin| {
plugin
.system_setup(render::<Bunny>, |system| system.name("render:bunny"))
.system_setup(render::<Fox>, |system| system.name("render:fox"))
})
.system_setup(display_screen, |system| system.name("display_screen")),
);
Systems::run_one_shot::<true>(&universe, init)?;
let jobs = Jobs::default();
let scheduler = GraphScheduler::<true>;
loop {
scheduler.run(&jobs, &mut universe)?;
sleep(Duration::from_millis(500));
}
}
fn init(context: SystemContext) -> Result<(), Box<dyn Error>> {
let (mut commands, screen) =
context.fetch::<(Res<true, &mut CommandBuffer>, Res<true, &ScreenGrid>)>()?;
for _ in 0..INITIAL_POPULATION {
let position = Position(
rng().random_range(0..screen.width),
rng().random_range(0..screen.height),
);
let lifetime = Lifetime::default();
let reproduction = Reproduction::default();
if rng().random_bool(0.5) {
commands.command(SpawnCommand::new((Fox, position, lifetime, reproduction)));
} else {
commands.command(SpawnCommand::new((Bunny, position, lifetime, reproduction)));
}
}
Ok(())
}
fn death(context: SystemContext) -> Result<(), Box<dyn Error>> {
let (world, mut commands, query) = context.fetch::<(
&World,
Res<true, &mut CommandBuffer>,
Query<true, (Entity, &mut Lifetime)>,
)>()?;
for (entity, lifetime) in query.query(world) {
if lifetime.0 > 0 {
lifetime.0 -= 1;
} else {
commands.command(DespawnCommand::new(entity));
}
}
Ok(())
}
fn contacts(context: SystemContext) -> Result<(), Box<dyn Error>> {
let (world, mut contacts, query) = context.fetch::<(
&World,
Res<true, &mut Contacts>,
Query<true, (Entity, &Position)>,
)>()?;
contacts.pairs.clear();
for (entity_a, position_a) in query.query(world) {
for (entity_b, position_b) in query.query(world) {
if entity_a != entity_b && position_a == position_b {
let mut pair = [entity_a, entity_b];
pair.sort();
if !contacts.pairs.contains(&pair) {
contacts.pairs.push(pair);
}
}
}
}
Ok(())
}
fn movement(context: SystemContext) -> Result<(), Box<dyn Error>> {
let (world, screen, query) =
context.fetch::<(&World, Res<true, &ScreenGrid>, Query<true, &mut Position>)>()?;
let mut rng = rng();
for Position(col, row) in query.query(world) {
match rng.random_range(0..4) {
0 => *col = (*col + screen.width - 1) % screen.width,
1 => *row = (*row + screen.height - 1) % screen.height,
2 => *col = (*col + 1) % screen.width,
3 => *row = (*row + 1) % screen.height,
_ => unreachable!(),
}
}
Ok(())
}
fn reproduction<T: Kind>(context: SystemContext) -> Result<(), Box<dyn Error>> {
let (world, mut commands, contacts, query) = context.fetch::<(
&World,
Res<true, &mut CommandBuffer>,
Res<true, &Contacts>,
Query<true, &mut Reproduction>,
)>()?;
let selected = contacts
.iter()
.filter(|&(a, b)| {
world.has_entity_component::<T>(a)
&& world.has_entity_component::<T>(b)
&& world
.component::<true, Reproduction>(a)
.map(|reproduction| reproduction.0 == 0)
.unwrap_or_default()
&& world
.component::<true, Reproduction>(b)
.map(|reproduction| reproduction.0 == 0)
.unwrap_or_default()
})
.collect::<Vec<_>>();
for (a, b) in selected {
world.component_mut::<true, Reproduction>(a)?.0 = REPRODUCTION;
world.component_mut::<true, Reproduction>(b)?.0 = REPRODUCTION;
let position = *world.component::<true, Position>(a)?;
commands.command(SpawnCommand::new((
T::default(),
position,
Lifetime::default(),
Reproduction::default(),
)));
}
for reproduction in query.query(world) {
reproduction.0 = reproduction.0.saturating_sub(1);
}
Ok(())
}
fn kill<Predator: Kind, Pray: Kind>(context: SystemContext) -> Result<(), Box<dyn Error>> {
let (world, mut commands, contacts) =
context.fetch::<(&World, Res<true, &mut CommandBuffer>, Res<true, &Contacts>)>()?;
for (a, b) in contacts.iter() {
if world.has_entity_component::<Predator>(a) && world.has_entity_component::<Pray>(b) {
commands.command(DespawnCommand::new(b));
} else if world.has_entity_component::<Predator>(b) && world.has_entity_component::<Pray>(a)
{
commands.command(DespawnCommand::new(a));
}
}
Ok(())
}
fn render<T: Kind>(context: SystemContext) -> Result<(), Box<dyn Error>> {
let (world, mut screen, query) = context.fetch::<(
&World,
Res<true, &mut ScreenGrid>,
Query<true, (&Position, &Reproduction, Include<T>)>,
)>()?;
for (&Position(col, row), repro, _) in query.query(world) {
screen.set(col, row, T::symbol(repro));
}
Ok(())
}
fn display_screen(context: SystemContext) -> Result<(), Box<dyn Error>> {
let mut screen = context.fetch::<Res<true, &mut ScreenGrid>>()?;
screen.render();
screen.clear();
Ok(())
}
#[derive(Debug, Default)]
struct Contacts {
pairs: Vec<[Entity; 2]>,
}
impl Contacts {
fn iter(&self) -> impl Iterator<Item = (Entity, Entity)> + '_ {
self.pairs.iter().map(|[a, b]| (*a, *b))
}
}
#[derive(Debug)]
struct ScreenGrid {
width: usize,
height: usize,
buffer: Vec<char>,
}
impl ScreenGrid {
fn new(width: usize, height: usize) -> Self {
Self {
width,
height,
buffer: vec!['.'; width * height],
}
}
fn position_to_index(&self, col: usize, row: usize) -> usize {
row.min(self.height) * self.width + col.min(self.width)
}
fn clear(&mut self) {
for item in &mut self.buffer {
*item = '.';
}
}
fn set(&mut self, col: usize, row: usize, value: char) {
let index = self.position_to_index(col, row);
if let Some(item) = self.buffer.get_mut(index) {
*item = value;
}
}
fn render(&self) {
print!("\x1B[2J\x1B[H");
stdout().flush().unwrap();
let mut column = 0;
for item in &self.buffer {
print!("{item}");
column += 1;
if column >= self.width {
column = 0;
println!();
}
}
stdout().flush().unwrap();
}
}