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.

136 lines
3.5 KiB

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::{Entry, HashMap};
  3. pub struct ActualWorld {
  4. size: Position,
  5. pub agent_positions: HashMap<AgentId, Position>,
  6. pub agents: HashMap<AgentId, Box<dyn Agent>>,
  7. pub tagged: AgentId,
  8. pub tagged_by: Option<AgentId>,
  9. }
  10. impl ActualWorld {
  11. /// Agents receive incrementing ids starting with 0
  12. /// The last agent is 'It'
  13. pub fn new(size: Position, input_agents: Vec<(Position, Box<dyn Agent>)>) -> Self {
  14. let agent_count = input_agents.len();
  15. assert!(agent_count > 0);
  16. let mut id = 0;
  17. let mut agent_positions = HashMap::with_capacity(agent_count);
  18. let mut agents = HashMap::with_capacity(agent_count);
  19. for (pos, agent) in input_agents.into_iter() {
  20. agent_positions.insert(id, pos);
  21. agents.insert(id, agent);
  22. id += 1;
  23. }
  24. Self {
  25. size,
  26. tagged_by: None,
  27. tagged: id - 1,
  28. agents,
  29. agent_positions,
  30. }
  31. }
  32. pub fn do_step(&mut self) {
  33. let moves: Vec<_> = self
  34. .agents
  35. .iter()
  36. .map(|(id, agent)| {
  37. let pos = self.agent_positions.get(id).unwrap();
  38. (
  39. *id,
  40. agent.next_move(WorldView {
  41. self_id: *id,
  42. tagged_by: self.tagged_by,
  43. tagged: self.tagged,
  44. bounds_distance: (pos.y, self.size.x - pos.x, self.size.y - pos.y, pos.x),
  45. other_agents: self
  46. .agent_positions
  47. .iter()
  48. .filter(|(other_id, _)| id != *other_id)
  49. .map(|(id, other_pos)| {
  50. (
  51. Direction {
  52. x: other_pos.x - pos.x,
  53. y: other_pos.y - pos.y,
  54. },
  55. *id,
  56. )
  57. })
  58. .collect(),
  59. }),
  60. )
  61. })
  62. .collect();
  63. for (id, mv) in moves {
  64. match mv {
  65. Move::Noop => {}
  66. Move::TryTag(other_id) => {
  67. // FIXME check distance
  68. assert_eq!(self.tagged, id);
  69. assert_ne!(self.tagged_by, Some(other_id));
  70. assert!(
  71. self.agents.contains_key(&other_id),
  72. "Trying to tag a nonexisting agent"
  73. );
  74. self.tagged = other_id;
  75. self.tagged_by = Some(id);
  76. }
  77. Move::TryMove(dir) => {
  78. // FIXME check out of bounds
  79. // FIXME check speed
  80. let entry = self.agent_positions.entry(id);
  81. assert!(matches!(entry, Entry::Occupied(_)));
  82. entry.and_modify(|e| {
  83. e.x += dir.x;
  84. e.y += dir.y;
  85. });
  86. }
  87. }
  88. }
  89. }
  90. }
  91. #[cfg(test)]
  92. mod test {
  93. use crate::agent::{Move, NullAgent, Position, ScriptedAgent};
  94. use crate::world::ActualWorld;
  95. #[test]
  96. #[should_panic]
  97. fn empty() {
  98. ActualWorld::new(Position { x: 0, y: 0 }, vec![]);
  99. }
  100. #[test]
  101. fn trivial() {
  102. let mut world = ActualWorld::new(
  103. Position { x: 0, y: 0 },
  104. vec![(Position { x: 0, y: 0 }, Box::new(NullAgent))],
  105. );
  106. world.do_step();
  107. assert_eq!(world.tagged, 0);
  108. assert_eq!(world.tagged_by, None);
  109. }
  110. #[test]
  111. fn scripted_tagging() {
  112. let mut world = ActualWorld::new(
  113. Position { x: 0, y: 0 },
  114. vec![
  115. (
  116. Position { x: 0, y: 0 },
  117. Box::new(ScriptedAgent::new(vec![Move::Noop])),
  118. ),
  119. (
  120. Position { x: 0, y: 0 },
  121. Box::new(ScriptedAgent::new(vec![Move::TryTag(0)])),
  122. ),
  123. ],
  124. );
  125. world.do_step();
  126. assert_eq!(world.tagged, 0);
  127. assert_eq!(world.tagged_by, Some(1));
  128. }
  129. }