semi working dig routine

sideways
Rostyslav Hnatyshyn 9 months ago
parent ded96cc4b9
commit 6508b51fb3
  1. 17
      src/lib/ai.rs
  2. 93
      src/lib/point.rs
  3. 4
      src/lib/screen.rs
  4. 83
      src/lib/world.rs
  5. 11
      src/main.rs
  6. 21
      tests/ai_test.rs

@ -35,7 +35,6 @@ impl AI for Ant {
// return the next move for this ant // return the next move for this ant
fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand {
let goal = match self.plan.last() { let goal = match self.plan.last() {
Some(g) => g, Some(g) => g,
None => &AIGoal::Idle, None => &AIGoal::Idle,
@ -43,12 +42,18 @@ impl AI for Ant {
let choice = match goal { let choice = match goal {
AIGoal::Idle => { 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(); let mut rng = thread_rng();
valid.choose(&mut rng).cloned() valid.choose(&mut rng).cloned()
} }
AIGoal::Reach(target) => { 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() movements.pop()
} }
}; };
@ -57,6 +62,10 @@ impl AI for Ant {
let pos = choice.unwrap(); let pos = choice.unwrap();
if w.cleared.contains(&pos) { if w.cleared.contains(&pos) {
self.pos = pos; self.pos = pos;
} else {
if w.is_safe_to_dig(&self.pos, &pos, &b) {
w.clear(pos);
}
} }
} }
@ -77,7 +86,7 @@ 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) -> BoardCommand {
let valid: Vec<Point> = w.get_valid_movements(&self.pos, b); let valid: Vec<Point> = w.get_valid_movements(&self.pos, b, false);
let mut rng = thread_rng(); let mut rng = thread_rng();
let choice = valid.choose(&mut rng).cloned(); let choice = valid.choose(&mut rng).cloned();

@ -1,3 +1,5 @@
use crate::lib::screen::Screen;
use crate::lib::world::World;
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
#[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)] #[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)]
@ -5,42 +7,32 @@ 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()]
self.left(),
self.right(),
self.up(),
self.down()
]
} }
pub fn is_above(&self, other: &Point) -> bool { pub fn left(&self) -> Point {
other.up() == *self Point(self.0 - 1, self.1)
}
pub fn is_below(&self, other: &Point) -> bool {
other.down() == *self
} }
pub fn is_left(&self, other: &Point) -> bool { pub fn right(&self) -> Point {
other.left() == *self Point(self.0 + 1, self.1)
} }
pub fn is_right(&self, other:&Point) -> bool { pub fn uldiag(&self) -> Point {
other.right() == *self Point(self.0 - 1, self.1 - 1)
} }
pub fn is_adjacent(&self, other: &Point) -> bool { pub fn bldiag(&self) -> Point {
other.is_left(self) || other.is_right(self) Point(self.0 + 1, self.1 - 1)
} }
pub fn left(&self) -> Point { pub fn brdiag(&self) -> Point {
Point(self.0 - 1, self.1) Point(self.0 + 1, self.1 + 1)
} }
pub fn right(&self) -> Point { pub fn urdiag(&self) -> Point {
Point(self.0 + 1, self.1) Point(self.0 - 1, self.1 + 1)
} }
// y values are reversed in ncurses // y values are reversed in ncurses
pub fn down(&self) -> Point { pub fn down(&self) -> Point {
Point(self.0, self.1 + 1) 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(); return (c2.0 - c1.0).abs() + (c2.1 - c1.1).abs();
} }
pub fn astar(start: &Point, goal: &Point) -> Vec<Point> { fn weigh_point(w: &World, p: &Point) -> i32 {
let mut open_set : HashSet<Point> = HashSet::new(); 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<Vec<Point>, PathNotFoundError> {
let mut open_set: HashSet<Point> = HashSet::new();
open_set.insert(*start); open_set.insert(*start);
let mut came_from: HashMap<Point, Point> = HashMap::new(); let mut came_from: HashMap<Point, Point> = HashMap::new();
@ -86,21 +88,37 @@ pub fn astar(start: &Point, goal: &Point) -> Vec<Point> {
answer.push(current.clone()); answer.push(current.clone());
} }
} }
break; return Ok(answer);
} }
open_set.remove(&current); open_set.remove(&current);
let current_gscore = gscore[&current]; let current_gscore = gscore[&current];
let all_neighbors: Vec<Point> = current.get_neighbors(); let all_neighbors: Vec<Point> = 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() { for neighbor in all_neighbors.iter() {
let neighbor_score = if gscore.contains_key(&neighbor) { let neighbor_score = if gscore.contains_key(&neighbor) {
gscore[neighbor] gscore[&neighbor]
} else { } else {
i32::MAX 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 { if score < neighbor_score {
gscore.insert(neighbor.clone(), score); gscore.insert(neighbor.clone(), score);
fscore.insert(neighbor.clone(), score + manhattan(&neighbor, &goal)); fscore.insert(neighbor.clone(), score + manhattan(&neighbor, &goal));
@ -110,7 +128,7 @@ pub fn astar(start: &Point, goal: &Point) -> Vec<Point> {
} }
} }
answer Err(PathNotFoundError)
} }
#[test] #[test]
@ -118,9 +136,18 @@ fn test_astar() {
let start = Point(0, 0); let start = Point(0, 0);
let goal = Point(10, 10); let goal = Point(10, 10);
let answers: Vec<Point> = astar(&start, &goal); let answers = astar(&start, &goal, None, None);
for a in answers.iter() { match answers {
println!("{:?}", &a); Ok(ans) => {
for a in ans.iter() {
println!("{:?}", &a);
}
},
Err(_) => {
panic!("Path not found. That shouldn't happen!")
}
} }
} }

