From ded96cc4b939ba87399cf9c41e974f5c659b72bd Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Wed, 3 Jan 2024 15:33:38 -0700 Subject: [PATCH 1/9] render sideways correctly, added movement restrictions --- src/lib/ai.rs | 8 +------- src/lib/point.rs | 45 +++++++++++++++++++++++++++++++++++++++++---- src/lib/screen.rs | 4 ++-- src/lib/world.rs | 43 ++++++++++++++++++++++++++----------------- src/main.rs | 15 ++++++++++----- 5 files changed, 80 insertions(+), 35 deletions(-) diff --git a/src/lib/ai.rs b/src/lib/ai.rs index 0aebb09..5347289 100644 --- a/src/lib/ai.rs +++ b/src/lib/ai.rs @@ -56,12 +56,8 @@ impl AI for Ant { if !choice.is_none() { let pos = choice.unwrap(); if w.cleared.contains(&pos) { - let old_pos = self.pos; self.pos = pos; - w.update_occupied(&old_pos, &pos); - } else { - w.clear(pos); - } + } } BoardCommand::Noop @@ -94,9 +90,7 @@ impl AI for Queen { self.egg_count += 1; return BoardCommand::LayEgg(pos, self.id); } else { - let old_pos = self.pos; self.pos = pos; - w.update_occupied(&old_pos, &pos); } } } diff --git a/src/lib/point.rs b/src/lib/point.rs index 084e3c7..3727ba4 100644 --- a/src/lib/point.rs +++ b/src/lib/point.rs @@ -6,12 +6,49 @@ pub struct Point(pub i32, pub i32); impl Point { pub fn get_neighbors(&self) -> Vec { vec![ - Point(self.0, self.1 + 1), - Point(self.0, self.1 - 1), - Point(self.0 - 1, self.1), - Point(self.0 + 1, self.1), + 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 is_left(&self, other: &Point) -> bool { + other.left() == *self + } + + pub fn is_right(&self, other:&Point) -> bool { + other.right() == *self + } + + pub fn is_adjacent(&self, other: &Point) -> bool { + other.is_left(self) || other.is_right(self) + } + + pub fn left(&self) -> Point { + Point(self.0 - 1, self.1) + } + + pub fn right(&self) -> Point { + Point(self.0 + 1, self.1) + } + + // y values are reversed in ncurses + pub fn down(&self) -> Point { + Point(self.0, self.1 + 1) + } + + pub fn up(&self) -> Point { + Point(self.0, self.1 - 1) + } } fn manhattan(c1: &Point, c2: &Point) -> i32 { diff --git a/src/lib/screen.rs b/src/lib/screen.rs index 8b07159..0a2d911 100644 --- a/src/lib/screen.rs +++ b/src/lib/screen.rs @@ -32,7 +32,7 @@ impl Screen { } 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 { @@ -45,7 +45,7 @@ impl Screen { } 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) { diff --git a/src/lib/world.rs b/src/lib/world.rs index b851498..9c009b5 100644 --- a/src/lib/world.rs +++ b/src/lib/world.rs @@ -1,21 +1,19 @@ -use crate::lib::screen::{Screen, BoardCommand}; +use crate::lib::entity::{Ant, Egg, Entities, Food, Queen}; use crate::lib::point::Point; -use crate::lib::entity::{Entities, Queen, Egg, Ant, Food}; +use crate::lib::screen::{BoardCommand, Screen}; use std::collections::HashSet; #[derive(Clone)] pub struct World { pub cleared: HashSet, - pub occupied: HashSet, - pub food: HashSet + pub food: HashSet, } impl World { pub fn new() -> World { World { cleared: HashSet::new(), - occupied: HashSet::new(), - food: HashSet::new() + food: HashSet::new(), } } @@ -23,23 +21,34 @@ impl World { self.cleared.insert(pos); } - pub fn is_valid_movement(&self, pos: &Point, b: &Screen) -> bool { - b.is_in_bounds(pos) && !self.occupied.contains(pos) + pub fn is_valid_movement(&self, current_pos: &Point, target: &Point, b: &Screen) -> bool { + let mut safe = true; + + if target.is_above(current_pos) { + safe = + !self.cleared.contains(&target.left()) || !self.cleared.contains(&target.right()); + } + + if target.is_adjacent(current_pos) { + let target_down = target.down(); + if self.cleared.contains(&target_down) { + safe = !self.cleared.contains(&target_down.left()) + || !self.cleared.contains(&target_down.right()); + } else { + safe = !self.cleared.contains(&target.down()); + } + } + b.is_in_bounds(target) && safe } pub fn get_valid_movements(&self, pos: &Point, b: &Screen) -> Vec { let moves = b.get_valid_movements(pos); moves .iter() - .filter(|p| !self.occupied.contains(p)) + .filter(|p| self.is_valid_movement(pos, p, b)) .map(|p| p.clone()) .collect() } - - pub fn update_occupied(&mut self, old: &Point, new: &Point) { - self.occupied.remove(old); - self.occupied.insert(*new); - } } pub fn render(e: &Entities, w: &World, b: &Screen) { @@ -56,9 +65,9 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) { let cmds: Vec = e .data .values_mut() - .map(|a| { + .map(|a| { a.plan(w); - a.step(b, w) + a.step(b, w) }) .collect(); @@ -83,7 +92,7 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) { } BoardCommand::SpawnFood(pos) => { let food = Food::new(pos.0, pos.1); - e.add_entity(&food); + e.add_entity(&food); } BoardCommand::Noop => {} } diff --git a/src/main.rs b/src/main.rs index 2bfef4a..9d66608 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,12 +24,17 @@ fn main() { let mut world = World::new(); let mut entities = Entities::new(); - let q = Queen::new(0,0); + let q = Queen::new(3,0); entities.add_entity(&q); - for i in -3..3 { - for j in -3..3 { - world.clear(Point(i, j)); + 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)); + } } } @@ -37,7 +42,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(100)); + sleep(time::Duration::from_millis(1000)); refresh(); } endwin(); From 6508b51fb3356a4740354bb04ccf892fc0d3f04d Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Thu, 4 Jan 2024 12:12:50 -0700 Subject: [PATCH 2/9] semi working dig routine --- src/lib/ai.rs | 21 ++++++++--- src/lib/point.rs | 95 ++++++++++++++++++++++++++++++----------------- src/lib/screen.rs | 4 +- src/lib/world.rs | 83 +++++++++++++++++++++++++++++++++++++---- src/main.rs | 11 ++---- tests/ai_test.rs | 37 +++++++++--------- 6 files changed, 176 insertions(+), 75 deletions(-) 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(); }*/ From b718397682e06affe9c93559ed3c2bcca0d259f9 Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Thu, 4 Jan 2024 12:27:02 -0700 Subject: [PATCH 3/9] chamber func, falling is valid move --- src/lib/ai.rs | 2 +- src/lib/point.rs | 6 ++++- src/lib/world.rs | 67 +++++++++++++----------------------------------- src/main.rs | 8 ++---- 4 files changed, 26 insertions(+), 57 deletions(-) diff --git a/src/lib/ai.rs b/src/lib/ai.rs index e5f2069..deabf10 100644 --- a/src/lib/ai.rs +++ b/src/lib/ai.rs @@ -63,7 +63,7 @@ impl AI for Ant { if w.cleared.contains(&pos) { self.pos = pos; } else { - if w.is_safe_to_dig(&self.pos, &pos, &b) { + if w.is_safe_to_dig(&pos, &b) { w.clear(pos); } } diff --git a/src/lib/point.rs b/src/lib/point.rs index fd2e21d..46aab2d 100644 --- a/src/lib/point.rs +++ b/src/lib/point.rs @@ -14,6 +14,10 @@ impl Point { Point(self.0 - 1, self.1) } + pub fn is_below(&self, other: Point) -> bool { + other.down() == *self + } + pub fn right(&self) -> Point { Point(self.0 + 1, self.1) } @@ -99,7 +103,7 @@ pub fn astar(start: &Point, goal: &Point, w: Option<&World>, s: Option<&Screen>) Some(w) => current .get_neighbors() .iter() - .filter(|p| w.is_valid_movement(p, s.unwrap())) + .filter(|p| w.is_valid_movement(¤t, p, s.unwrap())) .map(|e| e.clone()) .collect(), None => current.get_neighbors(), diff --git a/src/lib/world.rs b/src/lib/world.rs index 5ae25e4..b74341d 100644 --- a/src/lib/world.rs +++ b/src/lib/world.rs @@ -21,44 +21,28 @@ impl World { self.cleared.insert(pos); } - // 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 = + pub fn create_chamber(&mut self, center: Point, radius: i32) { + let cx = center.0; + let cy = center.1; + + for i in cx-radius..cx+radius { + for j in cy-radius..cy+radius { + self.clear(Point(i,j)); + } + } + } + + pub fn is_valid_movement(&self, current: &Point, target: &Point, b: &Screen) -> bool { + // should allow down movements always + + let safe = target.is_below(*current) || ( !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(¤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) { - let target_down = target.down(); - if self.cleared.contains(&target_down) { - safe = !self.cleared.contains(&target_down.left()) - || !self.cleared.contains(&target_down.right()); - } else { - safe = !self.cleared.contains(&target.down()); - } - } - - if target.is_below(current_pos) { - safe = true; - }*/ + || !self.cleared.contains(&target.urdiag())); // of course, its only a valid move if you can walk there! b.is_in_bounds(target) && self.cleared.contains(target) && safe @@ -68,25 +52,10 @@ impl World { 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 + pub fn is_safe_to_dig(&self, target: &Point, b: &Screen) -> bool { 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() } @@ -108,7 +77,7 @@ impl World { let moves = b.get_valid_movements(pos); let mut ans: Vec = moves .iter() - .filter(|p| self.is_valid_movement(p, b)) + .filter(|p| self.is_valid_movement(pos, p, b)) .map(|p| p.clone()) .collect(); diff --git a/src/main.rs b/src/main.rs index 3bbffcc..4b511cb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,14 +24,10 @@ fn main() { let mut world = World::new(); let mut entities = Entities::new(); - let q = Queen::new(3,0); + let q = Queen::new(board.center.0,board.center.1); entities.add_entity(&q); - for i in 0..6 { - for j in 0..1 { - world.clear(Point(i, j)); - } - } + world.create_chamber(Point(board.center.0, board.center.1), 3); loop { // TODO: add way to break out of the loop by hitting a random key From 1e3032c51be58acf6c1f0b6036b133dab9dbdb54 Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Thu, 4 Jan 2024 12:37:55 -0700 Subject: [PATCH 4/9] fix foodgen to use correct dim, add limit on food --- src/lib/entity.rs | 14 ++++++++------ src/lib/screen.rs | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/lib/entity.rs b/src/lib/entity.rs index e768176..c8f7ac3 100644 --- a/src/lib/entity.rs +++ b/src/lib/entity.rs @@ -203,20 +203,21 @@ impl Renderable for Food { // no position, does not get rendered yet acts per turn #[derive(Clone)] pub struct FoodGenerator { - counter: u32, + timer: u32, pos: Point, - id: u32 + id: u32, + counter: u32 } impl FoodGenerator { pub fn new() -> FoodGenerator { - FoodGenerator { counter: 0, id: 0, pos: Point(0,0) } + FoodGenerator { timer: 0, id: 0, pos: Point(0,0), counter: 0 } } } impl AI for FoodGenerator { fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { - if self.counter % 600 == 0 { + if self.timer % 600 == 0 && self.counter < 10 { // generate random coords, if valid, spawn // if not, try again next step let (min, max) = b.get_dimensions(); @@ -224,10 +225,11 @@ impl AI for FoodGenerator { 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)); } - self.counter += 1; + self.timer += 1; BoardCommand::Noop } } diff --git a/src/lib/screen.rs b/src/lib/screen.rs index 0ec94d6..ac6c73d 100644 --- a/src/lib/screen.rs +++ b/src/lib/screen.rs @@ -49,7 +49,7 @@ impl Screen { } 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)) } } From 47c6eff011eb5bbaaa19972d293b76d2f9c2ff5b Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Thu, 4 Jan 2024 14:32:28 -0700 Subject: [PATCH 5/9] added seek --- src/lib/ai.rs | 39 ++++++++++++++++----------------------- src/lib/entity.rs | 1 + src/lib/world.rs | 43 +++++++++++++++++++++++-------------------- src/main.rs | 14 +++++++++++--- 4 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/lib/ai.rs b/src/lib/ai.rs index deabf10..23381fe 100644 --- a/src/lib/ai.rs +++ b/src/lib/ai.rs @@ -12,7 +12,7 @@ pub trait AI { #[derive(Clone, Debug)] pub enum AIGoal { - Reach(Point), + Seek, //Pickup(Point), //Drop(), Idle, @@ -23,8 +23,9 @@ impl AI for Ant { // check last part of plan if let Some(goal) = self.plan.last() { match goal { - AIGoal::Reach(target) => { - if self.pos == *target { + AIGoal::Seek => { + // if we reach food, we change state + if w.food.contains(&self.pos) { self.plan.pop(); } } @@ -40,31 +41,23 @@ impl AI for Ant { None => &AIGoal::Idle, }; - let choice = match goal { - AIGoal::Idle => { - // 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) => { - // 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() - } - }; - + let valid = w.get_valid_movements(&self.pos, b, true); + let mut rng = thread_rng(); + let choice = valid.choose(&mut rng).cloned(); + if !choice.is_none() { let pos = choice.unwrap(); if w.cleared.contains(&pos) { self.pos = pos; } else { - if w.is_safe_to_dig(&pos, &b) { - w.clear(pos); + + match goal { + AIGoal::Seek => { + if w.is_safe_to_dig(&pos, &b) { + w.clear(pos); + } + }, + AIGoal::Idle => {} } } } diff --git a/src/lib/entity.rs b/src/lib/entity.rs index c8f7ac3..c22168a 100644 --- a/src/lib/entity.rs +++ b/src/lib/entity.rs @@ -217,6 +217,7 @@ impl FoodGenerator { impl AI for FoodGenerator { fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { + // eventually might want to use poisson disk distrib instead if self.timer % 600 == 0 && self.counter < 10 { // generate random coords, if valid, spawn // if not, try again next step diff --git a/src/lib/world.rs b/src/lib/world.rs index b74341d..222bcaf 100644 --- a/src/lib/world.rs +++ b/src/lib/world.rs @@ -6,7 +6,7 @@ use std::collections::HashSet; #[derive(Clone)] pub struct World { pub cleared: HashSet, - pub food: HashSet, + pub food: HashSet, } impl World { @@ -22,27 +22,27 @@ impl World { } pub fn create_chamber(&mut self, center: Point, radius: i32) { - let cx = center.0; - let cy = center.1; - - for i in cx-radius..cx+radius { - for j in cy-radius..cy+radius { - self.clear(Point(i,j)); - } - } + let cx = center.0; + let cy = center.1; + + for i in cx - radius..cx + radius { + for j in cy - radius..cy + radius { + self.clear(Point(i, j)); + } + } } pub fn is_valid_movement(&self, current: &Point, target: &Point, b: &Screen) -> bool { // should allow down movements always - - let safe = target.is_below(*current) || ( - !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())); + + let safe = target.is_below(*current) + || (!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())); // of course, its only a valid move if you can walk there! b.is_in_bounds(target) && self.cleared.contains(target) && safe @@ -55,7 +55,7 @@ impl World { pub fn is_safe_to_dig(&self, target: &Point, b: &Screen) -> bool { let mut hypothetical_world = self.clone(); hypothetical_world.clear(*target); - let result = astar(target, &Point(0, 0), Some(&hypothetical_world), Some(b)); + let result = astar(target, &b.center, Some(&hypothetical_world), Some(b)); b.is_in_bounds(target) && result.is_ok() } @@ -82,10 +82,12 @@ impl World { .collect(); if return_diggables { - ans.extend(self.get_diggable(b, pos)); + let digs = self.get_diggable(b, pos); + ans.extend(digs); } ans + } } @@ -131,6 +133,7 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) { BoardCommand::SpawnFood(pos) => { let food = Food::new(pos.0, pos.1); e.add_entity(&food); + w.food.insert(pos); } BoardCommand::Noop => {} } diff --git a/src/main.rs b/src/main.rs index 4b511cb..5f8f67e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,16 +17,24 @@ mod lib { use lib::point::Point; use lib::screen::init_screen; use lib::world::{World, simulate, render}; -use lib::entity::{Entities, Queen}; +use lib::entity::{Entities, Ant, FoodGenerator}; fn main() { let mut board = init_screen(); let mut world = World::new(); let mut entities = Entities::new(); - let q = Queen::new(board.center.0,board.center.1); - entities.add_entity(&q); + //let q = Queen::new(board.center.0,board.center.1); + //entities.add_entity(&q); + + let fg = FoodGenerator::new(); + entities.add_entity(&fg); + + let mut a = Ant::new(board.center.0, board.center.1); + a.plan.push(lib::ai::AIGoal::Seek); + entities.add_entity(&a); + world.create_chamber(Point(board.center.0, board.center.1), 3); loop { From 823b5d39b8a2710ab476148e40e07ae6e2bd0b7e Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Sun, 7 Jan 2024 20:19:52 -0700 Subject: [PATCH 6/9] semi functioning pheremone sim --- src/lib/ai.rs | 117 +++++++++++++++++++++++----------- src/lib/entity.rs | 69 +++++++++++++++++--- src/lib/point.rs | 147 +++++++++---------------------------------- src/lib/screen.rs | 3 + src/lib/world.rs | 128 +++++++++++++++++++------------------ src/main.rs | 26 ++++---- tests/ai_test.rs | 8 +-- tests/entity_test.rs | 4 +- 8 files changed, 256 insertions(+), 246 deletions(-) diff --git a/src/lib/ai.rs b/src/lib/ai.rs index 23381fe..c4a5c0f 100644 --- a/src/lib/ai.rs +++ b/src/lib/ai.rs @@ -1,67 +1,108 @@ use crate::lib::entity::{Ant, Egg, Queen}; -use crate::lib::point::{astar, Point}; +use crate::lib::point::{Direction, Point}; use crate::lib::screen::{BoardCommand, Screen}; -use crate::lib::world::World; +use crate::lib::world::{Pheremone, World}; use rand::prelude::SliceRandom; use rand::thread_rng; +use std::iter::zip; pub trait AI { fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand; - fn plan(&mut self, w: &World) {} + fn plan(&mut self, b: &Screen, w: &mut World) -> BoardCommand { + BoardCommand::Noop + } } #[derive(Clone, Debug)] pub enum AIGoal { Seek, - //Pickup(Point), - //Drop(), - Idle, + Return, } impl AI for Ant { - fn plan(&mut self, w: &World) { - // check last part of plan - if let Some(goal) = self.plan.last() { - match goal { - AIGoal::Seek => { - // if we reach food, we change state - if w.food.contains(&self.pos) { - self.plan.pop(); + fn plan(&mut self, b: &Screen, w: &mut World) -> BoardCommand { + match self.goal { + AIGoal::Seek => { + // if we reach food, we change state + if w.food.contains(&self.pos) { + for p in &self.history { + w.drop_pheremone(&p, &self.goal); } + self.history.clear(); + self.cw(); + self.cw(); + self.goal = AIGoal::Return; + } + BoardCommand::Noop + } + AIGoal::Return => { + if self.pos == b.center { + for p in &self.history { + w.drop_pheremone(&p, &self.goal); + } + self.history.clear(); + self.cw(); + self.cw(); + self.goal = AIGoal::Seek; + return BoardCommand::SpawnAnt + } else { + BoardCommand::Noop } - 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, + + let valid = vec![ + (self.dir, self.dir.relative_point(&self.pos)), + (self.dir.ccw(), self.dir.ccw().relative_point(&self.pos)), + (self.dir.cw(), self.dir.cw().relative_point(&self.pos)), + ]; + + let ph: Vec = valid + .iter() + .map(|(_, pnt)| { + let op = w.cleared.get(pnt); + match op { + Some(ph) => ph.clone(), + None => { + w.cleared.insert(pnt.clone(), Pheremone::new()); + w.cleared.get(pnt).unwrap().clone() + } + } + }) + .collect(); + + let ph_fn = match self.goal { + AIGoal::Seek => |ph: &Pheremone| ph.food, + AIGoal::Return => |ph: &Pheremone| ph.home, }; - let valid = w.get_valid_movements(&self.pos, b, true); - let mut rng = thread_rng(); - let choice = valid.choose(&mut rng).cloned(); + let r: f32 = rand::random(); - if !choice.is_none() { - let pos = choice.unwrap(); - if w.cleared.contains(&pos) { - self.pos = pos; - } else { - - match goal { - AIGoal::Seek => { - if w.is_safe_to_dig(&pos, &b) { - w.clear(pos); - } - }, - AIGoal::Idle => {} + let mut dir = &valid[0].0; + if r < 0.2 || ph.len() == 0 { + let mut rng = thread_rng(); + let choice = valid.choose(&mut rng).unwrap(); + dir = &choice.0; + } 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(w,b); + } else if dir == &self.dir.cw() { + self.cw(); + } else if dir == &self.dir.ccw() { + self.ccw(); + } BoardCommand::Noop } } @@ -79,14 +120,14 @@ 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, false); + let valid: Vec = self.pos.get_neighbors(); let mut rng = thread_rng(); let choice = valid.choose(&mut rng).cloned(); if !choice.is_none() { let pos = choice.unwrap(); - if w.cleared.contains(&pos) { + if w.is_cleared(&pos) { // choose between laying an egg and moving if self.egg_count < 3 { self.egg_count += 1; diff --git a/src/lib/entity.rs b/src/lib/entity.rs index c22168a..e85199c 100644 --- a/src/lib/entity.rs +++ b/src/lib/entity.rs @@ -1,10 +1,10 @@ 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::world::World; use rand::Rng; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use ncurses::*; @@ -40,7 +40,9 @@ impl_downcast!(Renderable); pub struct Ant { pub pos: Point, pub id: u32, - pub plan: Vec, + pub goal: AIGoal, + pub dir: Direction, + pub history: HashSet, } impl Ant { @@ -48,10 +50,36 @@ impl Ant { Ant { pos: Point(x, y), id: 0, - plan: vec![], + goal: AIGoal::Seek, + dir: Direction::Up, + history: HashSet::new(), } } + + pub fn forward(&mut self, w: &mut World, b: &Screen) { + let target = self.dir.relative_point(&self.pos); + if b.is_in_bounds(&target) { + if w.is_cleared(&self.pos) { + self.history.insert(self.pos); + self.pos = target; + } else { + w.clear(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(); + } } + impl_entity!(Ant); impl_entity!(Food); impl_entity!(FoodGenerator); @@ -60,14 +88,27 @@ impl_entity!(Queen); impl Renderable for Ant { fn representation(&self) -> &str { - "o" + match self.dir { + Direction::Up => "^", + Direction::Down => "v", + Direction::Right => ">", + Direction::Left => "<", + } } fn before_render(&self) { attron(A_BOLD()); + match self.goal { + AIGoal::Return => attron(A_UNDERLINE()), + AIGoal::Seek => 0, + }; } fn after_render(&self) { attroff(A_BOLD()); + match self.goal { + AIGoal::Return => attroff(A_UNDERLINE()), + AIGoal::Seek => 0, + }; } } @@ -188,8 +229,11 @@ 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? + for n in self.pos.get_neighbors() { + w.drop_pheremone(&n, &AIGoal::Seek); + } + w.drop_pheremone(&self.pos, &AIGoal::Seek); + BoardCommand::Noop } } @@ -206,12 +250,17 @@ pub struct FoodGenerator { timer: u32, pos: Point, id: u32, - counter: u32 + counter: u32, } impl FoodGenerator { pub fn new() -> FoodGenerator { - FoodGenerator { timer: 0, id: 0, pos: Point(0,0), counter: 0 } + FoodGenerator { + timer: 0, + id: 0, + pos: Point(0, 0), + counter: 0, + } } } @@ -226,7 +275,7 @@ impl AI for FoodGenerator { 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)); } diff --git a/src/lib/point.rs b/src/lib/point.rs index 46aab2d..6607232 100644 --- a/src/lib/point.rs +++ b/src/lib/point.rs @@ -14,29 +14,10 @@ impl Point { Point(self.0 - 1, self.1) } - pub fn is_below(&self, other: Point) -> bool { - other.down() == *self - } - pub fn right(&self) -> Point { Point(self.0 + 1, self.1) } - pub fn uldiag(&self) -> Point { - Point(self.0 - 1, self.1 - 1) - } - - pub fn bldiag(&self) -> Point { - Point(self.0 + 1, self.1 - 1) - } - - pub fn brdiag(&self) -> Point { - Point(self.0 + 1, self.1 + 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) @@ -47,111 +28,45 @@ impl Point { } } -fn manhattan(c1: &Point, c2: &Point) -> i32 { - return (c2.0 - c1.0).abs() + (c2.1 - c1.1).abs(); -} - -fn weigh_point(w: &World, p: &Point) -> i32 { - if w.is_cleared(p) { - 3 - } else { - 15 - } +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum Direction { + Up = 0, + Right = 1, + Down = 2, + Left = 3, } -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(); - - let mut gscore: HashMap = HashMap::new(); - gscore.insert(*start, 0); - - let mut fscore: HashMap = HashMap::new(); - fscore.insert(*start, manhattan(&start, &goal)); - - let mut answer = Vec::new(); - - let mut current: Point = *start; - while !open_set.is_empty() { - let mut min_score = i32::MAX; - for c in open_set.iter() { - let val = fscore[c]; - current = if val < min_score { c.clone() } else { current }; - min_score = if val < min_score { val } else { min_score }; - } - - if current == *goal { - answer.push(current.clone()); - while came_from.contains_key(¤t) { - current = came_from[¤t].clone(); - if current != *start { - answer.push(current.clone()); - } - } - return Ok(answer); +impl Direction { + pub fn from_u8(v: u8) -> Direction { + match v { + 0 => Direction::Up, + 1 => Direction::Right, + 2 => Direction::Down, + 3 => Direction::Left, + _ => panic!("Shouldn't happen") } + } - open_set.remove(¤t); - - let current_gscore = gscore[¤t]; - - let all_neighbors: Vec = match w { - Some(w) => current - .get_neighbors() - .iter() - .filter(|p| w.is_valid_movement(¤t, 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] - } else { - i32::MAX - }; - - let weight = match w { - Some(w) => weigh_point(&w, neighbor), - None => 1, - }; - - let score = current_gscore + weight; + pub fn cw(&self) -> Direction { + let val = *self as u8; + Direction::from_u8((val + 1) % 4) + } - 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()); - } + pub fn ccw(&self) -> Direction { + let val = *self as u8; + if val == 0 { + Direction::Left + } else { + Direction::from_u8(val - 1) } } - Err(PathNotFoundError) -} - -#[test] -fn test_astar() { - let start = Point(0, 0); - - let goal = Point(10, 10); - 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!") + pub fn relative_point(&self, p: &Point) -> Point { + match self { + Direction::Up => p.up(), + Direction::Left => p.left(), + Direction::Right => p.right(), + Direction::Down => p.down(), } } - - } diff --git a/src/lib/screen.rs b/src/lib/screen.rs index ac6c73d..ea2e630 100644 --- a/src/lib/screen.rs +++ b/src/lib/screen.rs @@ -11,6 +11,8 @@ use ncurses::*; pub fn init_screen() -> Screen { initscr(); + start_color(); + init_pair(0, 0, 1); /* Invisible cursor. */ curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE); @@ -81,5 +83,6 @@ pub enum BoardCommand { LayEgg(Point, u32), SpawnFood(Point), Hatch(u32, u32), + SpawnAnt, Noop, } diff --git a/src/lib/world.rs b/src/lib/world.rs index 222bcaf..ba88115 100644 --- a/src/lib/world.rs +++ b/src/lib/world.rs @@ -1,24 +1,46 @@ +use crate::lib::ai::AIGoal; use crate::lib::entity::{Ant, Egg, Entities, Food, Queen}; -use crate::lib::point::{astar, Point}; +use crate::lib::point::Point; use crate::lib::screen::{BoardCommand, Screen}; -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; #[derive(Clone)] pub struct World { - pub cleared: HashSet, + pub cleared: HashMap, pub food: HashSet, } +#[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 { pub fn new() -> World { World { - cleared: HashSet::new(), + cleared: HashMap::new(), food: HashSet::new(), } } pub fn clear(&mut self, pos: Point) { - self.cleared.insert(pos); + self.cleared.insert(pos, Pheremone::new()); } pub fn create_chamber(&mut self, center: Point, radius: i32) { @@ -32,67 +54,30 @@ impl World { } } - pub fn is_valid_movement(&self, current: &Point, target: &Point, b: &Screen) -> bool { - // should allow down movements always - - let safe = target.is_below(*current) - || (!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())); - - // 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) + self.cleared.contains_key(pos) } - pub fn is_safe_to_dig(&self, target: &Point, b: &Screen) -> bool { - let mut hypothetical_world = self.clone(); - hypothetical_world.clear(*target); - let result = astar(target, &b.center, Some(&hypothetical_world), Some(b)); - b.is_in_bounds(target) && result.is_ok() - } + pub fn drop_pheremone(&mut self, pos: &Point, state: &AIGoal) { + let op = self.cleared.get_mut(&pos); - pub fn get_diggable(&self, b: &Screen, pos: &Point) -> Vec { - let moves = b.get_valid_movements(pos); - moves - .iter() - .filter(|p| !self.is_cleared(p)) - .map(|p| p.clone()) - .collect() - } + let ph = match op { + Some(p) => p, + None => { + self.cleared.insert(*pos, Pheremone::new()); + self.cleared.get_mut(pos).unwrap() + } + }; - 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(pos, p, b)) - .map(|p| p.clone()) - .collect(); - - if return_diggables { - let digs = self.get_diggable(b, pos); - ans.extend(digs); + match state { + AIGoal::Seek => ph.home += 1, + AIGoal::Return => ph.food += 1, } - - ans - } } pub fn render(e: &Entities, w: &World, b: &Screen) { - for c in w.cleared.iter() { + for c in w.cleared.keys() { b.render(c, "x"); } @@ -101,18 +86,19 @@ pub fn render(e: &Entities, w: &World, b: &Screen) { } } -pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) { - let cmds: Vec = e - .data - .values_mut() - .map(|a| { - a.plan(w); - a.step(b, w) - }) - .collect(); +pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen, step: u32) { + let plan_cmds: Vec = e.data.values_mut().map(|a| a.plan(b, w)).collect(); + + let mut cmds: Vec = e.data.values_mut().map(|a| a.step(b, w)).collect(); + + cmds.extend(plan_cmds); for cmd in cmds { match cmd { + BoardCommand::SpawnAnt => { + let ant = Ant::new(b.center.0, b.center.1); + e.add_entity(&ant); + } BoardCommand::Dig(pos) => { w.clear(pos); } @@ -138,4 +124,16 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) { BoardCommand::Noop => {} } } + + for n in b.center.get_neighbors() { + w.drop_pheremone(&n, &AIGoal::Return); + } + w.drop_pheremone(&b.center, &AIGoal::Return); + + // decay all pheremones by some amount + if step % 60 == 0 { + for ph in w.cleared.values_mut() { + ph.decay(); + } + } } diff --git a/src/main.rs b/src/main.rs index 5f8f67e..f6935c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,42 +7,46 @@ use std::thread::sleep; use std::time; mod lib { + pub mod ai; + pub mod entity; pub mod point; pub mod screen; pub mod world; - pub mod entity; - pub mod ai; } +use lib::entity::{Ant, Entities, FoodGenerator}; use lib::point::Point; use lib::screen::init_screen; -use lib::world::{World, simulate, render}; -use lib::entity::{Entities, Ant, FoodGenerator}; +use lib::world::{render, simulate, World}; fn main() { - let mut board = init_screen(); + let mut board = init_screen(); let mut world = World::new(); let mut entities = Entities::new(); //let q = Queen::new(board.center.0,board.center.1); //entities.add_entity(&q); - + let fg = FoodGenerator::new(); entities.add_entity(&fg); - let mut a = Ant::new(board.center.0, board.center.1); - a.plan.push(lib::ai::AIGoal::Seek); - entities.add_entity(&a); - + for _ in 0..5 { + let mut a = Ant::new(board.center.0, board.center.1); + a.goal = lib::ai::AIGoal::Seek; + entities.add_entity(&a); + } + world.create_chamber(Point(board.center.0, board.center.1), 3); + let mut t = 0; loop { // 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); sleep(time::Duration::from_millis(100)); refresh(); + t += 1; } endwin(); } diff --git a/tests/ai_test.rs b/tests/ai_test.rs index ea10e44..00ea159 100644 --- a/tests/ai_test.rs +++ b/tests/ai_test.rs @@ -23,15 +23,15 @@ fn test_reach_astar() { let a = entities.data.get_mut(&id).unwrap(); let ant: &mut Ant = a.downcast_mut::().unwrap(); - ant.plan.push(AIGoal::Reach(Point(0, 0))); + /*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))); + ant.plan.push(AIGoal::Reach(Point(20, 20)));*/ // 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 - simulate(&mut entities, &mut world, &mut board); + simulate(&mut entities, &mut world, &mut board, t); render(&entities, &world, &board); sleep(time::Duration::from_millis(100)); refresh(); diff --git a/tests/entity_test.rs b/tests/entity_test.rs index 6053ed2..825511e 100644 --- a/tests/entity_test.rs +++ b/tests/entity_test.rs @@ -15,9 +15,9 @@ fn test_foodgen() { let fg = FoodGenerator::new(); 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 - simulate(&mut entities, &mut world, &mut board); + simulate(&mut entities, &mut world, &mut board, t); render(&entities, &world, &board); sleep(time::Duration::from_millis(100)); refresh(); From 0b13c7bfa396f8e9d08954f73ad86081bfc05cb9 Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Mon, 8 Jan 2024 21:22:59 -0700 Subject: [PATCH 7/9] cleared up gfx + ph storage --- src/lib/ai.rs | 41 ++++++++++------------------- src/lib/entity.rs | 15 +++++------ src/lib/point.rs | 4 --- src/lib/screen.rs | 33 +++-------------------- src/lib/world.rs | 62 +++++++++++++++----------------------------- src/main.rs | 4 +-- tests/ai_test.rs | 3 +-- tests/entity_test.rs | 3 ++- 8 files changed, 49 insertions(+), 116 deletions(-) diff --git a/src/lib/ai.rs b/src/lib/ai.rs index c4a5c0f..a00a393 100644 --- a/src/lib/ai.rs +++ b/src/lib/ai.rs @@ -32,7 +32,7 @@ impl AI for Ant { self.cw(); self.cw(); self.goal = AIGoal::Return; - } + } BoardCommand::Noop } AIGoal::Return => { @@ -44,7 +44,7 @@ impl AI for Ant { self.cw(); self.cw(); self.goal = AIGoal::Seek; - return BoardCommand::SpawnAnt + return BoardCommand::SpawnAnt; } else { BoardCommand::Noop } @@ -53,26 +53,13 @@ impl AI for Ant { } fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { - let valid = vec![ (self.dir, self.dir.relative_point(&self.pos)), (self.dir.ccw(), self.dir.ccw().relative_point(&self.pos)), (self.dir.cw(), self.dir.cw().relative_point(&self.pos)), ]; - let ph: Vec = valid - .iter() - .map(|(_, pnt)| { - let op = w.cleared.get(pnt); - match op { - Some(ph) => ph.clone(), - None => { - w.cleared.insert(pnt.clone(), Pheremone::new()); - w.cleared.get(pnt).unwrap().clone() - } - } - }) - .collect(); + let ph: Vec = valid.iter().map(|(_, pnt)| w.get_pheremone(pnt).clone()).collect(); let ph_fn = match self.goal { AIGoal::Seek => |ph: &Pheremone| ph.food, @@ -80,7 +67,7 @@ impl AI for Ant { }; let r: f32 = rand::random(); - + let mut dir = &valid[0].0; if r < 0.2 || ph.len() == 0 { let mut rng = thread_rng(); @@ -95,14 +82,14 @@ impl AI for Ant { } } } - + if dir == &self.dir { - self.forward(w,b); + self.forward(b); } else if dir == &self.dir.cw() { self.cw(); } else if dir == &self.dir.ccw() { self.ccw(); - } + } BoardCommand::Noop } } @@ -127,14 +114,12 @@ impl AI for Queen { if !choice.is_none() { let pos = choice.unwrap(); - if w.is_cleared(&pos) { - // choose between laying an egg and moving - if self.egg_count < 3 { - self.egg_count += 1; - return BoardCommand::LayEgg(pos, self.id); - } else { - self.pos = pos; - } + // choose between laying an egg and moving + if self.egg_count < 3 { + self.egg_count += 1; + return BoardCommand::LayEgg(pos, self.id); + } else { + self.pos = pos; } } BoardCommand::Noop diff --git a/src/lib/entity.rs b/src/lib/entity.rs index e85199c..7733123 100644 --- a/src/lib/entity.rs +++ b/src/lib/entity.rs @@ -16,6 +16,7 @@ pub trait Entity: AI + Downcast { fn set_id(&mut self, id: u32); } + macro_rules! impl_entity { ($t: ident) => { impl Entity for $t { @@ -56,21 +57,17 @@ impl Ant { } } - pub fn forward(&mut self, w: &mut World, b: &Screen) { + pub fn forward(&mut self, s: &Screen) { let target = self.dir.relative_point(&self.pos); - if b.is_in_bounds(&target) { - if w.is_cleared(&self.pos) { - self.history.insert(self.pos); - self.pos = target; - } else { - w.clear(target); - } + 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(); } diff --git a/src/lib/point.rs b/src/lib/point.rs index 6607232..d0383b3 100644 --- a/src/lib/point.rs +++ b/src/lib/point.rs @@ -1,7 +1,3 @@ -use crate::lib::screen::Screen; -use crate::lib::world::World; -use std::collections::{HashMap, HashSet}; - #[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)] pub struct Point(pub i32, pub i32); diff --git a/src/lib/screen.rs b/src/lib/screen.rs index ea2e630..96b5309 100644 --- a/src/lib/screen.rs +++ b/src/lib/screen.rs @@ -1,13 +1,12 @@ use crate::lib::point::Point; +use ncurses::*; pub struct Screen { pub center: Point, - max_x: i32, - max_y: i32, + pub max_x: i32, + pub max_y: i32, } -use ncurses::*; - pub fn init_screen() -> Screen { initscr(); @@ -34,7 +33,7 @@ impl Screen { } pub fn is_in_bounds(&self, pos: &Point) -> bool { - (pos.0 >= 0 && pos.0 < self.max_x) && (pos.1 >= 0 && 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 { @@ -55,31 +54,7 @@ impl Screen { } } -/*#[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 { - Dig(Point), LayEgg(Point, u32), SpawnFood(Point), Hatch(u32, u32), diff --git a/src/lib/world.rs b/src/lib/world.rs index ba88115..7ef04c5 100644 --- a/src/lib/world.rs +++ b/src/lib/world.rs @@ -2,12 +2,16 @@ use crate::lib::ai::AIGoal; use crate::lib::entity::{Ant, Egg, Entities, Food, Queen}; use crate::lib::point::Point; use crate::lib::screen::{BoardCommand, Screen}; -use std::collections::{HashMap, HashSet}; +use std::collections::HashSet; + +use ncurses::*; #[derive(Clone)] pub struct World { - pub cleared: HashMap, + pub pheremones: Vec, pub food: HashSet, + pub home: HashSet, + width: usize, } #[derive(Clone, PartialEq, PartialOrd, Debug)] @@ -32,42 +36,29 @@ impl Pheremone { } 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 = vec![Pheremone::new(); (x+1) as usize * (y+1) as usize]; + World { - cleared: HashMap::new(), + pheremones: ph, 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) { - self.cleared.insert(pos, Pheremone::new()); - } - - pub fn create_chamber(&mut self, center: Point, radius: i32) { - let cx = center.0; - let cy = center.1; - - for i in cx - radius..cx + radius { - for j in cy - radius..cy + radius { - self.clear(Point(i, j)); - } - } + pub fn get_mut_pheremone(&mut self, pos: &Point) -> &mut Pheremone { + &mut self.pheremones[((pos.0 as usize) * self.width) + (pos.1 as usize)] } - pub fn is_cleared(&self, pos: &Point) -> bool { - self.cleared.contains_key(pos) + pub fn get_pheremone(&self, pos: &Point) -> &Pheremone { + &self.pheremones[((pos.0 as usize) * self.width) + (pos.1 as usize)] } pub fn drop_pheremone(&mut self, pos: &Point, state: &AIGoal) { - let op = self.cleared.get_mut(&pos); - - let ph = match op { - Some(p) => p, - None => { - self.cleared.insert(*pos, Pheremone::new()); - self.cleared.get_mut(pos).unwrap() - } - }; + let ph = self.get_mut_pheremone(pos); match state { AIGoal::Seek => ph.home += 1, @@ -77,10 +68,7 @@ impl World { } pub fn render(e: &Entities, w: &World, b: &Screen) { - for c in w.cleared.keys() { - b.render(c, "x"); - } - + erase(); for a in e.data.values() { a.render(b); } @@ -99,9 +87,6 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen, step: u32) { let ant = Ant::new(b.center.0, b.center.1); e.add_entity(&ant); } - BoardCommand::Dig(pos) => { - w.clear(pos); - } BoardCommand::LayEgg(pos, id) => { let egg = Egg::new(pos.0, pos.1, id); e.add_entity(&egg); @@ -125,14 +110,9 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen, step: u32) { } } - for n in b.center.get_neighbors() { - w.drop_pheremone(&n, &AIGoal::Return); - } - w.drop_pheremone(&b.center, &AIGoal::Return); - // decay all pheremones by some amount if step % 60 == 0 { - for ph in w.cleared.values_mut() { + for ph in w.pheremones.iter_mut() { ph.decay(); } } diff --git a/src/main.rs b/src/main.rs index f6935c4..8c8dc34 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,7 +21,7 @@ use lib::world::{render, simulate, World}; fn main() { 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(); @@ -37,7 +37,7 @@ fn main() { entities.add_entity(&a); } - world.create_chamber(Point(board.center.0, board.center.1), 3); + //world.create_chamber(Point(board.center.0, board.center.1), 3); let mut t = 0; loop { diff --git a/tests/ai_test.rs b/tests/ai_test.rs index 00ea159..900c2ed 100644 --- a/tests/ai_test.rs +++ b/tests/ai_test.rs @@ -13,9 +13,8 @@ use std::time; #[test] fn test_reach_astar() { 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)); - world.clear(Point(0, 0)); let mut entities = Entities::new(); let a = Ant::new(0, 0); diff --git a/tests/entity_test.rs b/tests/entity_test.rs index 825511e..a78207d 100644 --- a/tests/entity_test.rs +++ b/tests/entity_test.rs @@ -1,6 +1,7 @@ use antf::lib::screen::init_screen; use antf::lib::world::{World, simulate, render}; use antf::lib::entity::{Entities, FoodGenerator}; +use antf::lib::point::Point; use ncurses::*; use std::thread::sleep; @@ -9,7 +10,7 @@ use std::time; #[test] fn test_foodgen() { 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 fg = FoodGenerator::new(); From 9021e9e3864943013fd7a7bc8ee6b63e13a15dab Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Mon, 8 Jan 2024 21:29:00 -0700 Subject: [PATCH 8/9] multiple home spots plus render --- src/lib/ai.rs | 7 +++++-- src/lib/world.rs | 6 +++++- src/main.rs | 11 ++++++----- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/lib/ai.rs b/src/lib/ai.rs index a00a393..d880016 100644 --- a/src/lib/ai.rs +++ b/src/lib/ai.rs @@ -36,7 +36,7 @@ impl AI for Ant { BoardCommand::Noop } AIGoal::Return => { - if self.pos == b.center { + if w.home.contains(&self.pos) { for p in &self.history { w.drop_pheremone(&p, &self.goal); } @@ -59,7 +59,10 @@ impl AI for Ant { (self.dir.cw(), self.dir.cw().relative_point(&self.pos)), ]; - let ph: Vec = valid.iter().map(|(_, pnt)| w.get_pheremone(pnt).clone()).collect(); + let ph: Vec = valid + .iter() + .map(|(_, pnt)| w.get_pheremone(pnt).clone()) + .collect(); let ph_fn = match self.goal { AIGoal::Seek => |ph: &Pheremone| ph.food, diff --git a/src/lib/world.rs b/src/lib/world.rs index 7ef04c5..5e776d5 100644 --- a/src/lib/world.rs +++ b/src/lib/world.rs @@ -69,6 +69,11 @@ impl World { pub fn render(e: &Entities, w: &World, b: &Screen) { erase(); + + for h in w.home.iter() { + b.render(h, "x"); + } + for a in e.data.values() { a.render(b); } @@ -76,7 +81,6 @@ pub fn render(e: &Entities, w: &World, b: &Screen) { pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen, step: u32) { let plan_cmds: Vec = e.data.values_mut().map(|a| a.plan(b, w)).collect(); - let mut cmds: Vec = e.data.values_mut().map(|a| a.step(b, w)).collect(); cmds.extend(plan_cmds); diff --git a/src/main.rs b/src/main.rs index 8c8dc34..a867dbf 100644 --- a/src/main.rs +++ b/src/main.rs @@ -20,8 +20,9 @@ use lib::screen::init_screen; use lib::world::{render, simulate, World}; fn main() { - let mut board = init_screen(); - let mut world = World::new(&Point(board.max_x, board.max_y)); + let mut screen = init_screen(); + let mut world = World::new(&Point(screen.max_x, screen.max_y)); + world.home.insert(screen.center); let mut entities = Entities::new(); @@ -32,7 +33,7 @@ fn main() { entities.add_entity(&fg); for _ in 0..5 { - let mut a = Ant::new(board.center.0, board.center.1); + let mut a = Ant::new(screen.center.0, screen.center.1); a.goal = lib::ai::AIGoal::Seek; entities.add_entity(&a); } @@ -42,8 +43,8 @@ fn main() { let mut t = 0; loop { // TODO: add way to break out of the loop by hitting a random key - simulate(&mut entities, &mut world, &mut board, t); - render(&entities, &world, &board); + simulate(&mut entities, &mut world, &mut screen, t); + render(&entities, &world, &screen); sleep(time::Duration::from_millis(100)); refresh(); t += 1; From b660cdb2bf372ddb53f1bba795aa79c84e74c5a5 Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Mon, 8 Jan 2024 21:50:21 -0700 Subject: [PATCH 9/9] code cleanup, fixed bug with food creating home ph --- src/lib/ai.rs | 51 +++++++++++++++++++++++++++++++++++++---------- src/lib/entity.rs | 46 +++++++++--------------------------------- src/lib/screen.rs | 9 --------- src/lib/world.rs | 4 ++-- src/main.rs | 5 ----- 5 files changed, 51 insertions(+), 64 deletions(-) diff --git a/src/lib/ai.rs b/src/lib/ai.rs index d880016..f13791e 100644 --- a/src/lib/ai.rs +++ b/src/lib/ai.rs @@ -1,13 +1,14 @@ -use crate::lib::entity::{Ant, Egg, Queen}; -use crate::lib::point::{Direction, Point}; +use crate::lib::entity::{Ant, Egg, Food, FoodGenerator, Queen}; +use crate::lib::point::Point; use crate::lib::screen::{BoardCommand, Screen}; use crate::lib::world::{Pheremone, World}; +use rand::Rng; use rand::prelude::SliceRandom; use rand::thread_rng; use std::iter::zip; 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, b: &Screen, w: &mut World) -> BoardCommand { BoardCommand::Noop } @@ -29,8 +30,7 @@ impl AI for Ant { w.drop_pheremone(&p, &self.goal); } self.history.clear(); - self.cw(); - self.cw(); + self.about_face(); self.goal = AIGoal::Return; } BoardCommand::Noop @@ -41,8 +41,7 @@ impl AI for Ant { w.drop_pheremone(&p, &self.goal); } self.history.clear(); - self.cw(); - self.cw(); + self.about_face(); self.goal = AIGoal::Seek; return BoardCommand::SpawnAnt; } else { @@ -52,7 +51,7 @@ impl AI for Ant { } } - 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![ (self.dir, self.dir.relative_point(&self.pos)), (self.dir.ccw(), self.dir.ccw().relative_point(&self.pos)), @@ -72,7 +71,7 @@ impl AI for Ant { let r: f32 = rand::random(); let mut dir = &valid[0].0; - if r < 0.2 || ph.len() == 0 { + if r < 0.1 || ph.len() == 0 { let mut rng = thread_rng(); let choice = valid.choose(&mut rng).unwrap(); dir = &choice.0; @@ -97,8 +96,27 @@ impl AI for Ant { } } +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 + } +} + 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 { self.counter -= 1; return BoardCommand::Noop; @@ -109,7 +127,7 @@ impl AI for Egg { } 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 = self.pos.get_neighbors(); let mut rng = thread_rng(); @@ -128,3 +146,14 @@ impl AI for Queen { 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 + } +} diff --git a/src/lib/entity.rs b/src/lib/entity.rs index 7733123..76b81f1 100644 --- a/src/lib/entity.rs +++ b/src/lib/entity.rs @@ -1,8 +1,6 @@ use crate::lib::ai::{AIGoal, AI}; use crate::lib::point::{Direction, Point}; -use crate::lib::screen::{BoardCommand, Screen}; -use crate::lib::world::World; -use rand::Rng; +use crate::lib::screen::Screen; use std::collections::{HashMap, HashSet}; @@ -75,6 +73,11 @@ impl Ant { pub fn cw(&mut self) { self.dir = self.dir.cw(); } + + pub fn about_face(&mut self) { + self.cw(); + self.cw(); + } } impl_entity!(Ant); @@ -211,7 +214,7 @@ impl Entities { #[derive(Clone)] pub struct Food { - pos: Point, + pub pos: Point, id: u32, } @@ -224,17 +227,6 @@ impl Food { } } -impl AI for Food { - fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { - for n in self.pos.get_neighbors() { - w.drop_pheremone(&n, &AIGoal::Seek); - } - w.drop_pheremone(&self.pos, &AIGoal::Seek); - - BoardCommand::Noop - } -} - impl Renderable for Food { fn representation(&self) -> &str { "f" @@ -244,10 +236,10 @@ impl Renderable for Food { // no position, does not get rendered yet acts per turn #[derive(Clone)] pub struct FoodGenerator { - timer: u32, + pub timer: u32, pos: Point, id: u32, - counter: u32, + pub counter: u32, } impl FoodGenerator { @@ -261,26 +253,6 @@ impl FoodGenerator { } } -impl AI for FoodGenerator { - fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand { - // eventually might want to use poisson disk distrib instead - if self.timer % 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)); - } - self.timer += 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 96b5309..8a2c57d 100644 --- a/src/lib/screen.rs +++ b/src/lib/screen.rs @@ -36,15 +36,6 @@ impl Screen { (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 { - 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) { mvprintw(p.1, p.0, char); } diff --git a/src/lib/world.rs b/src/lib/world.rs index 5e776d5..2aba20b 100644 --- a/src/lib/world.rs +++ b/src/lib/world.rs @@ -81,7 +81,7 @@ pub fn render(e: &Entities, w: &World, b: &Screen) { pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen, step: u32) { let plan_cmds: Vec = e.data.values_mut().map(|a| a.plan(b, w)).collect(); - let mut cmds: Vec = e.data.values_mut().map(|a| a.step(b, w)).collect(); + let mut cmds: Vec = e.data.values_mut().map(|a| a.step(b, w, step)).collect(); cmds.extend(plan_cmds); @@ -115,7 +115,7 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen, step: u32) { } // decay all pheremones by some amount - if step % 60 == 0 { + if step % 600 == 0 { for ph in w.pheremones.iter_mut() { ph.decay(); } diff --git a/src/main.rs b/src/main.rs index a867dbf..f52aa97 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,9 +26,6 @@ fn main() { let mut entities = Entities::new(); - //let q = Queen::new(board.center.0,board.center.1); - //entities.add_entity(&q); - let fg = FoodGenerator::new(); entities.add_entity(&fg); @@ -38,8 +35,6 @@ fn main() { entities.add_entity(&a); } - //world.create_chamber(Point(board.center.0, board.center.1), 3); - let mut t = 0; loop { // TODO: add way to break out of the loop by hitting a random key