#![allow(dead_code)]
use rand::Rng;
use rustsim_core::{
interaction::{PositionedAgent, SpaceInteraction},
space::Space,
types::AgentId,
};
use std::fmt;
#[derive(Debug, Default, Clone, Copy)]
pub struct NothingSpace;
impl Space for NothingSpace {}
impl<A> SpaceInteraction<A> for NothingSpace
where
A: PositionedAgent<Position = ()>,
{
type Error = std::convert::Infallible;
fn random_position<R: rand::RngCore>(&self, _rng: &mut R) -> A::Position {}
fn add_agent(&mut self, _agent: &A) -> Result<(), Self::Error> {
Ok(())
}
fn remove_agent(&mut self, _agent: &A) -> Result<(), Self::Error> {
Ok(())
}
fn nearby_ids(&self, _position: &A::Position, _radius: usize) -> Vec<AgentId> {
Vec::new()
}
}
pub type GridPos2 = (usize, usize);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GridError {
OutOfBounds(GridPos2),
}
impl fmt::Display for GridError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OutOfBounds((x, y)) => write!(formatter, "position ({x}, {y}) is out of bounds"),
}
}
}
#[derive(Debug, Clone)]
pub struct Grid2D {
width: usize,
height: usize,
periodic: bool,
cells: Vec<Vec<AgentId>>,
}
impl Grid2D {
pub fn new(width: usize, height: usize, periodic: bool) -> Self {
assert!(width > 0 && height > 0, "grid dimensions must be positive");
Self {
width,
height,
periodic,
cells: vec![Vec::new(); width * height],
}
}
fn normalize_pos(&self, pos: GridPos2) -> Option<GridPos2> {
if self.periodic {
Some((pos.0 % self.width, pos.1 % self.height))
} else if pos.0 < self.width && pos.1 < self.height {
Some(pos)
} else {
None
}
}
fn index(&self, pos: GridPos2) -> usize {
pos.1 * self.width + pos.0
}
}
impl Space for Grid2D {}
impl<A> SpaceInteraction<A> for Grid2D
where
A: PositionedAgent<Position = GridPos2>,
{
type Error = GridError;
fn random_position<R: rand::RngCore>(&self, rng: &mut R) -> A::Position {
(rng.gen_range(0..self.width), rng.gen_range(0..self.height))
}
fn add_agent(&mut self, agent: &A) -> Result<(), Self::Error> {
let pos = *agent.position();
let normalized = self.normalize_pos(pos).ok_or(GridError::OutOfBounds(pos))?;
let index = self.index(normalized);
self.cells[index].push(agent.id());
Ok(())
}
fn remove_agent(&mut self, agent: &A) -> Result<(), Self::Error> {
let pos = *agent.position();
let normalized = self.normalize_pos(pos).ok_or(GridError::OutOfBounds(pos))?;
let index = self.index(normalized);
if let Some(cell_index) = self.cells[index]
.iter()
.position(|candidate| *candidate == agent.id())
{
self.cells[index].swap_remove(cell_index);
}
Ok(())
}
fn nearby_ids(&self, position: &A::Position, radius: usize) -> Vec<AgentId> {
let mut ids = Vec::new();
let radius = radius as i32;
let (center_x, center_y) = (position.0 as i32, position.1 as i32);
for dy in -radius..=radius {
for dx in -radius..=radius {
let x = center_x + dx;
let y = center_y + dy;
let pos = if self.periodic {
let wrapped_x =
((x % self.width as i32) + self.width as i32) % self.width as i32;
let wrapped_y =
((y % self.height as i32) + self.height as i32) % self.height as i32;
Some((wrapped_x as usize, wrapped_y as usize))
} else if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
Some((x as usize, y as usize))
} else {
None
};
if let Some(pos) = pos {
ids.extend(self.cells[self.index(pos)].iter().copied());
}
}
}
ids
}
}