sideways #1

Merged
rostyhn merged 9 commits from sideways into master 9 months ago
  1. 166
      src/lib/ai.rs
  2. 95
      src/lib/entity.rs
  3. 117
      src/lib/point.rs
  4. 49
      src/lib/screen.rs
  5. 108
      src/lib/world.rs
  6. 32
      src/main.rs
  7. 42
      tests/ai_test.rs
  8. 7
      tests/entity_test.rs

@ -1,75 +1,122 @@
use crate::lib::entity::{Ant, Egg, Queen}; use crate::lib::entity::{Ant, Egg, Food, FoodGenerator, Queen};
use crate::lib::point::{astar, Point}; use crate::lib::point::Point;
use crate::lib::screen::{BoardCommand, Screen}; use crate::lib::screen::{BoardCommand, Screen};
use crate::lib::world::World; use crate::lib::world::{Pheremone, World};
use rand::Rng;
use rand::prelude::SliceRandom; use rand::prelude::SliceRandom;
use rand::thread_rng; use rand::thread_rng;
use std::iter::zip;
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, step: u32) -> BoardCommand;
fn plan(&mut self, w: &World) {} fn plan(&mut self, b: &Screen, w: &mut World) -> BoardCommand {
BoardCommand::Noop
}
} }
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub enum AIGoal { pub enum AIGoal {
Reach(Point), Seek,
//Pickup(Point), Return,
//Drop(),
Idle,
} }
impl AI for Ant { impl AI for Ant {
fn plan(&mut self, w: &World) { fn plan(&mut self, b: &Screen, w: &mut World) -> BoardCommand {
// check last part of plan match self.goal {
if let Some(goal) = self.plan.last() { AIGoal::Seek => {
match goal { // if we reach food, we change state
AIGoal::Reach(target) => { if w.food.contains(&self.pos) {
if self.pos == *target { for p in &self.history {
self.plan.pop(); w.drop_pheremone(&p, &self.goal);
} }
self.history.clear();
self.about_face();
self.goal = AIGoal::Return;
} }
AIGoal::Idle => {} BoardCommand::Noop
} }
} AIGoal::Return => {
if w.home.contains(&self.pos) {
for p in &self.history {
w.drop_pheremone(&p, &self.goal);
}
self.history.clear();
self.about_face();
self.goal = AIGoal::Seek;
return BoardCommand::SpawnAnt;
} else {
BoardCommand::Noop
}
}
}
} }
// return the next move for this ant fn step(&mut self, b: &Screen, w: &mut World, t: u32) -> BoardCommand {
fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { let valid = vec![
(self.dir, self.dir.relative_point(&self.pos)),
let goal = match self.plan.last() { (self.dir.ccw(), self.dir.ccw().relative_point(&self.pos)),
Some(g) => g, (self.dir.cw(), self.dir.cw().relative_point(&self.pos)),
None => &AIGoal::Idle, ];
};
let choice = match goal { let ph: Vec<Pheremone> = valid
AIGoal::Idle => { .iter()
let valid = w.get_valid_movements(&self.pos, b); .map(|(_, pnt)| w.get_pheremone(pnt).clone())
let mut rng = thread_rng(); .collect();
valid.choose(&mut rng).cloned()
} let ph_fn = match self.goal {
AIGoal::Reach(target) => { AIGoal::Seek => |ph: &Pheremone| ph.food,
let mut movements = astar(&self.pos, &target); AIGoal::Return => |ph: &Pheremone| ph.home,
movements.pop()
}
}; };
if !choice.is_none() { let r: f32 = rand::random();
let pos = choice.unwrap();
if w.cleared.contains(&pos) { let mut dir = &valid[0].0;
let old_pos = self.pos; if r < 0.1 || ph.len() == 0 {
self.pos = pos; let mut rng = thread_rng();
w.update_occupied(&old_pos, &pos); let choice = valid.choose(&mut rng).unwrap();
} else { dir = &choice.0;
w.clear(pos); } else {
let mut greatest = &ph[0];
for (tup, p) in zip(&valid, &ph) {
if ph_fn(p) > ph_fn(greatest) {
greatest = p;
dir = &tup.0;
}
} }
} }
if dir == &self.dir {
self.forward(b);
} else if dir == &self.dir.cw() {
self.cw();
} else if dir == &self.dir.ccw() {
self.ccw();
}
BoardCommand::Noop
}
}
impl AI for FoodGenerator {
fn step(&mut self, b: &Screen, w: &mut World, t: u32) -> BoardCommand {
// eventually might want to use poisson disk distrib instead
if t % 600 == 0 && self.counter < 10 {
// 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);
self.counter += 1;
return BoardCommand::SpawnFood(Point(r_x, r_y));
}
BoardCommand::Noop BoardCommand::Noop
} }
} }
impl AI for Egg { impl AI for Egg {
fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { fn step(&mut self, b: &Screen, w: &mut World, t: u32) -> BoardCommand {
if self.counter > 0 { if self.counter > 0 {
self.counter -= 1; self.counter -= 1;
return BoardCommand::Noop; return BoardCommand::Noop;
@ -80,26 +127,33 @@ impl AI for Egg {
} }
impl AI for Queen { impl AI for Queen {
fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { fn step(&mut self, b: &Screen, w: &mut World, t: u32) -> BoardCommand {
let valid: Vec<Point> = w.get_valid_movements(&self.pos, b); let valid: Vec<Point> = self.pos.get_neighbors();
let mut rng = thread_rng(); let mut rng = thread_rng();
let choice = valid.choose(&mut rng).cloned(); let choice = valid.choose(&mut rng).cloned();
if !choice.is_none() { if !choice.is_none() {
let pos = choice.unwrap(); let pos = choice.unwrap();
if w.cleared.contains(&pos) { // choose between laying an egg and moving
// choose between laying an egg and moving if self.egg_count < 3 {
if self.egg_count < 3 { self.egg_count += 1;
self.egg_count += 1; return BoardCommand::LayEgg(pos, self.id);
return BoardCommand::LayEgg(pos, self.id); } else {
} else { self.pos = pos;
let old_pos = self.pos;
self.pos = pos;
w.update_occupied(&old_pos, &pos);
}
} }
} }
BoardCommand::Noop BoardCommand::Noop
} }
} }
impl AI for Food {
fn step(&mut self, b: &Screen, w: &mut World, t: u32) -> BoardCommand {
for n in self.pos.get_neighbors() {
w.drop_pheremone(&n, &AIGoal::Return);
}
w.drop_pheremone(&self.pos, &AIGoal::Return);
BoardCommand::Noop
}
}

@ -1,10 +1,8 @@
use crate::lib::ai::{AIGoal, AI}; use crate::lib::ai::{AIGoal, AI};
use crate::lib::point::Point; use crate::lib::point::{Direction, Point};
use crate::lib::screen::{BoardCommand, Screen}; use crate::lib::screen::Screen;
use crate::lib::world::World;
use rand::Rng;
use std::collections::HashMap; use std::collections::{HashMap, HashSet};
use ncurses::*; use ncurses::*;
@ -16,6 +14,7 @@ pub trait Entity: AI + Downcast {
fn set_id(&mut self, id: u32); fn set_id(&mut self, id: u32);
} }
macro_rules! impl_entity { macro_rules! impl_entity {
($t: ident) => { ($t: ident) => {
impl Entity for $t { impl Entity for $t {
@ -40,7 +39,9 @@ impl_downcast!(Renderable);
pub struct Ant { pub struct Ant {
pub pos: Point, pub pos: Point,
pub id: u32, pub id: u32,
pub plan: Vec<AIGoal>, pub goal: AIGoal,
pub dir: Direction,
pub history: HashSet<Point>,
} }
impl Ant { impl Ant {
@ -48,10 +49,37 @@ impl Ant {
Ant { Ant {
pos: Point(x, y), pos: Point(x, y),
id: 0, id: 0,
plan: vec![], goal: AIGoal::Seek,
dir: Direction::Up,
history: HashSet::new(),
}
}
pub fn forward(&mut self, s: &Screen) {
let target = self.dir.relative_point(&self.pos);
if s.is_in_bounds(&target) {
self.history.insert(self.pos);
self.pos = target;
} else {
self.cw();
self.cw();
} }
} }
pub fn ccw(&mut self) {
self.dir = self.dir.ccw();
}
pub fn cw(&mut self) {
self.dir = self.dir.cw();
}
pub fn about_face(&mut self) {
self.cw();
self.cw();
}
} }
impl_entity!(Ant); impl_entity!(Ant);
impl_entity!(Food); impl_entity!(Food);
impl_entity!(FoodGenerator); impl_entity!(FoodGenerator);
@ -60,14 +88,27 @@ impl_entity!(Queen);
impl Renderable for Ant { impl Renderable for Ant {
fn representation(&self) -> &str { fn representation(&self) -> &str {
"o" match self.dir {
Direction::Up => "^",
Direction::Down => "v",
Direction::Right => ">",
Direction::Left => "<",
}
} }
fn before_render(&self) { fn before_render(&self) {
attron(A_BOLD()); attron(A_BOLD());
match self.goal {
AIGoal::Return => attron(A_UNDERLINE()),
AIGoal::Seek => 0,
};
} }
fn after_render(&self) { fn after_render(&self) {
attroff(A_BOLD()); attroff(A_BOLD());
match self.goal {
AIGoal::Return => attroff(A_UNDERLINE()),
AIGoal::Seek => 0,
};
} }
} }
@ -173,7 +214,7 @@ impl Entities {
#[derive(Clone)] #[derive(Clone)]
pub struct Food { pub struct Food {
pos: Point, pub pos: Point,
id: u32, id: u32,
} }
@ -186,14 +227,6 @@ impl Food {
} }
} }
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
}
}
impl Renderable for Food { impl Renderable for Food {
fn representation(&self) -> &str { fn representation(&self) -> &str {
"f" "f"
@ -203,32 +236,20 @@ impl Renderable for Food {
// no position, does not get rendered yet acts per turn // no position, does not get rendered yet acts per turn
#[derive(Clone)] #[derive(Clone)]
pub struct FoodGenerator { pub struct FoodGenerator {
counter: u32, pub timer: u32,
pos: Point, pos: Point,
id: u32 id: u32,
pub counter: u32,
} }
impl FoodGenerator { impl FoodGenerator {
pub fn new() -> FoodGenerator { pub fn new() -> FoodGenerator {
FoodGenerator { counter: 0, id: 0, pos: Point(0,0) } FoodGenerator {
} timer: 0,
} id: 0,
pos: Point(0, 0),
impl AI for FoodGenerator { counter: 0,
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
} }
} }

@ -1,89 +1,68 @@
use std::collections::{HashMap, HashSet};
#[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)] #[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)]
pub struct Point(pub i32, pub i32); pub struct Point(pub i32, pub i32);
impl Point { impl Point {
pub fn get_neighbors(&self) -> Vec<Point> { pub fn get_neighbors(&self) -> Vec<Point> {
vec![ vec![self.left(), self.right(), self.up(), self.down()]
Point(self.0, self.1 + 1),
Point(self.0, self.1 - 1),
Point(self.0 - 1, self.1),
Point(self.0 + 1, self.1),
]
} }
}
fn manhattan(c1: &Point, c2: &Point) -> i32 {
return (c2.0 - c1.0).abs() + (c2.1 - c1.1).abs();
}
pub fn astar(start: &Point, goal: &Point) -> Vec<Point> {
let mut open_set : HashSet<Point> = HashSet::new();
open_set.insert(*start);
let mut came_from: HashMap<Point, Point> = HashMap::new(); pub fn left(&self) -> Point {
Point(self.0 - 1, self.1)
}
let mut gscore: HashMap<Point, i32> = HashMap::new(); pub fn right(&self) -> Point {
gscore.insert(*start, 0); Point(self.0 + 1, self.1)
}
let mut fscore: HashMap<Point, i32> = HashMap::new(); // y values are reversed in ncurses
fscore.insert(*start, manhattan(&start, &goal)); pub fn down(&self) -> Point {
Point(self.0, self.1 + 1)
}
let mut answer = Vec::new(); pub fn up(&self) -> Point {
Point(self.0, self.1 - 1)
}
}
let mut current: Point = *start; #[derive(Clone, Copy, PartialEq, Eq, Debug)]
while !open_set.is_empty() { pub enum Direction {
let mut min_score = i32::MAX; Up = 0,
for c in open_set.iter() { Right = 1,
let val = fscore[c]; Down = 2,
current = if val < min_score { c.clone() } else { current }; Left = 3,
min_score = if val < min_score { val } else { min_score }; }
}
if current == *goal { impl Direction {
answer.push(current.clone()); pub fn from_u8(v: u8) -> Direction {
while came_from.contains_key(&current) { match v {
current = came_from[&current].clone(); 0 => Direction::Up,
if current != *start { 1 => Direction::Right,
answer.push(current.clone()); 2 => Direction::Down,
} 3 => Direction::Left,
} _ => panic!("Shouldn't happen")
break;
} }
}
open_set.remove(&current); pub fn cw(&self) -> Direction {
let val = *self as u8;
let current_gscore = gscore[&current]; Direction::from_u8((val + 1) % 4)
}
let all_neighbors: Vec<Point> = current.get_neighbors(); pub fn ccw(&self) -> Direction {
for neighbor in all_neighbors.iter() { let val = *self as u8;
let neighbor_score = if gscore.contains_key(&neighbor) { if val == 0 {
gscore[neighbor] Direction::Left
} else { } else {
i32::MAX Direction::from_u8(val - 1)
};
let score = current_gscore + 1;
if score < neighbor_score {
gscore.insert(neighbor.clone(), score);
fscore.insert(neighbor.clone(), score + manhattan(&neighbor, &goal));
came_from.insert(neighbor.clone(), current.clone());
open_set.insert(neighbor.clone());
}
} }
} }
answer pub fn relative_point(&self, p: &Point) -> Point {
} match self {
Direction::Up => p.up(),
#[test] Direction::Left => p.left(),
fn test_astar() { Direction::Right => p.right(),
let start = Point(0, 0); Direction::Down => p.down(),
}
let goal = Point(10, 10);
let answers: Vec<Point> = astar(&start, &goal);
for a in answers.iter() {
println!("{:?}", &a);
} }
} }

@ -1,16 +1,17 @@
use crate::lib::point::Point; use crate::lib::point::Point;
use ncurses::*;
pub struct Screen { pub struct Screen {
pub center: Point, pub center: Point,
max_x: i32, pub max_x: i32,
max_y: i32, pub max_y: i32,
} }
use ncurses::*;
pub fn init_screen() -> Screen { pub fn init_screen() -> Screen {
initscr(); initscr();
start_color();
init_pair(0, 0, 1);
/* Invisible cursor. */ /* Invisible cursor. */
curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE); curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);
@ -32,54 +33,22 @@ impl Screen {
} }
pub fn is_in_bounds(&self, pos: &Point) -> bool { pub fn is_in_bounds(&self, pos: &Point) -> bool {
(pos.0 > -self.max_x && pos.0 < self.max_x) && (pos.1 > -self.max_y && pos.1 < self.max_y) (pos.0 > 0 && pos.0 < self.max_x) && (pos.1 > 0 && pos.1 < self.max_y)
}
pub fn get_valid_movements(&self, pos: &Point) -> Vec<Point> {
let binding = pos.get_neighbors();
binding
.iter()
.filter(|e| self.is_in_bounds(e))
.map(|e| e.clone())
.collect()
} }
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, p.0, char);
} }
pub fn get_dimensions(&self) -> (Point, Point) { pub fn get_dimensions(&self) -> (Point, Point) {
(Point(-self.max_x, -self.max_y), Point(self.max_x, self.max_y)) (Point(0,0), Point(self.max_x, self.max_y))
} }
} }
#[test]
fn test_in_bounds() {
let x = 20;
let y = 20;
let s = Screen::new(x, y);
assert!(&s.is_in_bounds(&Point(0, 0)));
assert!(!&s.is_in_bounds(&Point(21, 0)));
}
#[test]
fn test_get_valid_movements_board() {
let x = 20;
let y = 20;
let s = Screen::new(x, y);
let valid = s.get_valid_movements(&Point(0,0));
assert_eq!(valid, Point(0,0).get_neighbors());
let border = s.get_valid_movements(&Point(19,19));
assert!(border.len() == 2);
}
pub enum BoardCommand { pub enum BoardCommand {
Dig(Point),
LayEgg(Point, u32), LayEgg(Point, u32),
SpawnFood(Point), SpawnFood(Point),
Hatch(u32, u32), Hatch(u32, u32),
SpawnAnt,
Noop, Noop,
} }

@ -1,50 +1,77 @@
use crate::lib::screen::{Screen, BoardCommand}; use crate::lib::ai::AIGoal;
use crate::lib::entity::{Ant, Egg, Entities, Food, Queen};
use crate::lib::point::Point; use crate::lib::point::Point;
use crate::lib::entity::{Entities, Queen, Egg, Ant, Food}; use crate::lib::screen::{BoardCommand, Screen};
use std::collections::HashSet; use std::collections::HashSet;
use ncurses::*;
#[derive(Clone)] #[derive(Clone)]
pub struct World { pub struct World {
pub cleared: HashSet<Point>, pub pheremones: Vec<Pheremone>,
pub occupied: HashSet<Point>, pub food: HashSet<Point>,
pub food: HashSet<u32> pub home: HashSet<Point>,
width: usize,
}
#[derive(Clone, PartialEq, PartialOrd, Debug)]
pub struct Pheremone {
pub home: u32,
pub food: u32,
}
impl Pheremone {
pub fn new() -> Pheremone {
Pheremone { home: 0, food: 0 }
}
pub fn decay(&mut self) {
if self.home > 0 {
self.home -= 1;
}
if self.food > 0 {
self.food -= 1;
}
}
} }
impl World { impl World {
pub fn new() -> World { pub fn new(grid_size: &Point) -> World {
let x = grid_size.0;
let y = grid_size.1;
let ph: Vec<Pheremone> = vec![Pheremone::new(); (x+1) as usize * (y+1) as usize];
World { World {
cleared: HashSet::new(), pheremones: ph,
occupied: HashSet::new(), food: HashSet::new(),
food: HashSet::new() home: HashSet::new(), // should be a property per colony
width: (y as usize), // width of map for Pheremone calculation
} }
} }
pub fn clear(&mut self, pos: Point) { pub fn get_mut_pheremone(&mut self, pos: &Point) -> &mut Pheremone {
self.cleared.insert(pos); &mut self.pheremones[((pos.0 as usize) * self.width) + (pos.1 as usize)]
} }
pub fn is_valid_movement(&self, pos: &Point, b: &Screen) -> bool { pub fn get_pheremone(&self, pos: &Point) -> &Pheremone {
b.is_in_bounds(pos) && !self.occupied.contains(pos) &self.pheremones[((pos.0 as usize) * self.width) + (pos.1 as usize)]
} }
pub fn get_valid_movements(&self, pos: &Point, b: &Screen) -> Vec<Point> { pub fn drop_pheremone(&mut self, pos: &Point, state: &AIGoal) {
let moves = b.get_valid_movements(pos); let ph = self.get_mut_pheremone(pos);
moves
.iter()
.filter(|p| !self.occupied.contains(p))
.map(|p| p.clone())
.collect()
}
pub fn update_occupied(&mut self, old: &Point, new: &Point) { match state {
self.occupied.remove(old); AIGoal::Seek => ph.home += 1,
self.occupied.insert(*new); AIGoal::Return => ph.food += 1,
}
} }
} }
pub fn render(e: &Entities, w: &World, b: &Screen) { pub fn render(e: &Entities, w: &World, b: &Screen) {
for c in w.cleared.iter() { erase();
b.render(c, "x");
for h in w.home.iter() {
b.render(h, "x");
} }
for a in e.data.values() { for a in e.data.values() {
@ -52,20 +79,17 @@ pub fn render(e: &Entities, w: &World, b: &Screen) {
} }
} }
pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) { pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen, step: u32) {
let cmds: Vec<BoardCommand> = e let plan_cmds: Vec<BoardCommand> = e.data.values_mut().map(|a| a.plan(b, w)).collect();
.data let mut cmds: Vec<BoardCommand> = e.data.values_mut().map(|a| a.step(b, w, step)).collect();
.values_mut()
.map(|a| { cmds.extend(plan_cmds);
a.plan(w);
a.step(b, w)
})
.collect();
for cmd in cmds { for cmd in cmds {
match cmd { match cmd {
BoardCommand::Dig(pos) => { BoardCommand::SpawnAnt => {
w.clear(pos); let ant = Ant::new(b.center.0, b.center.1);
e.add_entity(&ant);
} }
BoardCommand::LayEgg(pos, id) => { BoardCommand::LayEgg(pos, id) => {
let egg = Egg::new(pos.0, pos.1, id); let egg = Egg::new(pos.0, pos.1, id);
@ -83,9 +107,17 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) {
} }
BoardCommand::SpawnFood(pos) => { BoardCommand::SpawnFood(pos) => {
let food = Food::new(pos.0, pos.1); let food = Food::new(pos.0, pos.1);
e.add_entity(&food); e.add_entity(&food);
w.food.insert(pos);
} }
BoardCommand::Noop => {} BoardCommand::Noop => {}
} }
} }
// decay all pheremones by some amount
if step % 600 == 0 {
for ph in w.pheremones.iter_mut() {
ph.decay();
}
}
} }

@ -7,38 +7,42 @@ use std::thread::sleep;
use std::time; use std::time;
mod lib { mod lib {
pub mod ai;
pub mod entity;
pub mod point; pub mod point;
pub mod screen; pub mod screen;
pub mod world; pub mod world;
pub mod entity;
pub mod ai;
} }
use lib::entity::{Ant, Entities, FoodGenerator};
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::{render, simulate, World};
use lib::entity::{Entities, Queen};
fn main() { fn main() {
let mut board = init_screen(); let mut screen = init_screen();
let mut world = World::new(); let mut world = World::new(&Point(screen.max_x, screen.max_y));
world.home.insert(screen.center);
let mut entities = Entities::new(); let mut entities = Entities::new();
let q = Queen::new(0,0);
entities.add_entity(&q);
for i in -3..3 { let fg = FoodGenerator::new();
for j in -3..3 { entities.add_entity(&fg);
world.clear(Point(i, j));
} for _ in 0..5 {
let mut a = Ant::new(screen.center.0, screen.center.1);
a.goal = lib::ai::AIGoal::Seek;
entities.add_entity(&a);
} }
let mut t = 0;
loop { loop {
// TODO: add way to break out of the loop by hitting a random key // TODO: add way to break out of the loop by hitting a random key
simulate(&mut entities, &mut world, &mut board); simulate(&mut entities, &mut world, &mut screen, t);
render(&entities, &world, &board); render(&entities, &world, &screen);
sleep(time::Duration::from_millis(100)); sleep(time::Duration::from_millis(100));
refresh(); refresh();
t += 1;
} }
endwin(); endwin();
} }

@ -1,8 +1,8 @@
use antf::lib::screen::{Screen, init_screen};
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::entity::{Ant, Entities, Food, FoodGenerator};
use antf::lib::entity::{Entities, Ant, Food, FoodGenerator}; use antf::lib::point::Point;
use antf::lib::screen::{init_screen, Screen};
use antf::lib::world::{render, simulate, World};
use ncurses::*; use ncurses::*;
use std::thread::sleep; use std::thread::sleep;
@ -12,25 +12,25 @@ use std::time;
#[test] #[test]
fn test_reach_astar() { fn test_reach_astar() {
let mut board = init_screen();//Screen::new(40,40); let mut board = init_screen(); //Screen::new(40,40);
let mut world = World::new(); let mut world = World::new(&Point(board.max_x, board.max_y));
let mut entities = Entities::new(); let mut entities = Entities::new();
let a = Ant::new(0,0); let a = Ant::new(0, 0);
let id = entities.add_entity(&a); 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();
ant.plan.push(AIGoal::Reach(Point(10,10))); /*ant.plan.push(AIGoal::Reach(Point(0, 0)));
ant.plan.push(AIGoal::Reach(Point(-10,-10))); ant.plan.push(AIGoal::Reach(Point(20, 20)));
ant.plan.push(AIGoal::Reach(Point(10,-10))); ant.plan.push(AIGoal::Reach(Point(0, 0)));
ant.plan.push(AIGoal::Reach(Point(-10,10))); ant.plan.push(AIGoal::Reach(Point(20, 20)));*/
// craps out... need to make sure unwrap() is safe // craps out... need to make sure unwrap() is safe
for _ in 0..420 { for t in 0..420 {
// TODO: add way to break out of the loop by hitting a random key // TODO: add way to break out of the loop by hitting a random key
simulate(&mut entities, &mut world, &mut board); simulate(&mut entities, &mut world, &mut board, t);
render(&entities, &world, &board); render(&entities, &world, &board);
sleep(time::Duration::from_millis(100)); sleep(time::Duration::from_millis(100));
refresh(); refresh();
@ -41,21 +41,21 @@ fn test_reach_astar() {
/*#[test] /*#[test]
fn test_drag() { fn test_drag() {
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 f = Food::new(10,10); let f = Food::new(10,10);
let a = Ant::new(0,0); let a = Ant::new(0,0);
let fid = entities.add_entity(&f); let fid = entities.add_entity(&f);
let id = entities.add_entity(&a); 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();
//ant.goal = //ant.goal =
for _ in 0..60 { for _ in 0..60 {
// TODO: add way to break out of the loop by hitting a random key // TODO: add way to break out of the loop by hitting a random key
simulate(&mut entities, &mut world, &mut board); simulate(&mut entities, &mut world, &mut board);
@ -64,5 +64,5 @@ fn test_drag() {
refresh(); refresh();
} }
clear(); clear();
endwin(); endwin();
}*/ }*/

@ -1,6 +1,7 @@
use antf::lib::screen::init_screen; use antf::lib::screen::init_screen;
use antf::lib::world::{World, simulate, render}; use antf::lib::world::{World, simulate, render};
use antf::lib::entity::{Entities, FoodGenerator}; use antf::lib::entity::{Entities, FoodGenerator};
use antf::lib::point::Point;
use ncurses::*; use ncurses::*;
use std::thread::sleep; use std::thread::sleep;
@ -9,15 +10,15 @@ use std::time;
#[test] #[test]
fn test_foodgen() { fn test_foodgen() {
let mut board = init_screen(); let mut board = init_screen();
let mut world = World::new(); let mut world = World::new(&Point(board.max_x, board.max_y));
let mut entities = Entities::new(); let mut entities = Entities::new();
let fg = FoodGenerator::new(); let fg = FoodGenerator::new();
entities.add_entity(&fg); entities.add_entity(&fg);
for _ in 0..60 { for t in 0..60 {
// TODO: add way to break out of the loop by hitting a random key // TODO: add way to break out of the loop by hitting a random key
simulate(&mut entities, &mut world, &mut board); simulate(&mut entities, &mut world, &mut board, t);
render(&entities, &world, &board); render(&entities, &world, &board);
sleep(time::Duration::from_millis(100)); sleep(time::Duration::from_millis(100));
refresh(); refresh();

Loading…
Cancel
Save