From 1b6dd4b9ad0d86bd7a5e66f52831335838bd30fe Mon Sep 17 00:00:00 2001 From: Rostyslav Hnatyshyn Date: Tue, 2 Jan 2024 13:19:57 -0700 Subject: [PATCH] before removing side effects --- .gitignore | 1 + Cargo.lock | 102 ++++++++++++++++++ Cargo.toml | 10 ++ src/main.rs | 300 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 413 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..5cd0b23 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,102 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "antf" +version = "0.1.0" +dependencies = [ + "ncurses", + "rand", +] + +[[package]] +name = "cc" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" +dependencies = [ + "libc", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "getrandom" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "libc" +version = "0.2.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" + +[[package]] +name = "ncurses" +version = "5.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5d34d72657dc4b638a1c25d40aae81e4f1c699062f72f467237920752032" +dependencies = [ + "cc", + "libc", + "pkg-config", +] + +[[package]] +name = "pkg-config" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69d3587f8a9e599cc7ec2c00e331f71c4e69a5f9a4b8a6efd5b07466b9736f9a" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ac2581e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "antf" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +ncurses = "5.101.0" +rand = "0.8.5" diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..cd8daff --- /dev/null +++ b/src/main.rs @@ -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 { + 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, +} + +impl World { + fn new() -> World { + World { + cleared: HashSet::new(), + } + } + + fn clear(&mut self, pos: Point) { + self.cleared.insert(pos); + } +} + +struct Colony { + ants: Vec>, +} + +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(&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 = 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(); +}