A simple actor-based simulation of tag, implemented in rust.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

184 lines
5.0 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. use crate::agent::{Agent, AgentId, Direction, Move, Position, WorldView};
  2. use std::collections::hash_map::HashMap;
  3. pub struct ActualWorld {
  4. size: Position,
  5. pub agents: HashMap<AgentId, Box<dyn Agent>>,
  6. pub state: WorldState,
  7. }
  8. pub struct WorldState {
  9. pub agent_positions: HashMap<AgentId, Position>,
  10. pub tagged: AgentId,
  11. pub tagged_by: Option<AgentId>,
  12. }
  13. impl ActualWorld {
  14. /// Agents receive incrementing ids starting with 0
  15. /// The last agent is 'It'
  16. pub fn new(size: Position, input_agents: Vec<(Position, Box<dyn Agent>)>) -> Self {
  17. let agent_count = input_agents.len();
  18. assert!(agent_count > 0);
  19. let mut id = 0;
  20. let mut agent_positions = HashMap::with_capacity(agent_count);
  21. let mut agents = HashMap::with_capacity(agent_count);
  22. for (pos, agent) in input_agents.into_iter() {
  23. agent_positions.insert(id, pos);
  24. agents.insert(id, agent);
  25. id += 1;
  26. }
  27. let state = WorldState {
  28. tagged_by: None,
  29. tagged: id - 1,
  30. agent_positions,
  31. };
  32. Self {
  33. size,
  34. agents,
  35. state,
  36. }
  37. }
  38. pub fn do_step(&mut self) {
  39. let moves: Vec<_> = self
  40. .agents
  41. .iter()
  42. .map(|(id, agent)| {
  43. let pos = self.state.agent_positions.get(id).unwrap();
  44. (
  45. *id,
  46. agent.next_move(WorldView {
  47. self_id: *id,
  48. tagged_by: self.state.tagged_by,
  49. tagged: self.state.tagged,
  50. bounds_distance: (pos.y, self.size.x - pos.x, self.size.y - pos.y, pos.x),
  51. other_agents: self
  52. .state
  53. .agent_positions
  54. .iter()
  55. .filter(|(other_id, _)| id != *other_id)
  56. .map(|(id, other_pos)| {
  57. (
  58. Direction {
  59. x: other_pos.x - pos.x,
  60. y: other_pos.y - pos.y,
  61. },
  62. *id,
  63. )
  64. })
  65. .collect(),
  66. }),
  67. )
  68. })
  69. .collect();
  70. let mut new_state = WorldState {
  71. agent_positions: HashMap::with_capacity(self.state.agent_positions.len()),
  72. tagged: self.state.tagged,
  73. tagged_by: self.state.tagged_by,
  74. };
  75. for (id, mv) in moves {
  76. self.check_move(id, &mv);
  77. //println!("{} {:?}", id, mv);
  78. let mut new_pos = None;
  79. match mv {
  80. Move::Noop => {}
  81. Move::TryTag(other_id) => {
  82. new_state.tagged = other_id;
  83. new_state.tagged_by = Some(id);
  84. }
  85. Move::TryMove(dir) => {
  86. let pos = self.state.agent_positions.get(&id).unwrap();
  87. new_pos = Some((pos.x + dir.x, pos.y + dir.y).into());
  88. }
  89. }
  90. new_state.agent_positions.insert(
  91. id,
  92. new_pos.unwrap_or_else(|| self.state.agent_positions.remove(&id).unwrap()),
  93. );
  94. }
  95. self.state = new_state;
  96. }
  97. fn check_move(&self, id: AgentId, mv: &Move) {
  98. match mv {
  99. Move::Noop => {}
  100. Move::TryTag(other_id) => {
  101. let my_pos = self.state.agent_positions.get(&id).unwrap();
  102. let other_pos = self.state.agent_positions.get(&other_id).unwrap();
  103. assert!((my_pos.x - other_pos.x).abs() <= 1);
  104. assert!((my_pos.y - other_pos.y).abs() <= 1);
  105. assert_eq!(self.state.tagged, id);
  106. assert_ne!(self.state.tagged_by, Some(*other_id));
  107. assert!(
  108. self.agents.contains_key(&other_id),
  109. "Trying to tag a nonexisting agent"
  110. );
  111. }
  112. Move::TryMove(dir) => {
  113. assert!(dir.x.abs() <= 1);
  114. assert!(dir.y.abs() <= 1);
  115. let pos = self.state.agent_positions.get(&id).unwrap();
  116. let size = &self.size;
  117. assert!(pos.x + dir.x > 0);
  118. assert!(pos.x + dir.x < size.x);
  119. assert!(pos.y + dir.y > 0);
  120. assert!(pos.y + dir.y < size.y);
  121. }
  122. }
  123. }
  124. }
  125. #[cfg(test)]
  126. mod test {
  127. use crate::agent::{Move, NullAgent, Position, ScriptedAgent};
  128. use crate::world::ActualWorld;
  129. #[test]
  130. #[should_panic]
  131. fn empty() {
  132. ActualWorld::new(Position { x: 0, y: 0 }, vec![]);
  133. }
  134. #[test]
  135. fn trivial() {
  136. let mut world = ActualWorld::new(
  137. Position { x: 0, y: 0 },
  138. vec![(Position { x: 0, y: 0 }, Box::new(NullAgent))],
  139. );
  140. world.do_step();
  141. assert_eq!(world.state.tagged, 0);
  142. assert_eq!(world.state.tagged_by, None);
  143. }
  144. #[test]
  145. fn scripted_tagging() {
  146. let mut world = ActualWorld::new(
  147. Position { x: 0, y: 0 },
  148. vec![
  149. (
  150. Position { x: 0, y: 0 },
  151. Box::new(ScriptedAgent::new(vec![Move::Noop])),
  152. ),
  153. (
  154. Position { x: 0, y: 0 },
  155. Box::new(ScriptedAgent::new(vec![Move::TryTag(0)])),
  156. ),
  157. ],
  158. );
  159. world.do_step();
  160. assert_eq!(world.state.tagged, 0);
  161. assert_eq!(world.state.tagged_by, Some(1));
  162. }
  163. #[test]
  164. #[should_panic]
  165. fn move_out_of_bounds() {
  166. let mut world = ActualWorld::new(
  167. Position { x: 1, y: 1 },
  168. vec![(
  169. Position { x: 0, y: 0 },
  170. Box::new(ScriptedAgent::new(vec![Move::TryMove((-1, -1).into())])),
  171. )],
  172. );
  173. world.do_step();
  174. }
  175. }