before removing side effects

This commit is contained in:
2024-01-02 13:19:57 -07:00
commit 1b6dd4b9ad
4 changed files with 413 additions and 0 deletions
+300
View File
@@ -0,0 +1,300 @@
extern crate ncurses;
use ncurses::*;
use rand::seq::SliceRandom;
use rand::thread_rng;
use std::collections::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>,
}
impl World {
fn new() -> World {
World {
cleared: HashSet::new(),
}
}
fn clear(&mut self, pos: Point) {
self.cleared.insert(pos);
}
}
struct Colony {
ants: Vec<Box<dyn AI>>,
}
impl Colony {
fn new() -> Colony {
Colony { ants: vec![] }
}
fn add_ant(&mut self, x: i32, y: i32) {
self.ants.push(Box::new(Ant::new(x, y)));
}
fn add_entity<T: AI + 'static>(&mut self, e: T) {
self.ants.push(Box::new(e));
}
}
trait AI {
fn step(&mut self, b: &Board, w: &World) -> BoardCommand;
fn get_position(&self) -> Point;
fn set_position(&mut self, p: Point);
}
struct Ant {
pos: Point,
}
impl Ant {
fn new(x: i32, y: i32) -> Ant {
Ant { pos: Point(x, y) }
}
}
struct Queen {
pos: Point,
egg_count: u8,
}
impl Queen {
fn new(x: i32, y: i32) -> Queen {
Queen {
pos: Point(x, y),
egg_count: 0,
}
}
}
#[derive(Debug)]
struct Egg {
pos: Point,
counter: u8,
}
impl Egg {
fn new(x: i32, y: i32) -> Egg {
Egg {
pos: Point(x, y),
counter: 10,
}
}
}
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
}
}
fn get_position(&self) -> Point {
self.pos.clone()
}
fn set_position(&mut self, p: Point) {
self.pos = p;
}
}
enum BoardCommand<'a> {
Move(Box<&'a mut dyn AI>, Point),
Dig(Point),
LayEgg(Point),
Hatch,
Noop,
}
impl AI for Ant {
// return the next move for this ant
fn step(&mut self, b: &Board, w: &World) -> BoardCommand {
let valid = b.get_valid_movements(&self.pos);
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) {
return BoardCommand::Move(Box::new(self), pos);
} else {
return BoardCommand::Dig(pos);
}
}
}
fn get_position(&self) -> Point {
self.pos.clone()
}
fn set_position(&mut self, p: Point) {
self.pos = p
}
}
impl AI for Queen {
fn step(&mut self, b: &Board, w: &World) -> BoardCommand {
let valid = b.get_valid_movements(&self.pos);
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);
} else {
return BoardCommand::Move(Box::new(self), pos);
}
} else {
return BoardCommand::Noop;
}
}
}
fn get_position(&self) -> Point {
self.pos.clone()
}
fn set_position(&mut self, p: Point) {
self.pos = p
}
}
fn render(c: &Colony, w: &World, b: &Board) {
for c in w.cleared.iter() {
mvprintw(c.1 + b.center.1, c.0 + b.center.0, "x");
}
for a in c.ants.iter() {
let pos = a.get_position();
mvprintw(pos.1 + b.center.1, pos.0 + b.center.0, "o");
}
}
#[cfg(test)]
mod tests {
// TODO: tests, cleanup code in general
use super::*;
#[test]
fn board() {
let max_x = 20;
let max_y = 20;
let mut board = Board::new(max_x, max_y);
let mut world = World::new();
dbg!(board.is_in_bounds(&Point(0, 0)));
dbg!(board.get_valid_movements(&Point(0, 0)));
let mut ant = Ant { pos: Point(0, 0) };
}
}
use std::iter::zip;
fn simulate(c: &mut Colony, w: &mut World, b: &mut Board) {
let world = w.clone();
let cmds: Vec<BoardCommand> = c.ants.iter_mut().map(|a| a.step(b, &world)).collect();
for cmd in cmds {
match cmd {
BoardCommand::Move(a, pos) => {
a.set_position(pos);
}
BoardCommand::Dig(pos) => {
w.clear(pos);
}
BoardCommand::LayEgg(pos) => {
//c.add_entity(Egg::new(pos.0, pos.1));
}
BoardCommand::Hatch => {}
BoardCommand::Noop => {}
}
}
}
fn main() {
initscr();
/* Invisible cursor. */
curs_set(CURSOR_VISIBILITY::CURSOR_INVISIBLE);
let mut max_x = 0;
let mut max_y = 0;
getmaxyx(stdscr(), &mut max_y, &mut max_x);
// might move all of contents of board into world
let mut board = Board::new(max_x, max_y);
let mut world = World::new();
let mut colony = Colony::new();
colony.add_ant(0,0);
loop {
// TODO: add way to break out of the loop by hitting a random key
simulate(&mut colony, &mut world, &mut board);
render(&colony, &world, &board);
sleep(time::Duration::from_millis(100));
refresh();
}
endwin();
}