diff --git a/src/agent.rs b/src/agent.rs index 58ecea0..db15ccd 100644 --- a/src/agent.rs +++ b/src/agent.rs @@ -5,19 +5,35 @@ pub struct Position { pub y: Distance, } +impl From<(Distance, Distance)> for Position { + fn from((x, y): (Distance, Distance)) -> Self { + Self { x, y } + } +} + +#[derive(Debug)] pub struct Direction { pub x: Distance, pub y: Distance, } +impl From<(Distance, Distance)> for Direction { + fn from((x, y): (Distance, Distance)) -> Self { + Self { x, y } + } +} + pub type AgentId = usize; pub struct WorldView { - pub self_tagged: bool, + pub self_id: AgentId, + pub tagged: AgentId, + pub tagged_by: Option, pub bounds_distance: (Distance, Distance, Distance, Distance), pub other_agents: Vec<(Direction, AgentId)>, } +#[derive(Debug)] pub enum Move { Noop, // Why not an Option? TryTag(AgentId), @@ -26,5 +42,81 @@ pub enum Move { pub trait Agent { fn next_move(&self, view: WorldView) -> Move; - fn get_tagged(&mut self, by: AgentId); +} + +pub struct NullAgent; + +impl Agent for NullAgent { + fn next_move(&self, _view: WorldView) -> Move { + Move::Noop + } +} + +use std::cell::RefCell; + +pub struct ScriptedAgent { + moves: RefCell>, +} + +impl ScriptedAgent { + pub fn new(mut moves: Vec) -> Self { + moves.reverse(); + Self { + moves: RefCell::new(moves), + } + } +} + +impl Agent for ScriptedAgent { + fn next_move(&self, _view: WorldView) -> Move { + self + .moves + .borrow_mut() + .pop() + .expect("ScriptedAgent has no moves left to make") + } +} + +pub struct SimpleAgent; + +fn rand(exclusive_upper_bound: u32) -> u32 { + std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .subsec_micros() + % exclusive_upper_bound +} + +fn random_move() -> Move { + Move::TryMove( + match rand(8) { + 0 => (-1, -1), + 1 => (0, -1), + 2 => (1, -1), + 3 => (1, 0), + 4 => (1, 1), + 5 => (0, 1), + 6 => (-1, 1), + 7 => (-1, 0), + _ => unreachable!(), + } + .into(), + ) +} + +impl Agent for SimpleAgent { + fn next_move(&self, view: WorldView) -> Move { + if view.self_id == view.tagged { + match view + .other_agents + .iter() + .find(|(dist, id)| dist.x.abs() <= 1 && dist.y.abs() <= 1 && Some(*id) != view.tagged_by) + { + Some((_, other)) => Move::TryTag(*other), + None => random_move(), + } + } else { + random_move() + } + } } diff --git a/src/main.rs b/src/main.rs index 80a1832..5995229 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,16 @@ +use gntag::agent::{Agent, Position, SimpleAgent}; +use gntag::world::ActualWorld; + fn main() { - println!("Hello, world!"); + let mut agents: Vec<(_, Box)> = vec![]; + for x in 0..5 { + for y in 0..5 { + agents.push(((x, y).into(), Box::new(SimpleAgent))); + } + } + let mut world = ActualWorld::new((80, 40).into(), agents); + loop { + world.do_step(); + println!("{}", world.tagged); + } } diff --git a/src/world.rs b/src/world.rs index 9c9dda4..aeaf9cf 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,5 +1,5 @@ use crate::agent::{Agent, AgentId, Direction, Move, Position, WorldView}; -use std::collections::HashMap; +use std::collections::hash_map::{Entry, HashMap}; pub struct ActualWorld { size: Position, @@ -10,6 +10,28 @@ pub struct ActualWorld { } impl ActualWorld { + /// Agents receive incrementing ids starting with 0 + /// The last agent is 'It' + pub fn new(size: Position, input_agents: Vec<(Position, Box)>) -> Self { + let agent_count = input_agents.len(); + assert!(agent_count > 0); + let mut id = 0; + let mut agent_positions = HashMap::with_capacity(agent_count); + let mut agents = HashMap::with_capacity(agent_count); + for (pos, agent) in input_agents.into_iter() { + agent_positions.insert(id, pos); + agents.insert(id, agent); + id += 1; + } + Self { + size, + tagged_by: None, + tagged: id - 1, + agents, + agent_positions, + } + } + pub fn do_step(&mut self) { let moves: Vec<_> = self .agents @@ -19,11 +41,14 @@ impl ActualWorld { ( *id, agent.next_move(WorldView { - self_tagged: *id == self.tagged, + self_id: *id, + tagged_by: self.tagged_by, + tagged: self.tagged, bounds_distance: (pos.y, self.size.x - pos.x, self.size.y - pos.y, pos.x), other_agents: self .agent_positions .iter() + .filter(|(other_id, _)| id != *other_id) .map(|(id, other_pos)| { ( Direction { @@ -43,15 +68,21 @@ impl ActualWorld { Move::Noop => {} Move::TryTag(other_id) => { // FIXME check distance - // FIXME check tagged_by - self.agents.get_mut(&other_id).unwrap().get_tagged(other_id); - // FIXME update tagged - // FIXME update tagged_by + assert_eq!(self.tagged, id); + assert_ne!(self.tagged_by, Some(other_id)); + assert!( + self.agents.contains_key(&other_id), + "Trying to tag a nonexisting agent" + ); + self.tagged = other_id; + self.tagged_by = Some(id); } Move::TryMove(dir) => { // FIXME check out of bounds - // FIXME matches! - self.agent_positions.entry(id).and_modify(|e| { + // FIXME check speed + let entry = self.agent_positions.entry(id); + assert!(matches!(entry, Entry::Occupied(_))); + entry.and_modify(|e| { e.x += dir.x; e.y += dir.y; }); @@ -60,3 +91,46 @@ impl ActualWorld { } } } + +#[cfg(test)] +mod test { + use crate::agent::{Move, NullAgent, Position, ScriptedAgent}; + use crate::world::ActualWorld; + + #[test] + #[should_panic] + fn empty() { + ActualWorld::new(Position { x: 0, y: 0 }, vec![]); + } + + #[test] + fn trivial() { + let mut world = ActualWorld::new( + Position { x: 0, y: 0 }, + vec![(Position { x: 0, y: 0 }, Box::new(NullAgent))], + ); + world.do_step(); + assert_eq!(world.tagged, 0); + assert_eq!(world.tagged_by, None); + } + + #[test] + fn scripted_tagging() { + let mut world = ActualWorld::new( + Position { x: 0, y: 0 }, + vec![ + ( + Position { x: 0, y: 0 }, + Box::new(ScriptedAgent::new(vec![Move::Noop])), + ), + ( + Position { x: 0, y: 0 }, + Box::new(ScriptedAgent::new(vec![Move::TryTag(0)])), + ), + ], + ); + world.do_step(); + assert_eq!(world.tagged, 0); + assert_eq!(world.tagged_by, Some(1)); + } +}