|
|
|
@ -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<Point>, |
|
|
|
|
pub cleared: HashMap<Point, Pheremone>, |
|
|
|
|
pub food: HashSet<Point>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
#[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<Point> { |
|
|
|
|
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<Point> { |
|
|
|
|
let moves = b.get_valid_movements(pos); |
|
|
|
|
let mut ans: Vec<Point> = 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<BoardCommand> = 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<BoardCommand> = e.data.values_mut().map(|a| a.plan(b, w)).collect(); |
|
|
|
|
|
|
|
|
|
let mut cmds: Vec<BoardCommand> = 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(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|