diff --git a/src/lib/ai.rs b/src/lib/ai.rs index 41552e6..d47bffa 100644 --- a/src/lib/ai.rs +++ b/src/lib/ai.rs @@ -7,20 +7,16 @@ use rand::thread_rng; pub trait AI { fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand; - fn get_position(&self) -> Point; fn plan(&mut self, w: &World) {} } +#[derive(Clone)] pub enum AIGoal { Reach(Point), Idle, } impl AI for Ant { - fn get_position(&self) -> Point { - self.pos.clone() - } - fn plan(&mut self, w: &World) { if self.plan.len() == 0 { self.plan = match self.goal { @@ -31,7 +27,7 @@ impl AI for Ant { } else { astar(&self.pos, &target) } - }, + } AIGoal::Idle => vec![], } } @@ -41,16 +37,15 @@ impl AI for Ant { fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { let choice = match self.goal { AIGoal::Idle => { - let valid = w.get_valid_movements(&self.pos, b); - let mut rng = thread_rng(); - valid.choose(&mut rng).cloned() - }, + let valid = w.get_valid_movements(&self.pos, b); + let mut rng = thread_rng(); + valid.choose(&mut rng).cloned() + } AIGoal::Reach(_) => { let movement = self.plan.pop(); movement } }; - if !choice.is_none() { let pos = choice.unwrap(); @@ -83,16 +78,9 @@ impl AI for Egg { BoardCommand::Hatch(self.id, self.queen_id) } } - - fn get_position(&self) -> Point { - self.pos.clone() - } } impl AI for Queen { - fn get_position(&self) -> Point { - self.pos.clone() - } fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { let valid: Vec = w.get_valid_movements(&self.pos, b); let mut rng = thread_rng(); diff --git a/src/lib/entity.rs b/src/lib/entity.rs index a7b1035..5ae270f 100644 --- a/src/lib/entity.rs +++ b/src/lib/entity.rs @@ -1,6 +1,8 @@ -use crate::lib::ai::{AI, AIGoal}; +use crate::lib::ai::{AIGoal, AI}; use crate::lib::point::Point; -use crate::lib::screen::Screen; +use crate::lib::screen::{BoardCommand, Screen}; +use crate::lib::world::World; +use rand::Rng; use std::collections::HashMap; @@ -8,28 +10,55 @@ use ncurses::*; use downcast_rs::{impl_downcast, Downcast}; -pub trait Entity: AI + Renderable + Downcast {} -impl_downcast!(Entity); +pub trait Entity: AI + Downcast { + fn get_position(&self) -> Point; + fn get_id(&self) -> u32; + fn set_id(&mut self, id: u32); +} + +macro_rules! impl_entity { + ($t: ident) => { + impl Entity for $t { + fn get_position(&self) -> Point { + self.pos.clone() + } + + fn get_id(&self) -> u32 { + self.id + } + fn set_id(&mut self, id: u32) { + self.id = id; + } + } + }; +} + +impl_downcast!(Renderable); + +#[derive(Clone)] pub struct Ant { pub pos: Point, pub id: u32, pub goal: AIGoal, - pub plan: Vec + pub plan: Vec, } impl Ant { - pub fn new(x: i32, y: i32, id: u32) -> Ant { + pub fn new(x: i32, y: i32) -> Ant { Ant { pos: Point(x, y), - id, - plan: vec![], - goal: AIGoal::Idle + id: 0, + plan: vec![], + goal: AIGoal::Idle, } } } - -impl Entity for Ant {} +impl_entity!(Ant); +impl_entity!(Food); +impl_entity!(FoodGenerator); +impl_entity!(Egg); +impl_entity!(Queen); impl Renderable for Ant { fn representation(&self) -> &str { @@ -44,6 +73,7 @@ impl Renderable for Ant { } } +#[derive(Clone)] pub struct Queen { pub pos: Point, pub egg_count: u8, @@ -64,8 +94,10 @@ impl Renderable for Queen { } } -pub trait Renderable: AI { - fn representation(&self) -> &str; +pub trait Renderable: Entity { + fn representation(&self) -> &str { + "X" + } fn before_render(&self) {} fn after_render(&self) {} fn render(&self, b: &Screen) { @@ -76,16 +108,16 @@ pub trait Renderable: AI { } impl Queen { - pub fn new(x: i32, y: i32, id: u32) -> Queen { + pub fn new(x: i32, y: i32) -> Queen { Queen { pos: Point(x, y), egg_count: 0, - id, + id: 0, } } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Egg { pub pos: Point, pub counter: u8, @@ -108,21 +140,18 @@ impl Renderable for Egg { } impl Egg { - pub fn new(x: i32, y: i32, id: u32, queen_id: u32) -> Egg { + pub fn new(x: i32, y: i32, queen_id: u32) -> Egg { Egg { pos: Point(x, y), counter: 10, - id, + id: 0, queen_id, } } } -impl Entity for Queen {} -impl Entity for Egg {} - pub struct Entities { - pub data: HashMap>, + pub data: HashMap>, id_counter: u32, } @@ -134,27 +163,77 @@ impl Entities { } } - pub fn add_ant(&mut self, x: i32, y: i32) -> u32 { - let a = Ant::new(x, y, self.id_counter); - let id = a.id; - self.data.insert(a.id, Box::new(a)); + pub fn add_entity(&mut self, e: &T) -> u32 { + let mut clone = e.clone(); + clone.set_id(self.id_counter); + let id = clone.get_id(); + self.data.insert(id, Box::new(clone)); self.id_counter += 1; - id + e.get_id() } +} - pub fn add_queen(&mut self, x: i32, y: i32) -> u32 { - let q = Queen::new(x, y, self.id_counter); - let id = q.id; - self.data.insert(q.id, Box::new(q)); - self.id_counter += 1; - id +#[derive(Clone)] +pub struct Food { + pos: Point, + id: u32, +} + +impl Food { + pub fn new(x: i32, y: i32) -> Food { + Food { + pos: Point(x, y), + id: 0, + } + } +} + +impl AI for Food { + fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { + // perhaps check if we're in target? + // implement drag logic? + BoardCommand::Noop } +} - pub fn add_egg(&mut self, x: i32, y: i32, queen_id: u32) -> u32 { - let e = Egg::new(x, y, self.id_counter, queen_id); - let id = e.id; - self.data.insert(e.id, Box::new(e)); - self.id_counter += 1; - id +impl Renderable for Food { + fn representation(&self) -> &str { + "f" + } +} + +// no position, does not get rendered yet acts per turn +#[derive(Clone)] +pub struct FoodGenerator { + counter: u32, + pos: Point, + id: u32 +} + +impl FoodGenerator { + pub fn new() -> FoodGenerator { + FoodGenerator { counter: 0, id: 0, pos: Point(0,0) } } } + +impl AI for FoodGenerator { + fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { + if self.counter % 600 == 0 { + // generate random coords, if valid, spawn + // if not, try again next step + let (min, max) = b.get_dimensions(); + + let mut rng = rand::thread_rng(); + let r_x = rng.gen_range(min.0..max.0 + 1); + let r_y = rng.gen_range(min.1..max.1 + 1); + + return BoardCommand::SpawnFood(Point(r_x, r_y)); + } + self.counter += 1; + BoardCommand::Noop + } +} + +impl Renderable for FoodGenerator { + fn render(&self, b: &Screen) {} +} diff --git a/src/lib/screen.rs b/src/lib/screen.rs index f3d104a..8b07159 100644 --- a/src/lib/screen.rs +++ b/src/lib/screen.rs @@ -47,6 +47,10 @@ impl Screen { pub fn render(&self, p: &Point, char: &str) { mvprintw(p.1 + self.center.1, p.0 + self.center.0, char); } + + pub fn get_dimensions(&self) -> (Point, Point) { + (Point(-self.max_x, -self.max_y), Point(self.max_x, self.max_y)) + } } #[test] @@ -75,6 +79,7 @@ fn test_get_valid_movements_board() { pub enum BoardCommand { Dig(Point), LayEgg(Point, u32), + SpawnFood(Point), Hatch(u32, u32), Noop, } diff --git a/src/lib/world.rs b/src/lib/world.rs index 1c77143..b851498 100644 --- a/src/lib/world.rs +++ b/src/lib/world.rs @@ -1,12 +1,13 @@ use crate::lib::screen::{Screen, BoardCommand}; use crate::lib::point::Point; -use crate::lib::entity::{Entities, Queen}; +use crate::lib::entity::{Entities, Queen, Egg, Ant, Food}; use std::collections::HashSet; #[derive(Clone)] pub struct World { pub cleared: HashSet, pub occupied: HashSet, + pub food: HashSet } impl World { @@ -14,6 +15,7 @@ impl World { World { cleared: HashSet::new(), occupied: HashSet::new(), + food: HashSet::new() } } @@ -66,7 +68,8 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) { w.clear(pos); } BoardCommand::LayEgg(pos, id) => { - e.add_egg(pos.0, pos.1, id); + let egg = Egg::new(pos.0, pos.1, id); + e.add_entity(&egg); } BoardCommand::Hatch(egg_id, queen_id) => { let egg = e.data.remove(&egg_id); @@ -75,7 +78,12 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) { let q = e.data.get_mut(&queen_id).unwrap(); let queen: &mut Queen = q.downcast_mut::().unwrap(); queen.egg_count -= 1; - e.add_ant(pos.0, pos.1); + let ant = Ant::new(pos.0, pos.1); + e.add_entity(&ant); + } + BoardCommand::SpawnFood(pos) => { + let food = Food::new(pos.0, pos.1); + e.add_entity(&food); } BoardCommand::Noop => {} } diff --git a/src/main.rs b/src/main.rs index a27a0eb..2bfef4a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,14 +17,15 @@ mod lib { use lib::point::Point; use lib::screen::init_screen; use lib::world::{World, simulate, render}; -use lib::entity::Entities; +use lib::entity::{Entities, Queen}; fn main() { let mut board = init_screen(); let mut world = World::new(); let mut entities = Entities::new(); - entities.add_queen(0,0); + let q = Queen::new(0,0); + entities.add_entity(&q); for i in -3..3 { for j in -3..3 { diff --git a/tests/ai_test.rs b/tests/ai_test.rs index be2bd63..131e288 100644 --- a/tests/ai_test.rs +++ b/tests/ai_test.rs @@ -2,19 +2,23 @@ use antf::lib::screen::init_screen; use antf::lib::point::Point; use antf::lib::ai::AIGoal; use antf::lib::world::{World, simulate, render}; -use antf::lib::entity::{Entities, Ant}; +use antf::lib::entity::{Entities, Ant, FoodGenerator}; use ncurses::*; use std::thread::sleep; use std::time; +// make sure to run with --test-threads 1! otherwise output will be bugged + #[test] -fn test_astar() { +fn test_reach_astar() { let mut board = init_screen(); let mut world = World::new(); let mut entities = Entities::new(); - let id = entities.add_ant(0,0); + + let a = Ant::new(0,0); + let id = entities.add_entity(&a); let a = entities.data.get_mut(&id).unwrap(); let ant: &mut Ant = a.downcast_mut::().unwrap(); @@ -27,6 +31,26 @@ fn test_astar() { sleep(time::Duration::from_millis(100)); refresh(); } + clear(); endwin(); } +#[test] +fn test_foodgen() { + let mut board = init_screen(); + let mut world = World::new(); + + let mut entities = Entities::new(); + let fg = FoodGenerator::new(); + entities.add_entity(&fg); + + for _ in 0..60 { + // TODO: add way to break out of the loop by hitting a random key + simulate(&mut entities, &mut world, &mut board); + render(&entities, &world, &board); + sleep(time::Duration::from_millis(100)); + refresh(); + } + clear(); + endwin(); +}