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.

153 lines
3.9 KiB

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<AgentId, Position>,
pub agents: HashMap<AgentId, Box<dyn Agent>>,
pub tagged: AgentId,
pub tagged_by: Option<AgentId>,
}
impl ActualWorld {
/// Agents receive incrementing ids starting with 0
/// The last agent is 'It'
pub fn new(size: Position, input_agents: Vec<(Position, Box<dyn Agent>)>) -> 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();
}
}