|
|
|
@ -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<Point> { |
|
|
|
|
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<Point> { |
|
|
|
|
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<Point>, |
|
|
|
|
occupied: HashSet<Point>, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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<Point> { |
|
|
|
|
let moves = b.get_valid_movements(pos); |
|
|
|
|
moves |
|
|
|
|
.iter() |
|
|
|
|
.filter(|p| !self.occupied.contains(p)) |
|
|
|
|
.map(|p| p.clone()) |
|
|
|
|
.collect() |
|
|
|
|
} |
|
|
|
|
mod lib { |
|
|
|
|
pub mod point; |
|
|
|
|
pub mod screen; |
|
|
|
|
pub mod world; |
|
|
|
|
pub mod entity; |
|
|
|
|
pub mod ai; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
use lib::point::Point; |
|
|
|
|
use lib::screen::{BoardCommand, Screen}; |
|
|
|
|
use lib::world::World; |
|
|
|
|
use lib::entity::{Queen, Entities}; |
|
|
|
|
|
|
|
|
|
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); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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<Point> = 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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
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<BoardCommand> = 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<u32, Box<dyn Entity>>, |
|
|
|
|
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(); |
|
|
|
|