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.

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