cleaned up some api impl, added food + foodgen

master
Rostyslav Hnatyshyn 9 months ago
parent 2e0dbc958b
commit 4f76f301bc
  1. 24
      src/lib/ai.rs
  2. 155
      src/lib/entity.rs
  3. 5
      src/lib/screen.rs
  4. 14
      src/lib/world.rs
  5. 5
      src/main.rs
  6. 30
      tests/ai_test.rs

@ -7,20 +7,16 @@ use rand::thread_rng;
pub trait AI { pub trait AI {
fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand; fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand;
fn get_position(&self) -> Point;
fn plan(&mut self, w: &World) {} fn plan(&mut self, w: &World) {}
} }
#[derive(Clone)]
pub enum AIGoal { pub enum AIGoal {
Reach(Point), Reach(Point),
Idle, Idle,
} }
impl AI for Ant { impl AI for Ant {
fn get_position(&self) -> Point {
self.pos.clone()
}
fn plan(&mut self, w: &World) { fn plan(&mut self, w: &World) {
if self.plan.len() == 0 { if self.plan.len() == 0 {
self.plan = match self.goal { self.plan = match self.goal {
@ -31,7 +27,7 @@ impl AI for Ant {
} else { } else {
astar(&self.pos, &target) astar(&self.pos, &target)
} }
}, }
AIGoal::Idle => vec![], AIGoal::Idle => vec![],
} }
} }
@ -41,17 +37,16 @@ impl AI for Ant {
fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand {
let choice = match self.goal { let choice = match self.goal {
AIGoal::Idle => { AIGoal::Idle => {
let valid = w.get_valid_movements(&self.pos, b); let valid = w.get_valid_movements(&self.pos, b);
let mut rng = thread_rng(); let mut rng = thread_rng();
valid.choose(&mut rng).cloned() valid.choose(&mut rng).cloned()
}, }
AIGoal::Reach(_) => { AIGoal::Reach(_) => {
let movement = self.plan.pop(); let movement = self.plan.pop();
movement movement
} }
}; };
if !choice.is_none() { if !choice.is_none() {
let pos = choice.unwrap(); let pos = choice.unwrap();
if w.cleared.contains(&pos) { if w.cleared.contains(&pos) {
@ -83,16 +78,9 @@ impl AI for Egg {
BoardCommand::Hatch(self.id, self.queen_id) BoardCommand::Hatch(self.id, self.queen_id)
} }
} }
fn get_position(&self) -> Point {
self.pos.clone()
}
} }
impl AI for Queen { impl AI for Queen {
fn get_position(&self) -> Point {
self.pos.clone()
}
fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand {
let valid: Vec<Point> = w.get_valid_movements(&self.pos, b); let valid: Vec<Point> = w.get_valid_movements(&self.pos, b);
let mut rng = thread_rng(); let mut rng = thread_rng();

@ -1,6 +1,8 @@
use crate::lib::ai::{AI, AIGoal}; use crate::lib::ai::{AIGoal, AI};
use crate::lib::point::Point; 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; use std::collections::HashMap;
@ -8,28 +10,55 @@ use ncurses::*;
use downcast_rs::{impl_downcast, Downcast}; use downcast_rs::{impl_downcast, Downcast};
pub trait Entity: AI + Renderable + Downcast {} pub trait Entity: AI + Downcast {
impl_downcast!(Entity); 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 struct Ant {
pub pos: Point, pub pos: Point,
pub id: u32, pub id: u32,
pub goal: AIGoal, pub goal: AIGoal,
pub plan: Vec<Point> pub plan: Vec<Point>,
} }
impl Ant { impl Ant {
pub fn new(x: i32, y: i32, id: u32) -> Ant { pub fn new(x: i32, y: i32) -> Ant {
Ant { Ant {
pos: Point(x, y), pos: Point(x, y),
id, id: 0,
plan: vec![], plan: vec![],
goal: AIGoal::Idle goal: AIGoal::Idle,
} }
} }
} }
impl_entity!(Ant);
impl Entity for Ant {} impl_entity!(Food);
impl_entity!(FoodGenerator);
impl_entity!(Egg);
impl_entity!(Queen);
impl Renderable for Ant { impl Renderable for Ant {
fn representation(&self) -> &str { fn representation(&self) -> &str {
@ -44,6 +73,7 @@ impl Renderable for Ant {
} }
} }
#[derive(Clone)]
pub struct Queen { pub struct Queen {
pub pos: Point, pub pos: Point,
pub egg_count: u8, pub egg_count: u8,
@ -64,8 +94,10 @@ impl Renderable for Queen {
} }
} }
pub trait Renderable: AI { pub trait Renderable: Entity {
fn representation(&self) -> &str; fn representation(&self) -> &str {
"X"
}
fn before_render(&self) {} fn before_render(&self) {}
fn after_render(&self) {} fn after_render(&self) {}
fn render(&self, b: &Screen) { fn render(&self, b: &Screen) {
@ -76,16 +108,16 @@ pub trait Renderable: AI {
} }
impl Queen { impl Queen {
pub fn new(x: i32, y: i32, id: u32) -> Queen { pub fn new(x: i32, y: i32) -> Queen {
Queen { Queen {
pos: Point(x, y), pos: Point(x, y),
egg_count: 0, egg_count: 0,
id, id: 0,
} }
} }
} }
#[derive(Debug)] #[derive(Debug, Clone)]
pub struct Egg { pub struct Egg {
pub pos: Point, pub pos: Point,
pub counter: u8, pub counter: u8,
@ -108,21 +140,18 @@ impl Renderable for Egg {
} }
impl 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 { Egg {
pos: Point(x, y), pos: Point(x, y),
counter: 10, counter: 10,
id, id: 0,
queen_id, queen_id,
} }
} }
} }
impl Entity for Queen {}
impl Entity for Egg {}
pub struct Entities { pub struct Entities {
pub data: HashMap<u32, Box<dyn Entity>>, pub data: HashMap<u32, Box<dyn Renderable>>,
id_counter: u32, id_counter: u32,
} }
@ -134,27 +163,77 @@ impl Entities {
} }
} }
pub fn add_ant(&mut self, x: i32, y: i32) -> u32 { pub fn add_entity<T: Renderable + Clone>(&mut self, e: &T) -> u32 {
let a = Ant::new(x, y, self.id_counter); let mut clone = e.clone();
let id = a.id; clone.set_id(self.id_counter);
self.data.insert(a.id, Box::new(a)); let id = clone.get_id();
self.data.insert(id, Box::new(clone));
self.id_counter += 1; self.id_counter += 1;
id e.get_id()
} }
}
pub fn add_queen(&mut self, x: i32, y: i32) -> u32 { #[derive(Clone)]
let q = Queen::new(x, y, self.id_counter); pub struct Food {
let id = q.id; pos: Point,
self.data.insert(q.id, Box::new(q)); id: u32,
self.id_counter += 1; }
id
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 { impl Renderable for Food {
let e = Egg::new(x, y, self.id_counter, queen_id); fn representation(&self) -> &str {
let id = e.id; "f"
self.data.insert(e.id, Box::new(e)); }
self.id_counter += 1; }
id
// 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) {}
}

@ -47,6 +47,10 @@ impl Screen {
pub fn render(&self, p: &Point, char: &str) { pub fn render(&self, p: &Point, char: &str) {
mvprintw(p.1 + self.center.1, p.0 + self.center.0, char); 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] #[test]
@ -75,6 +79,7 @@ fn test_get_valid_movements_board() {
pub enum BoardCommand { pub enum BoardCommand {
Dig(Point), Dig(Point),
LayEgg(Point, u32), LayEgg(Point, u32),
SpawnFood(Point),
Hatch(u32, u32), Hatch(u32, u32),
Noop, Noop,
} }

@ -1,12 +1,13 @@
use crate::lib::screen::{Screen, BoardCommand}; use crate::lib::screen::{Screen, BoardCommand};
use crate::lib::point::Point; use crate::lib::point::Point;
use crate::lib::entity::{Entities, Queen}; use crate::lib::entity::{Entities, Queen, Egg, Ant, Food};
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Clone)] #[derive(Clone)]
pub struct World { pub struct World {
pub cleared: HashSet<Point>, pub cleared: HashSet<Point>,
pub occupied: HashSet<Point>, pub occupied: HashSet<Point>,
pub food: HashSet<u32>
} }
impl World { impl World {
@ -14,6 +15,7 @@ impl World {
World { World {
cleared: HashSet::new(), cleared: HashSet::new(),
occupied: 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); w.clear(pos);
} }
BoardCommand::LayEgg(pos, id) => { 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) => { BoardCommand::Hatch(egg_id, queen_id) => {
let egg = e.data.remove(&egg_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 q = e.data.get_mut(&queen_id).unwrap();
let queen: &mut Queen = q.downcast_mut::<Queen>().unwrap(); let queen: &mut Queen = q.downcast_mut::<Queen>().unwrap();
queen.egg_count -= 1; 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 => {} BoardCommand::Noop => {}
} }

@ -17,14 +17,15 @@ mod lib {
use lib::point::Point; use lib::point::Point;
use lib::screen::init_screen; use lib::screen::init_screen;
use lib::world::{World, simulate, render}; use lib::world::{World, simulate, render};
use lib::entity::Entities; use lib::entity::{Entities, Queen};
fn main() { fn main() {
let mut board = init_screen(); let mut board = init_screen();
let mut world = World::new(); let mut world = World::new();
let mut entities = Entities::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 i in -3..3 {
for j in -3..3 { for j in -3..3 {

@ -2,19 +2,23 @@ use antf::lib::screen::init_screen;
use antf::lib::point::Point; use antf::lib::point::Point;
use antf::lib::ai::AIGoal; use antf::lib::ai::AIGoal;
use antf::lib::world::{World, simulate, render}; use antf::lib::world::{World, simulate, render};
use antf::lib::entity::{Entities, Ant}; use antf::lib::entity::{Entities, Ant, FoodGenerator};
use ncurses::*; use ncurses::*;
use std::thread::sleep; use std::thread::sleep;
use std::time; use std::time;
// make sure to run with --test-threads 1! otherwise output will be bugged
#[test] #[test]
fn test_astar() { fn test_reach_astar() {
let mut board = init_screen(); let mut board = init_screen();
let mut world = World::new(); let mut world = World::new();
let mut entities = Entities::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 a = entities.data.get_mut(&id).unwrap();
let ant: &mut Ant = a.downcast_mut::<Ant>().unwrap(); let ant: &mut Ant = a.downcast_mut::<Ant>().unwrap();
@ -27,6 +31,26 @@ fn test_astar() {
sleep(time::Duration::from_millis(100)); sleep(time::Duration::from_millis(100));
refresh(); refresh();
} }
clear();
endwin(); 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();
}

Loading…
Cancel
Save