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.

192 lines
5.0 KiB

use crate::agent::{Agent, AgentId, Direction, Move, Position, WorldView};
use std::collections::hash_map::HashMap;
pub struct ActualWorld {
size: Position,
pub agents: HashMap<AgentId, Box<dyn Agent>>,
pub state: WorldState,
validating: bool,
}
pub struct WorldState {
pub agent_positions: HashMap<AgentId, Position>,
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>)>,
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: &mut 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();
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();
}
}