diff --git a/src/lib/ai.rs b/src/lib/ai.rs index 5347289..e5f2069 100644 --- a/src/lib/ai.rs +++ b/src/lib/ai.rs @@ -30,12 +30,11 @@ impl AI for Ant { } AIGoal::Idle => {} } - } + } } // return the next move for this ant fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { - let goal = match self.plan.last() { Some(g) => g, None => &AIGoal::Idle, @@ -43,12 +42,18 @@ impl AI for Ant { let choice = match goal { AIGoal::Idle => { - let valid = w.get_valid_movements(&self.pos, b); + // valid_movements does not return any diggables! + let valid = w.get_valid_movements(&self.pos, b, true); let mut rng = thread_rng(); valid.choose(&mut rng).cloned() } AIGoal::Reach(target) => { - let mut movements = astar(&self.pos, &target); + // here astar only produces a path between the target & pos without digging + let result = astar(&self.pos, &target, Some(w), Some(b)); + let mut movements = match result { + Ok(m) => m, + Err(_) => vec![] + }; movements.pop() } }; @@ -57,7 +62,11 @@ impl AI for Ant { let pos = choice.unwrap(); if w.cleared.contains(&pos) { self.pos = pos; - } + } else { + if w.is_safe_to_dig(&self.pos, &pos, &b) { + w.clear(pos); + } + } } BoardCommand::Noop @@ -77,7 +86,7 @@ impl AI for Egg { impl AI for Queen { fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { - let valid: Vec = w.get_valid_movements(&self.pos, b); + let valid: Vec = w.get_valid_movements(&self.pos, b, false); let mut rng = thread_rng(); let choice = valid.choose(&mut rng).cloned(); diff --git a/src/lib/point.rs b/src/lib/point.rs index 3727ba4..fd2e21d 100644 --- a/src/lib/point.rs +++ b/src/lib/point.rs @@ -1,3 +1,5 @@ +use crate::lib::screen::Screen; +use crate::lib::world::World; use std::collections::{HashMap, HashSet}; #[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)] @@ -5,42 +7,32 @@ pub struct Point(pub i32, pub i32); impl Point { pub fn get_neighbors(&self) -> Vec { - vec![ - self.left(), - self.right(), - self.up(), - self.down() - ] + vec![self.left(), self.right(), self.up(), self.down()] } - pub fn is_above(&self, other: &Point) -> bool { - other.up() == *self - } - - pub fn is_below(&self, other: &Point) -> bool { - other.down() == *self + pub fn left(&self) -> Point { + Point(self.0 - 1, self.1) } - pub fn is_left(&self, other: &Point) -> bool { - other.left() == *self + pub fn right(&self) -> Point { + Point(self.0 + 1, self.1) } - pub fn is_right(&self, other:&Point) -> bool { - other.right() == *self + pub fn uldiag(&self) -> Point { + Point(self.0 - 1, self.1 - 1) } - pub fn is_adjacent(&self, other: &Point) -> bool { - other.is_left(self) || other.is_right(self) + pub fn bldiag(&self) -> Point { + Point(self.0 + 1, self.1 - 1) } - pub fn left(&self) -> Point { - Point(self.0 - 1, self.1) + pub fn brdiag(&self) -> Point { + Point(self.0 + 1, self.1 + 1) } - pub fn right(&self) -> Point { - Point(self.0 + 1, self.1) + pub fn urdiag(&self) -> Point { + Point(self.0 - 1, self.1 + 1) } - // y values are reversed in ncurses pub fn down(&self) -> Point { Point(self.0, self.1 + 1) @@ -55,8 +47,18 @@ 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 { - let mut open_set : HashSet = HashSet::new(); +fn weigh_point(w: &World, p: &Point) -> i32 { + if w.is_cleared(p) { + 3 + } else { + 15 + } +} + +pub struct PathNotFoundError; + +pub fn astar(start: &Point, goal: &Point, w: Option<&World>, s: Option<&Screen>) -> Result, PathNotFoundError> { + let mut open_set: HashSet = HashSet::new(); open_set.insert(*start); let mut came_from: HashMap = HashMap::new(); @@ -86,21 +88,37 @@ pub fn astar(start: &Point, goal: &Point) -> Vec { answer.push(current.clone()); } } - break; + return Ok(answer); } open_set.remove(¤t); let current_gscore = gscore[¤t]; - let all_neighbors: Vec = current.get_neighbors(); + let all_neighbors: Vec = match w { + Some(w) => current + .get_neighbors() + .iter() + .filter(|p| w.is_valid_movement(p, s.unwrap())) + .map(|e| e.clone()) + .collect(), + None => current.get_neighbors(), + }; + for neighbor in all_neighbors.iter() { let neighbor_score = if gscore.contains_key(&neighbor) { - gscore[neighbor] + gscore[&neighbor] } else { i32::MAX }; - let score = current_gscore + 1; + + let weight = match w { + Some(w) => weigh_point(&w, neighbor), + None => 1, + }; + + let score = current_gscore + weight; + if score < neighbor_score { gscore.insert(neighbor.clone(), score); fscore.insert(neighbor.clone(), score + manhattan(&neighbor, &goal)); @@ -110,7 +128,7 @@ pub fn astar(start: &Point, goal: &Point) -> Vec { } } - answer + Err(PathNotFoundError) } #[test] @@ -118,9 +136,18 @@ fn test_astar() { let start = Point(0, 0); let goal = Point(10, 10); - let answers: Vec = astar(&start, &goal); - - for a in answers.iter() { - println!("{:?}", &a); + let answers = astar(&start, &goal, None, None); + + match answers { + Ok(ans) => { + for a in ans.iter() { + println!("{:?}", &a); + } + }, + Err(_) => { + panic!("Path not found. That shouldn't happen!") + } } + + } diff --git a/src/lib/screen.rs b/src/lib/screen.rs index 0a2d911..0ec94d6 100644 --- a/src/lib/screen.rs +++ b/src/lib/screen.rs @@ -53,7 +53,7 @@ impl Screen { } } -#[test] +/*#[test] fn test_in_bounds() { let x = 20; let y = 20; @@ -74,7 +74,7 @@ fn test_get_valid_movements_board() { let border = s.get_valid_movements(&Point(19,19)); assert!(border.len() == 2); -} +}*/ pub enum BoardCommand { Dig(Point), diff --git a/src/lib/world.rs b/src/lib/world.rs index 9c009b5..5ae25e4 100644 --- a/src/lib/world.rs +++ b/src/lib/world.rs @@ -1,5 +1,5 @@ use crate::lib::entity::{Ant, Egg, Entities, Food, Queen}; -use crate::lib::point::Point; +use crate::lib::point::{astar, Point}; use crate::lib::screen::{BoardCommand, Screen}; use std::collections::HashSet; @@ -21,12 +21,29 @@ impl World { self.cleared.insert(pos); } - pub fn is_valid_movement(&self, current_pos: &Point, target: &Point, b: &Screen) -> bool { - let mut safe = true; + // make sure that this is as simple & robust as possible + // then, use this set of rules when digging + // call astar from the suggested dig site to origin (or whatever "home" coordinate") + // if route does not exist, do not dig + pub fn is_valid_movement(&self, target: &Point, b: &Screen) -> bool { + let safe = + !self.cleared.contains(&target.down()) + || !self.cleared.contains(&target.left()) + || !self.cleared.contains(&target.right()) + || !self.cleared.contains(&target.bldiag()) + || !self.cleared.contains(&target.uldiag()) + || !self.cleared.contains(&target.brdiag()) + || !self.cleared.contains(&target.urdiag()); + // can only go to target if + // its above or left or right of a not cleared space + + /* if target.is_above(current_pos) { safe = - !self.cleared.contains(&target.left()) || !self.cleared.contains(&target.right()); + (!self.cleared.contains(&target.left()) || !self.cleared.contains(&target.right())) + || (!self.cleared.contains(¤t_pos.right()) && self.cleared.contains(&target.right())) + || (!self.cleared.contains(¤t_pos.left()) && self.cleared.contains(&target.left())); } if target.is_adjacent(current_pos) { @@ -38,17 +55,69 @@ impl World { safe = !self.cleared.contains(&target.down()); } } - b.is_in_bounds(target) && safe + + if target.is_below(current_pos) { + safe = true; + }*/ + + // of course, its only a valid move if you can walk there! + b.is_in_bounds(target) && self.cleared.contains(target) && safe + } + + pub fn is_cleared(&self, pos: &Point) -> bool { + self.cleared.contains(pos) + } + + pub fn is_safe_to_dig(&self, current_pos: &Point, target: &Point, b: &Screen) -> bool { + // create a scenario in which this has been dug out + let mut hypothetical_world = self.clone(); + hypothetical_world.clear(*target); + // test if we can return from the target to the beginning + // if yes, dig + let result = astar(target, &Point(0, 0), Some(&hypothetical_world), Some(b)); + //let to_current_pos = astar(target, current_pos, Some(&hypothetical_world), Some(b)); + /*if target.is_above(current_pos) { + safe = !self.cleared.contains(&target.up()); + } + + if target.is_adjacent(current_pos) { + safe = !self.cleared.contains(&target.up()) && !self.cleared.contains(&target.down()); + } + + if target.is_below(current_pos) { + safe = !self.cleared.contains(&target.down()) && !self.cleared.contains(&target.left()) && !self.cleared.contains(&target.right()); + }*/ + b.is_in_bounds(target) && result.is_ok() } - pub fn get_valid_movements(&self, pos: &Point, b: &Screen) -> Vec { + pub fn get_diggable(&self, b: &Screen, pos: &Point) -> Vec { let moves = b.get_valid_movements(pos); moves .iter() - .filter(|p| self.is_valid_movement(pos, p, b)) + .filter(|p| !self.is_cleared(p)) .map(|p| p.clone()) .collect() } + + pub fn get_valid_movements( + &self, + pos: &Point, + b: &Screen, + return_diggables: bool, + ) -> Vec { + let moves = b.get_valid_movements(pos); + let mut ans: Vec = moves + .iter() + .filter(|p| self.is_valid_movement(p, b)) + .map(|p| p.clone()) + .collect(); + + if return_diggables { + ans.extend(self.get_diggable(b, pos)); + } + + ans + } } pub fn render(e: &Entities, w: &World, b: &Screen) { diff --git a/src/main.rs b/src/main.rs index 9d66608..3bbffcc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -28,13 +28,8 @@ fn main() { entities.add_entity(&q); for i in 0..6 { - for j in 0..6 { - if j != 1 { - world.clear(Point(i, j)); - } else { - world.clear(Point(0, j)); - world.clear(Point(5, j)); - } + for j in 0..1 { + world.clear(Point(i, j)); } } @@ -42,7 +37,7 @@ fn main() { // 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(1000)); + sleep(time::Duration::from_millis(100)); refresh(); } endwin(); diff --git a/tests/ai_test.rs b/tests/ai_test.rs index 5a97d06..ea10e44 100644 --- a/tests/ai_test.rs +++ b/tests/ai_test.rs @@ -1,8 +1,8 @@ -use antf::lib::screen::{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, Food, FoodGenerator}; +use antf::lib::entity::{Ant, Entities, Food, FoodGenerator}; +use antf::lib::point::Point; +use antf::lib::screen::{init_screen, Screen}; +use antf::lib::world::{render, simulate, World}; use ncurses::*; use std::thread::sleep; @@ -12,20 +12,21 @@ use std::time; #[test] 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(); + world.clear(Point(0, 0)); let mut entities = Entities::new(); - - let a = Ant::new(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(); - ant.plan.push(AIGoal::Reach(Point(10,10))); - ant.plan.push(AIGoal::Reach(Point(-10,-10))); - ant.plan.push(AIGoal::Reach(Point(10,-10))); - ant.plan.push(AIGoal::Reach(Point(-10,10))); + let ant: &mut Ant = a.downcast_mut::().unwrap(); + ant.plan.push(AIGoal::Reach(Point(0, 0))); + ant.plan.push(AIGoal::Reach(Point(20, 20))); + ant.plan.push(AIGoal::Reach(Point(0, 0))); + ant.plan.push(AIGoal::Reach(Point(20, 20))); // craps out... need to make sure unwrap() is safe for _ in 0..420 { @@ -41,21 +42,21 @@ fn test_reach_astar() { /*#[test] fn test_drag() { - let mut board = init_screen(); + let mut board = init_screen(); let mut world = World::new(); let mut entities = Entities::new(); - + let f = Food::new(10,10); let a = Ant::new(0,0); let fid = entities.add_entity(&f); let id = entities.add_entity(&a); - + let a = entities.data.get_mut(&id).unwrap(); let ant: &mut Ant = a.downcast_mut::().unwrap(); - //ant.goal = - + //ant.goal = + 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); @@ -64,5 +65,5 @@ fn test_drag() { refresh(); } clear(); - endwin(); + endwin(); }*/