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