diff --git a/src/lib/ai.rs b/src/lib/ai.rs new file mode 100644 index 0000000..bfb45ce --- /dev/null +++ b/src/lib/ai.rs @@ -0,0 +1,82 @@ + +use crate::lib::screen::{Screen, BoardCommand}; +use crate::World; +use crate::Point; +use crate::lib::entity::{Ant, Egg, Queen}; +use rand::thread_rng; +use rand::prelude::SliceRandom; + +pub trait AI { + fn step(&mut self, b: &Screen, w: &World) -> BoardCommand; + fn get_position(&self) -> Point; +} + +impl AI for Ant { + fn get_position(&self) -> Point { + self.pos.clone() + } + // return the next move for this ant + fn step(&mut self, b: &Screen, w: &World) -> BoardCommand { + let valid = w.get_valid_movements(&self.pos, b); + let mut rng = thread_rng(); + + let choice = valid.choose(&mut rng).cloned(); + + if choice.is_none() { + return BoardCommand::Noop; + } else { + let pos = choice.unwrap(); + if w.cleared.contains(&pos) { + self.pos = pos; + return BoardCommand::Noop; + } else { + return BoardCommand::Dig(pos); + } + } + } +} + +impl AI for Egg { + fn step(&mut self, b: &Screen, w: &World) -> BoardCommand { + if self.counter > 0 { + self.counter -= 1; + return BoardCommand::Noop; + } else { + BoardCommand::Hatch(self.id, self.queen_id) + } + } + + fn get_position(&self) -> Point { + self.pos.clone() + } +} + +impl AI for Queen { + fn get_position(&self) -> Point { + self.pos.clone() + } + fn step(&mut self, b: &Screen, w: &World) -> BoardCommand { + let valid: Vec = w.get_valid_movements(&self.pos, b); + let mut rng = thread_rng(); + + let choice = valid.choose(&mut rng).cloned(); + + if choice.is_none() { + return BoardCommand::Noop; + } else { + let pos = choice.unwrap(); + if w.cleared.contains(&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; + return BoardCommand::Noop; + } + } else { + return BoardCommand::Noop; + } + } + } +} diff --git a/src/lib/entity.rs b/src/lib/entity.rs new file mode 100644 index 0000000..101973b --- /dev/null +++ b/src/lib/entity.rs @@ -0,0 +1,126 @@ + +use crate::Point; +use std::collections::HashMap; +use crate::lib::ai::AI; + +use downcast_rs::{impl_downcast, Downcast}; + +pub trait Entity: AI + Renderable + Downcast {} +impl_downcast!(Entity); + +pub struct Ant { + pub pos: Point, + pub id: u32, +} + +impl Ant { + pub fn new(x: i32, y: i32, id: u32) -> Ant { + Ant { + pos: Point(x, y), + id, + } + } +} + +impl Entity for Ant {} + +impl Renderable for Ant { + fn render(&self) -> &str { + "o" + } +} + +pub struct Queen { + pub pos: Point, + pub egg_count: u8, + pub id: u32, +} + +impl Renderable for Queen { + fn render(&self) -> &str { + "q" + } +} + +pub trait Renderable { + fn render(&self) -> &str { + "z" + } +} + +impl Queen { + pub fn new(x: i32, y: i32, id: u32) -> Queen { + Queen { + pos: Point(x, y), + egg_count: 0, + id, + } + } +} + +#[derive(Debug)] +pub struct Egg { + pub pos: Point, + pub counter: u8, + pub queen_id: u32, + pub id: u32, +} + +impl Renderable for Egg { + fn render(&self) -> &str { + "e" + } +} + +impl Egg { + pub fn new(x: i32, y: i32, id: u32, queen_id: u32) -> Egg { + Egg { + pos: Point(x, y), + counter: 10, + id, + queen_id, + } + } +} + +impl Entity for Queen {} +impl Entity for Egg {} + +pub struct Entities { + pub data: HashMap>, + id_counter: u32, +} + +impl Entities { + pub fn new() -> Entities { + Entities { + id_counter: 0, + data: HashMap::new(), + } + } + + pub fn add_ant(&mut self, x: i32, y: i32) -> u32 { + let a = Ant::new(x, y, self.id_counter); + let id = a.id; + self.data.insert(a.id, Box::new(a)); + self.id_counter += 1; + id + } + + pub fn add_queen(&mut self, x: i32, y: i32) -> u32 { + let q = Queen::new(x, y, self.id_counter); + let id = q.id; + self.data.insert(q.id, Box::new(q)); + self.id_counter += 1; + id + } + + pub fn add_egg(&mut self, x: i32, y: i32, queen_id: u32) -> u32 { + let e = Egg::new(x, y, self.id_counter, queen_id); + let id = e.id; + self.data.insert(e.id, Box::new(e)); + self.id_counter += 1; + id + } +} + diff --git a/src/lib/point.rs b/src/lib/point.rs new file mode 100644 index 0000000..dbf85d6 --- /dev/null +++ b/src/lib/point.rs @@ -0,0 +1,13 @@ +#[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)] +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), + ] + } +} diff --git a/src/lib/screen.rs b/src/lib/screen.rs new file mode 100644 index 0000000..64d09f0 --- /dev/null +++ b/src/lib/screen.rs @@ -0,0 +1,37 @@ +use crate::Point; + +pub struct Screen { + pub center: Point, + max_x: i32, + max_y: i32, +} + +impl Screen { + pub fn new(max_x: i32, max_y: i32) -> Screen { + Screen { + center: Point(max_x / 2, max_y / 2), + max_x, + max_y, + } + } + + 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) + } + + 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 enum BoardCommand { + Dig(Point), + LayEgg(Point, u32), + Hatch(u32, u32), + Noop, +} diff --git a/src/lib/world.rs b/src/lib/world.rs new file mode 100644 index 0000000..9b81587 --- /dev/null +++ b/src/lib/world.rs @@ -0,0 +1,31 @@ +use crate::Screen; +use crate::Point; +use std::collections::HashSet; + +#[derive(Clone)] +pub struct World { + pub cleared: HashSet, + pub occupied: HashSet, +} + +impl World { + pub fn new() -> World { + World { + cleared: HashSet::new(), + occupied: HashSet::new(), + } + } + + pub fn clear(&mut self, pos: Point) { + self.cleared.insert(pos); + } + + 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)) + .map(|p| p.clone()) + .collect() + } +} diff --git a/src/main.rs b/src/main.rs index 661df3c..1c1fb63 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,246 +2,24 @@ extern crate downcast_rs; extern crate ncurses; use ncurses::*; -use rand::seq::SliceRandom; -use rand::thread_rng; -use std::collections::{HashMap, HashSet}; + use std::thread::sleep; use std::time; -#[derive(Hash, PartialEq, Eq, Clone, Debug, Copy)] -struct Point(i32, i32); - -struct Board { - center: Point, - max_x: i32, - max_y: i32, -} - -fn get_neighbors(pos: &Point) -> Vec { - vec![ - Point(pos.0, pos.1 + 1), - Point(pos.0, pos.1 - 1), - Point(pos.0 - 1, pos.1), - Point(pos.0 + 1, pos.1), - ] -} - -impl Board { - fn new(max_x: i32, max_y: i32) -> Board { - Board { - center: Point(max_x / 2, max_y / 2), - max_x, - max_y, - } - } - - 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) - } - - fn get_valid_movements(&self, pos: &Point) -> Vec { - let binding = get_neighbors(pos); - binding - .iter() - .filter(|e| self.is_in_bounds(e)) - .map(|e| e.clone()) - .collect() - } -} -#[derive(Clone)] -struct World { - cleared: HashSet, - occupied: HashSet, -} - -impl World { - fn new() -> World { - World { - cleared: HashSet::new(), - occupied: HashSet::new(), - } - } - - fn clear(&mut self, pos: Point) { - self.cleared.insert(pos); - } - - fn get_valid_movements(&self, pos: &Point, b: &Board) -> Vec { - let moves = b.get_valid_movements(pos); - moves - .iter() - .filter(|p| !self.occupied.contains(p)) - .map(|p| p.clone()) - .collect() - } -} - -use downcast_rs::{impl_downcast, Downcast}; - -trait Entity: AI + Renderable + Downcast {} -impl_downcast!(Entity); - -trait AI { - fn step(&mut self, b: &Board, w: &World) -> BoardCommand; - fn get_position(&self) -> Point; -} - -struct Ant { - pos: Point, - id: u32, -} - -impl Ant { - fn new(x: i32, y: i32, id: u32) -> Ant { - Ant { - pos: Point(x, y), - id, - } - } -} - -impl Entity for Ant {} - -impl Renderable for Ant { - fn render(&self) -> &str { - "o" - } -} - -struct Queen { - pos: Point, - egg_count: u8, - id: u32, -} - -impl Renderable for Queen { - fn render(&self) -> &str { - "q" - } -} - -trait Renderable { - fn render(&self) -> &str { - "z" - } -} - -impl Queen { - fn new(x: i32, y: i32, id: u32) -> Queen { - Queen { - pos: Point(x, y), - egg_count: 0, - id, - } - } -} - -#[derive(Debug)] -struct Egg { - pos: Point, - counter: u8, - queen_id: u32, - id: u32, -} - -impl Renderable for Egg { - fn render(&self) -> &str { - "e" - } -} - -impl Egg { - fn new(x: i32, y: i32, id: u32, queen_id: u32) -> Egg { - Egg { - pos: Point(x, y), - counter: 10, - id, - queen_id, - } - } -} - -impl AI for Egg { - fn step(&mut self, b: &Board, w: &World) -> BoardCommand { - if self.counter > 0 { - self.counter -= 1; - return BoardCommand::Noop; - } else { - BoardCommand::Hatch(self.id, self.queen_id) - } - } - - fn get_position(&self) -> Point { - self.pos.clone() - } -} - -enum BoardCommand { - Dig(Point), - LayEgg(Point, u32), - Hatch(u32, u32), - Noop, -} - -impl AI for Ant { - fn get_position(&self) -> Point { - self.pos.clone() - } - // return the next move for this ant - fn step(&mut self, b: &Board, w: &World) -> BoardCommand { - let valid = w.get_valid_movements(&self.pos, b); - let mut rng = thread_rng(); - - let choice = valid.choose(&mut rng).cloned(); - - if choice.is_none() { - return BoardCommand::Noop; - } else { - let pos = choice.unwrap(); - if w.cleared.contains(&pos) { - self.pos = pos; - return BoardCommand::Noop; - } else { - return BoardCommand::Dig(pos); - } - } - } +mod lib { + pub mod point; + pub mod screen; + pub mod world; + pub mod entity; + pub mod ai; } -impl Entity for Queen {} -impl Entity for Egg {} - -impl AI for Queen { - fn get_position(&self) -> Point { - self.pos.clone() - } - fn step(&mut self, b: &Board, w: &World) -> BoardCommand { - let valid: Vec = w.get_valid_movements(&self.pos, b); - let mut rng = thread_rng(); - - let choice = valid.choose(&mut rng).cloned(); +use lib::point::Point; +use lib::screen::{BoardCommand, Screen}; +use lib::world::World; +use lib::entity::{Queen, Entities}; - if choice.is_none() { - return BoardCommand::Noop; - } else { - let pos = choice.unwrap(); - if w.cleared.contains(&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; - return BoardCommand::Noop; - } - } else { - return BoardCommand::Noop; - } - } - } -} - -fn render(e: &Entities, w: &World, b: &Board) { +fn render(e: &Entities, w: &World, b: &Screen) { for c in w.cleared.iter() { mvprintw(c.1 + b.center.1, c.0 + b.center.0, "x"); } @@ -262,7 +40,7 @@ mod tests { let max_x = 20; let max_y = 20; - let mut board = Board::new(max_x, max_y); + let mut board = Screen::new(max_x, max_y); let mut world = World::new(); dbg!(board.is_in_bounds(&Point(0, 0))); @@ -270,7 +48,7 @@ mod tests { } } -fn simulate(e: &mut Entities, w: &mut World, b: &mut Board) { +fn simulate(e: &mut Entities, w: &mut World, b: &mut Screen) { let cmds: Vec = e .data .values_mut() @@ -301,43 +79,6 @@ fn simulate(e: &mut Entities, w: &mut World, b: &mut Board) { let _ = e.data.values().map(|p| w.occupied.insert(p.get_position())); } -struct Entities { - data: HashMap>, - id_counter: u32, -} - -impl Entities { - fn new() -> Entities { - Entities { - id_counter: 0, - data: HashMap::new(), - } - } - - fn add_ant(&mut self, x: i32, y: i32) -> u32 { - let a = Ant::new(x, y, self.id_counter); - let id = a.id; - self.data.insert(a.id, Box::new(a)); - self.id_counter += 1; - id - } - - fn add_queen(&mut self, x: i32, y: i32) -> u32 { - let q = Queen::new(x, y, self.id_counter); - let id = q.id; - self.data.insert(q.id, Box::new(q)); - self.id_counter += 1; - id - } - - fn add_egg(&mut self, x: i32, y: i32, queen_id: u32) -> u32 { - let e = Egg::new(x, y, self.id_counter, queen_id); - let id = e.id; - self.data.insert(e.id, Box::new(e)); - self.id_counter += 1; - id - } -} fn main() { initscr(); @@ -350,14 +91,9 @@ fn main() { getmaxyx(stdscr(), &mut max_y, &mut max_x); - // might move all of contents of board into world - - // TODO: rename board to screen -> will keep track of where we are looking at in the world - // TODO: create entity factory to build new entities, will make it easier to have multiple - // colonies because no ids will ever overlap // TODO: fix renderable to render different colors - let mut board = Board::new(max_x, max_y); + let mut board = Screen::new(max_x, max_y); let mut world = World::new(); let mut entities = Entities::new();