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.

156 lines
3.2 KiB

pub type Distance = isize;
#[derive(Clone)]
pub struct Position {
pub x: Distance,
pub y: Distance,
}
impl From<(Distance, Distance)> for Position {
fn from((x, y): (Distance, Distance)) -> Self {
Self { x, y }
}
}
#[derive(Debug)]
pub struct Direction {
pub x: Distance,
pub y: Distance,
}
impl From<(Distance, Distance)> for Direction {
fn from((x, y): (Distance, Distance)) -> Self {
Self { x, y }
}
}
pub type AgentId = usize;
pub struct WorldView<'o> {
pub self_id: AgentId,
pub tagged: AgentId,
pub tagged_by: Option<AgentId>,
pub bounds_distance: (Distance, Distance, Distance, Distance),
pub other_agents: &'o mut dyn Iterator<Item = (Direction, AgentId)>,
}
#[derive(Debug)]
pub enum Move {
Noop, // Why not an Option?
TryTag(AgentId),
TryMove(Direction),
}
pub trait Agent {
fn next_move(&self, view: WorldView) -> Move;
}
pub struct NullAgent;
impl Agent for NullAgent {
fn next_move(&self, _view: WorldView) -> Move {
Move::Noop
}
}
use std::cell::RefCell;
pub struct ScriptedAgent {
moves: RefCell<Vec<Move>>,
}
impl ScriptedAgent {
pub fn new(mut moves: Vec<Move>) -> Self {
moves.reverse();
Self {
moves: RefCell::new(moves),
}
}
}
impl Agent for ScriptedAgent {
fn next_move(&self, _view: WorldView) -> Move {
self
.moves
.borrow_mut()
.pop()
.expect("ScriptedAgent has no moves left to make")
}
}
pub struct SimpleAgent;
fn rand(exclusive_upper_bound: u32) -> u32 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.subsec_micros()
% exclusive_upper_bound
}
fn random_move() -> Move {
Move::TryMove(
match rand(8) {
0 => (-1, -1),
1 => (0, -1),
2 => (1, -1),
3 => (1, 0),
4 => (1, 1),
5 => (0, 1),
6 => (-1, 1),
7 => (-1, 0),
_ => unreachable!(),
}
.into(),
)
}
fn random_move_within(
(top, right, bottom, left): (Distance, Distance, Distance, Distance),
) -> Move {
loop {
let mv = random_move();
if let Move::TryMove(Direction { x, y }) = mv {
if top + y >= 0 && bottom - y >= 0 && left + x >= 0 && right - x >= 0 {
return mv;
}
}
}
}
impl Agent for SimpleAgent {
fn next_move(&self, view: WorldView) -> Move {
if view.self_id == view.tagged {
let mut cur = None;
for (dist, id) in view.other_agents {
if Some(id) == view.tagged_by {
continue;
}
if dist.x.abs() <= 1 && dist.y.abs() <= 1 {
cur = Some((id, 0, dist));
break;
}
let total_dist = dist.x.abs() + dist.y.abs();
let cur_dist = if let Some((_, dist, _)) = cur {
dist
} else {
isize::MAX
};
if total_dist < cur_dist {
cur = Some((id, total_dist, dist));
}
}
match cur {
Some((other, total_dist, dist)) => {
if total_dist == 0 {
Move::TryTag(other)
} else {
Move::TryMove((dist.x.signum(), dist.y.signum()).into())
}
}
None => random_move_within(view.bounds_distance),
}
} else {
random_move_within(view.bounds_distance)
}
}
}