sideways #1
+107
-53
@@ -1,75 +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;
|
||||||
}
|
}
|
||||||
AIGoal::Idle => {}
|
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();
|
||||||
// return the next move for this ant
|
self.goal = AIGoal::Seek;
|
||||||
fn step(&mut self, b: &Screen, w: &mut World) -> BoardCommand {
|
return BoardCommand::SpawnAnt;
|
||||||
|
|
||||||
let goal = match self.plan.last() {
|
|
||||||
Some(g) => g,
|
|
||||||
None => &AIGoal::Idle,
|
|
||||||
};
|
|
||||||
|
|
||||||
let choice = match goal {
|
|
||||||
AIGoal::Idle => {
|
|
||||||
let valid = w.get_valid_movements(&self.pos, b);
|
|
||||||
let mut rng = thread_rng();
|
|
||||||
valid.choose(&mut rng).cloned()
|
|
||||||
}
|
|
||||||
AIGoal::Reach(target) => {
|
|
||||||
let mut movements = astar(&self.pos, &target);
|
|
||||||
movements.pop()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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 {
|
} else {
|
||||||
w.clear(pos);
|
BoardCommand::Noop
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)),
|
||||||
|
(self.dir.cw(), self.dir.cw().relative_point(&self.pos)),
|
||||||
|
];
|
||||||
|
|
||||||
|
let ph: Vec<Pheremone> = valid
|
||||||
|
.iter()
|
||||||
|
.map(|(_, pnt)| w.get_pheremone(pnt).clone())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let ph_fn = match self.goal {
|
||||||
|
AIGoal::Seek => |ph: &Pheremone| ph.food,
|
||||||
|
AIGoal::Return => |ph: &Pheremone| ph.home,
|
||||||
|
};
|
||||||
|
|
||||||
|
let r: f32 = rand::random();
|
||||||
|
|
||||||
|
let mut dir = &valid[0].0;
|
||||||
|
if r < 0.1 || 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(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;
|
||||||
@@ -80,26 +127,33 @@ 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);
|
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;
|
||||||
return BoardCommand::LayEgg(pos, self.id);
|
return BoardCommand::LayEgg(pos, self.id);
|
||||||
} else {
|
} else {
|
||||||
let old_pos = self.pos;
|
|
||||||
self.pos = pos;
|
self.pos = pos;
|
||||||
w.update_occupied(&old_pos, &pos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BoardCommand::Noop
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+58
-37
@@ -1,10 +1,8 @@
|
|||||||
use crate::lib::ai::{AIGoal, AI};
|
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::screen::Screen;
|
||||||
use crate::lib::world::World;
|
|
||||||
use rand::Rng;
|
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use ncurses::*;
|
use ncurses::*;
|
||||||
|
|
||||||
@@ -16,6 +14,7 @@ pub trait Entity: AI + Downcast {
|
|||||||
fn set_id(&mut self, id: u32);
|
fn set_id(&mut self, id: u32);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
macro_rules! impl_entity {
|
macro_rules! impl_entity {
|
||||||
($t: ident) => {
|
($t: ident) => {
|
||||||
impl Entity for $t {
|
impl Entity for $t {
|
||||||
@@ -40,7 +39,9 @@ impl_downcast!(Renderable);
|
|||||||
pub struct Ant {
|
pub struct Ant {
|
||||||
pub pos: Point,
|
pub pos: Point,
|
||||||
pub id: u32,
|
pub id: u32,
|
||||||
pub plan: Vec<AIGoal>,
|
pub goal: AIGoal,
|
||||||
|
pub dir: Direction,
|
||||||
|
pub history: HashSet<Point>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ant {
|
impl Ant {
|
||||||
@@ -48,10 +49,37 @@ impl Ant {
|
|||||||
Ant {
|
Ant {
|
||||||
pos: Point(x, y),
|
pos: Point(x, y),
|
||||||
id: 0,
|
id: 0,
|
||||||
plan: vec![],
|
goal: AIGoal::Seek,
|
||||||
|
dir: Direction::Up,
|
||||||
|
history: HashSet::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn forward(&mut self, s: &Screen) {
|
||||||
|
let target = self.dir.relative_point(&self.pos);
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cw(&mut self) {
|
||||||
|
self.dir = self.dir.cw();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn about_face(&mut self) {
|
||||||
|
self.cw();
|
||||||
|
self.cw();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl_entity!(Ant);
|
impl_entity!(Ant);
|
||||||
impl_entity!(Food);
|
impl_entity!(Food);
|
||||||
impl_entity!(FoodGenerator);
|
impl_entity!(FoodGenerator);
|
||||||
@@ -60,14 +88,27 @@ impl_entity!(Queen);
|
|||||||
|
|
||||||
impl Renderable for Ant {
|
impl Renderable for Ant {
|
||||||
fn representation(&self) -> &str {
|
fn representation(&self) -> &str {
|
||||||
"o"
|
match self.dir {
|
||||||
|
Direction::Up => "^",
|
||||||
|
Direction::Down => "v",
|
||||||
|
Direction::Right => ">",
|
||||||
|
Direction::Left => "<",
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn before_render(&self) {
|
fn before_render(&self) {
|
||||||
attron(A_BOLD());
|
attron(A_BOLD());
|
||||||
|
match self.goal {
|
||||||
|
AIGoal::Return => attron(A_UNDERLINE()),
|
||||||
|
AIGoal::Seek => 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
fn after_render(&self) {
|
fn after_render(&self) {
|
||||||
attroff(A_BOLD());
|
attroff(A_BOLD());
|
||||||
|
match self.goal {
|
||||||
|
AIGoal::Return => attroff(A_UNDERLINE()),
|
||||||
|
AIGoal::Seek => 0,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +214,7 @@ impl Entities {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Food {
|
pub struct Food {
|
||||||
pos: Point,
|
pub pos: Point,
|
||||||
id: u32,
|
id: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,14 +227,6 @@ 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?
|
|
||||||
BoardCommand::Noop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Renderable for Food {
|
impl Renderable for Food {
|
||||||
fn representation(&self) -> &str {
|
fn representation(&self) -> &str {
|
||||||
"f"
|
"f"
|
||||||
@@ -203,33 +236,21 @@ impl Renderable for Food {
|
|||||||
// no position, does not get rendered yet acts per turn
|
// no position, does not get rendered yet acts per turn
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct FoodGenerator {
|
pub struct FoodGenerator {
|
||||||
counter: u32,
|
pub timer: u32,
|
||||||
pos: Point,
|
pos: Point,
|
||||||
id: u32
|
id: u32,
|
||||||
|
pub counter: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FoodGenerator {
|
impl FoodGenerator {
|
||||||
pub fn new() -> 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 {
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
return BoardCommand::SpawnFood(Point(r_x, r_y));
|
|
||||||
}
|
|
||||||
self.counter += 1;
|
|
||||||
BoardCommand::Noop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Renderable for FoodGenerator {
|
impl Renderable for FoodGenerator {
|
||||||
|
|||||||
+48
-69
@@ -1,89 +1,68 @@
|
|||||||
use std::collections::{HashMap, HashSet};
|
|
||||||
|
|
||||||
#[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)]
|
#[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)]
|
||||||
pub struct Point(pub i32, pub i32);
|
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()]
|
||||||
Point(self.0, self.1 + 1),
|
}
|
||||||
Point(self.0, self.1 - 1),
|
|
||||||
Point(self.0 - 1, self.1),
|
pub fn left(&self) -> Point {
|
||||||
Point(self.0 + 1, self.1),
|
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 {
|
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
||||||
return (c2.0 - c1.0).abs() + (c2.1 - c1.1).abs();
|
pub enum Direction {
|
||||||
|
Up = 0,
|
||||||
|
Right = 1,
|
||||||
|
Down = 2,
|
||||||
|
Left = 3,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn astar(start: &Point, goal: &Point) -> Vec<Point> {
|
impl Direction {
|
||||||
let mut open_set : HashSet<Point> = HashSet::new();
|
pub fn from_u8(v: u8) -> Direction {
|
||||||
open_set.insert(*start);
|
match v {
|
||||||
|
0 => Direction::Up,
|
||||||
let mut came_from: HashMap<Point, Point> = HashMap::new();
|
1 => Direction::Right,
|
||||||
|
2 => Direction::Down,
|
||||||
let mut gscore: HashMap<Point, i32> = HashMap::new();
|
3 => Direction::Left,
|
||||||
gscore.insert(*start, 0);
|
_ => panic!("Shouldn't happen")
|
||||||
|
|
||||||
let mut fscore: HashMap<Point, i32> = 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());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
pub fn cw(&self) -> Direction {
|
||||||
|
let val = *self as u8;
|
||||||
|
Direction::from_u8((val + 1) % 4)
|
||||||
}
|
}
|
||||||
|
|
||||||
open_set.remove(¤t);
|
pub fn ccw(&self) -> Direction {
|
||||||
|
let val = *self as u8;
|
||||||
let current_gscore = gscore[¤t];
|
if val == 0 {
|
||||||
|
Direction::Left
|
||||||
let all_neighbors: Vec<Point> = current.get_neighbors();
|
|
||||||
for neighbor in all_neighbors.iter() {
|
|
||||||
let neighbor_score = if gscore.contains_key(&neighbor) {
|
|
||||||
gscore[neighbor]
|
|
||||||
} else {
|
} else {
|
||||||
i32::MAX
|
Direction::from_u8(val - 1)
|
||||||
};
|
|
||||||
let score = current_gscore + 1;
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
answer
|
pub fn relative_point(&self, p: &Point) -> Point {
|
||||||
}
|
match self {
|
||||||
|
Direction::Up => p.up(),
|
||||||
#[test]
|
Direction::Left => p.left(),
|
||||||
fn test_astar() {
|
Direction::Right => p.right(),
|
||||||
let start = Point(0, 0);
|
Direction::Down => p.down(),
|
||||||
|
}
|
||||||
let goal = Point(10, 10);
|
|
||||||
let answers: Vec<Point> = astar(&start, &goal);
|
|
||||||
|
|
||||||
for a in answers.iter() {
|
|
||||||
println!("{:?}", &a);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-40
@@ -1,16 +1,17 @@
|
|||||||
use crate::lib::point::Point;
|
use crate::lib::point::Point;
|
||||||
|
use ncurses::*;
|
||||||
|
|
||||||
pub struct Screen {
|
pub struct Screen {
|
||||||
pub center: Point,
|
pub center: Point,
|
||||||
max_x: i32,
|
pub max_x: i32,
|
||||||
max_y: i32,
|
pub max_y: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
use ncurses::*;
|
|
||||||
|
|
||||||
pub fn init_screen() -> Screen {
|
pub fn init_screen() -> Screen {
|
||||||
initscr();
|
initscr();
|
||||||
|
|
||||||
|
start_color();
|
||||||
|
init_pair(0, 0, 1);
|
||||||
/* Invisible cursor. */
|
/* Invisible cursor. */
|
||||||
curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);
|
curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);
|
||||||
|
|
||||||
@@ -32,54 +33,22 @@ impl Screen {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_in_bounds(&self, pos: &Point) -> bool {
|
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<Point> {
|
|
||||||
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) {
|
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) {
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[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 {
|
pub enum BoardCommand {
|
||||||
Dig(Point),
|
|
||||||
LayEgg(Point, u32),
|
LayEgg(Point, u32),
|
||||||
SpawnFood(Point),
|
SpawnFood(Point),
|
||||||
Hatch(u32, u32),
|
Hatch(u32, u32),
|
||||||
|
SpawnAnt,
|
||||||
Noop,
|
Noop,
|
||||||
}
|
}
|
||||||
|
|||||||
+69
-37
@@ -1,50 +1,77 @@
|
|||||||
use crate::lib::screen::{Screen, BoardCommand};
|
use crate::lib::ai::AIGoal;
|
||||||
|
use crate::lib::entity::{Ant, Egg, Entities, Food, Queen};
|
||||||
use crate::lib::point::Point;
|
use crate::lib::point::Point;
|
||||||
use crate::lib::entity::{Entities, Queen, Egg, Ant, Food};
|
use crate::lib::screen::{BoardCommand, Screen};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use ncurses::*;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
pub cleared: HashSet<Point>,
|
pub pheremones: Vec<Pheremone>,
|
||||||
pub occupied: HashSet<Point>,
|
pub food: HashSet<Point>,
|
||||||
pub food: HashSet<u32>
|
pub home: HashSet<Point>,
|
||||||
|
width: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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 {
|
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<Pheremone> = vec![Pheremone::new(); (x+1) as usize * (y+1) as usize];
|
||||||
|
|
||||||
World {
|
World {
|
||||||
cleared: HashSet::new(),
|
pheremones: ph,
|
||||||
occupied: HashSet::new(),
|
food: HashSet::new(),
|
||||||
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) {
|
pub fn get_mut_pheremone(&mut self, pos: &Point) -> &mut Pheremone {
|
||||||
self.cleared.insert(pos);
|
&mut self.pheremones[((pos.0 as usize) * self.width) + (pos.1 as usize)]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_valid_movement(&self, pos: &Point, b: &Screen) -> bool {
|
pub fn get_pheremone(&self, pos: &Point) -> &Pheremone {
|
||||||
b.is_in_bounds(pos) && !self.occupied.contains(pos)
|
&self.pheremones[((pos.0 as usize) * self.width) + (pos.1 as usize)]
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_valid_movements(&self, pos: &Point, b: &Screen) -> Vec<Point> {
|
pub fn drop_pheremone(&mut self, pos: &Point, state: &AIGoal) {
|
||||||
let moves = b.get_valid_movements(pos);
|
let ph = self.get_mut_pheremone(pos);
|
||||||
moves
|
|
||||||
.iter()
|
|
||||||
.filter(|p| !self.occupied.contains(p))
|
|
||||||
.map(|p| p.clone())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_occupied(&mut self, old: &Point, new: &Point) {
|
match state {
|
||||||
self.occupied.remove(old);
|
AIGoal::Seek => ph.home += 1,
|
||||||
self.occupied.insert(*new);
|
AIGoal::Return => ph.food += 1,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(e: &Entities, w: &World, b: &Screen) {
|
pub fn render(e: &Entities, w: &World, b: &Screen) {
|
||||||
for c in w.cleared.iter() {
|
erase();
|
||||||
b.render(c, "x");
|
|
||||||
|
for h in w.home.iter() {
|
||||||
|
b.render(h, "x");
|
||||||
}
|
}
|
||||||
|
|
||||||
for a in e.data.values() {
|
for a in e.data.values() {
|
||||||
@@ -52,20 +79,17 @@ pub fn render(e: &Entities, w: &World, b: &Screen) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) {
|
pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen, step: u32) {
|
||||||
let cmds: Vec<BoardCommand> = e
|
let plan_cmds: Vec<BoardCommand> = e.data.values_mut().map(|a| a.plan(b, w)).collect();
|
||||||
.data
|
let mut cmds: Vec<BoardCommand> = e.data.values_mut().map(|a| a.step(b, w, step)).collect();
|
||||||
.values_mut()
|
|
||||||
.map(|a| {
|
cmds.extend(plan_cmds);
|
||||||
a.plan(w);
|
|
||||||
a.step(b, w)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
for cmd in cmds {
|
for cmd in cmds {
|
||||||
match cmd {
|
match cmd {
|
||||||
BoardCommand::Dig(pos) => {
|
BoardCommand::SpawnAnt => {
|
||||||
w.clear(pos);
|
let ant = Ant::new(b.center.0, b.center.1);
|
||||||
|
e.add_entity(&ant);
|
||||||
}
|
}
|
||||||
BoardCommand::LayEgg(pos, id) => {
|
BoardCommand::LayEgg(pos, id) => {
|
||||||
let egg = Egg::new(pos.0, pos.1, id);
|
let egg = Egg::new(pos.0, pos.1, id);
|
||||||
@@ -84,8 +108,16 @@ pub fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) {
|
|||||||
BoardCommand::SpawnFood(pos) => {
|
BoardCommand::SpawnFood(pos) => {
|
||||||
let food = Food::new(pos.0, pos.1);
|
let food = Food::new(pos.0, pos.1);
|
||||||
e.add_entity(&food);
|
e.add_entity(&food);
|
||||||
|
w.food.insert(pos);
|
||||||
}
|
}
|
||||||
BoardCommand::Noop => {}
|
BoardCommand::Noop => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// decay all pheremones by some amount
|
||||||
|
if step % 600 == 0 {
|
||||||
|
for ph in w.pheremones.iter_mut() {
|
||||||
|
ph.decay();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-14
@@ -7,38 +7,42 @@ use std::thread::sleep;
|
|||||||
use std::time;
|
use std::time;
|
||||||
|
|
||||||
mod lib {
|
mod lib {
|
||||||
|
pub mod ai;
|
||||||
|
pub mod entity;
|
||||||
pub mod point;
|
pub mod point;
|
||||||
pub mod screen;
|
pub mod screen;
|
||||||
pub mod world;
|
pub mod world;
|
||||||
pub mod entity;
|
|
||||||
pub mod ai;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use lib::entity::{Ant, Entities, FoodGenerator};
|
||||||
use lib::point::Point;
|
use lib::point::Point;
|
||||||
use lib::screen::init_screen;
|
use lib::screen::init_screen;
|
||||||
use lib::world::{World, simulate, render};
|
use lib::world::{render, simulate, World};
|
||||||
use lib::entity::{Entities, Queen};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let mut board = init_screen();
|
let mut screen = init_screen();
|
||||||
let mut world = World::new();
|
let mut world = World::new(&Point(screen.max_x, screen.max_y));
|
||||||
|
world.home.insert(screen.center);
|
||||||
|
|
||||||
let mut entities = Entities::new();
|
let mut entities = Entities::new();
|
||||||
let q = Queen::new(0,0);
|
|
||||||
entities.add_entity(&q);
|
|
||||||
|
|
||||||
for i in -3..3 {
|
let fg = FoodGenerator::new();
|
||||||
for j in -3..3 {
|
entities.add_entity(&fg);
|
||||||
world.clear(Point(i, j));
|
|
||||||
}
|
for _ in 0..5 {
|
||||||
|
let mut a = Ant::new(screen.center.0, screen.center.1);
|
||||||
|
a.goal = lib::ai::AIGoal::Seek;
|
||||||
|
entities.add_entity(&a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut t = 0;
|
||||||
loop {
|
loop {
|
||||||
// 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 screen, t);
|
||||||
render(&entities, &world, &board);
|
render(&entities, &world, &screen);
|
||||||
sleep(time::Duration::from_millis(100));
|
sleep(time::Duration::from_millis(100));
|
||||||
refresh();
|
refresh();
|
||||||
|
t += 1;
|
||||||
}
|
}
|
||||||
endwin();
|
endwin();
|
||||||
}
|
}
|
||||||
|
|||||||
+11
-11
@@ -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;
|
||||||
@@ -13,7 +13,7 @@ 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(&Point(board.max_x, board.max_y));
|
||||||
|
|
||||||
let mut entities = Entities::new();
|
let mut entities = Entities::new();
|
||||||
|
|
||||||
@@ -22,15 +22,15 @@ fn test_reach_astar() {
|
|||||||
|
|
||||||
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 t in 0..420 {
|
||||||
// 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, t);
|
||||||
render(&entities, &world, &board);
|
render(&entities, &world, &board);
|
||||||
sleep(time::Duration::from_millis(100));
|
sleep(time::Duration::from_millis(100));
|
||||||
refresh();
|
refresh();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use antf::lib::screen::init_screen;
|
use antf::lib::screen::init_screen;
|
||||||
use antf::lib::world::{World, simulate, render};
|
use antf::lib::world::{World, simulate, render};
|
||||||
use antf::lib::entity::{Entities, FoodGenerator};
|
use antf::lib::entity::{Entities, FoodGenerator};
|
||||||
|
use antf::lib::point::Point;
|
||||||
|
|
||||||
use ncurses::*;
|
use ncurses::*;
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
@@ -9,15 +10,15 @@ use std::time;
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_foodgen() {
|
fn test_foodgen() {
|
||||||
let mut board = init_screen();
|
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 mut entities = Entities::new();
|
||||||
let fg = FoodGenerator::new();
|
let fg = FoodGenerator::new();
|
||||||
entities.add_entity(&fg);
|
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
|
// 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);
|
render(&entities, &world, &board);
|
||||||
sleep(time::Duration::from_millis(100));
|
sleep(time::Duration::from_millis(100));
|
||||||
refresh();
|
refresh();
|
||||||
|
|||||||
Reference in New Issue
Block a user