use crate::agent::{Agent, AgentId, Direction, Move, Position, WorldView}; use std::collections::hash_map::HashMap; pub struct ActualWorld { size: Position, pub agents: HashMap>, pub state: WorldState, validating: bool, } pub struct WorldState { pub agent_positions: 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)>, validating: bool, ) -> 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; } let state = WorldState { tagged_by: None, tagged: id - 1, agent_positions, }; Self { size, agents, state, validating, } } pub fn do_step(&mut self) { let moves: Vec<_> = self .agents .iter() .map(|(id, agent)| { let pos = self.state.agent_positions.get(id).unwrap(); ( *id, agent.next_move(WorldView { self_id: *id, tagged_by: self.state.tagged_by, tagged: self.state.tagged, bounds_distance: (pos.y, self.size.x - pos.x, self.size.y - pos.y, pos.x), other_agents: self .state .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(); let mut new_state = WorldState { agent_positions: HashMap::with_capacity(self.state.agent_positions.len()), tagged: self.state.tagged, tagged_by: self.state.tagged_by, }; for (id, mv) in moves { if self.validating { self.check_move(id, &mv); } let mut new_pos = None; let pos = self.state.agent_positions.get(&id).unwrap(); match mv { Move::Noop => {} Move::TryTag(other_id) => { new_state.tagged = other_id; new_state.tagged_by = Some(id); } Move::TryMove(dir) => { new_pos = Some((pos.x + dir.x, pos.y + dir.y).into()); } } new_state .agent_positions .insert(id, new_pos.unwrap_or_else(|| pos.clone())); } self.state = new_state; } fn check_move(&self, id: AgentId, mv: &Move) { match mv { Move::Noop => {} Move::TryTag(other_id) => { let my_pos = self.state.agent_positions.get(&id).unwrap(); let other_pos = self.state.agent_positions.get(&other_id).unwrap(); assert!((my_pos.x - other_pos.x).abs() <= 1); assert!((my_pos.y - other_pos.y).abs() <= 1); assert_eq!(self.state.tagged, id); assert_ne!(self.state.tagged_by, Some(*other_id)); assert!( self.agents.contains_key(&other_id), "Trying to tag a nonexisting agent" ); } Move::TryMove(dir) => { assert!(dir.x.abs() <= 1); assert!(dir.y.abs() <= 1); let pos = self.state.agent_positions.get(&id).unwrap(); let size = &self.size; assert!(pos.x + dir.x > 0); assert!(pos.x + dir.x < size.x); assert!(pos.y + dir.y > 0); assert!(pos.y + dir.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![], true); } #[test] fn trivial() { let mut world = ActualWorld::new( Position { x: 0, y: 0 }, vec![(Position { x: 0, y: 0 }, Box::new(NullAgent))], true, ); world.do_step(); assert_eq!(world.state.tagged, 0); assert_eq!(world.state.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)])), ), ], true, ); world.do_step(); assert_eq!(world.state.tagged, 0); assert_eq!(world.state.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())])), )], true, ); world.do_step(); } }