@ -53,7 +53,7 @@ impl Screen {
} }
} }
#[test] /*#[test]
fn test_in_bounds() { fn test_in_bounds() {
let x = 20; let x = 20;
let y = 20; let y = 20;
@ -74,7 +74,7 @@ fn test_get_valid_movements_board() {
let border = s.get_valid_movements(&Point(19,19)); let border = s.get_valid_movements(&Point(19,19));
assert!(border.len() == 2); assert!(border.len() == 2);
} }*/
pub enum BoardCommand { pub enum BoardCommand {
Dig(Point), Dig(Point),

@ -1,5 +1,5 @@
use crate::lib::entity::{Ant, Egg, Entities, Food, Queen}; 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 crate::lib::screen::{BoardCommand, Screen};
use std::collections::HashSet; use std::collections::HashSet;
@ -21,12 +21,29 @@ impl World {
self.cleared.insert(pos); self.cleared.insert(pos);
} }
pub fn is_valid_movement(&self, current_pos: &Point, target: &Point, b: &Screen) -> bool { // make sure that this is as simple & robust as possible
let mut safe = true; // 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) { if target.is_above(current_pos) {
safe = safe =
!self.cleared.contains(&target.left()) || !self.cleared.contains(&target.right()); (!self.cleared.contains(&target.left()) || !self.cleared.contains(&target.right()))
|| (!self.cleared.contains(&current_pos.right()) && self.cleared.contains(&target.right()))
|| (!self.cleared.contains(&current_pos.left()) && self.cleared.contains(&target.left()));
} }
if target.is_adjacent(current_pos) { if target.is_adjacent(current_pos) {
@ -38,17 +55,69 @@ impl World {
safe = !self.cleared.contains(&target.down()); 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<Point> { pub fn get_diggable(&self, b: &Screen, pos: &Point) -> Vec<Point> {
let moves = b.get_valid_movements(pos); let moves = b.get_valid_movements(pos);
moves moves
.iter() .iter()
.filter(|p| self.is_valid_movement(pos, p, b)) .filter(|p| !self.is_cleared(p))
.map(|p| p.clone()) .map(|p| p.clone())
.collect() .collect()
} }
pub fn get_valid_movements(
&self,
pos: &Point,
b: &Screen,
return_diggables: bool,
) -> Vec<Point> {
let moves = b.get_valid_movements(pos);
let mut ans: Vec<Point> = 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) { pub fn render(e: &Entities, w: &World, b: &Screen) {

@ -28,13 +28,8 @@ fn main() {
entities.add_entity(&q); entities.add_entity(&q);
for i in 0..6 { for i in 0..6 {
for j in 0..6 { for j in 0..1 {
if j != 1 { world.clear(Point(i, j));
world.clear(Point(i, j));
} else {
world.clear(Point(0, j));
world.clear(Point(5, j));
}
} }
} }
@ -42,7 +37,7 @@ fn main() {
// 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);
render(&entities, &world, &board); render(&entities, &world, &board);
sleep(time::Duration::from_millis(1000)); sleep(time::Duration::from_millis(100));
refresh(); refresh();
} }
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,20 +12,21 @@ 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();
world.clear(Point(0, 0));
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 _ in 0..420 {

Loading…
Cancel
Save