use crate::agent::{Agent, AgentId, Direction, Move, Position, WorldView}; use std::collections::hash_map::{Entry, HashMap}; pub struct ActualWorld { size: Position, pub agent_positions: HashMap, pub agents: HashMap>, pub tagged: AgentId, pub tagged_by: Option, } 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 .iter() .map(|(id, agent)| { let pos = self.agent_positions.get(id).unwrap(); ( *id, agent.next_move(WorldView { 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 { x: other_pos.x - pos.x, y: other_pos.y - pos.y, }, *id, ) }) .collect(), }), ) }) .collect(); for (id, mv) in moves { match mv { Move::Noop => {} Move::TryTag(other_id) => { // FIXME check distance 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 speed let entry = self.agent_positions.entry(id); let size = &self.size; assert!(matches!(entry, Entry::Occupied(_))); entry.and_modify(|e| { e.x += dir.x; assert!(e.x > 0); assert!(e.x < size.x); e.y += dir.y; assert!(e.y > 0); assert!(e.y < size.y); }); } } } } } #[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)); } #[test] #[should_panic] fn move_out_of_bounds() { let mut world = ActualWorld::new( Position { x: 1, y: 1 }, vec![( Position { x: 0, y: 0 }, Box::new(ScriptedAgent::new(vec![Move::TryMove((-1, -1).into())])), )], ); world.do_step(); } }