1use rand::Rng;
14use rustsim_core::{
15 interaction::{PositionedAgent, SpaceInteraction},
16 space::Space,
17 types::AgentId,
18};
19use thiserror::Error;
20
21pub type GridPos2 = (usize, usize);
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
26pub enum GridError {
27 #[error("position ({}, {}) is out of bounds", .0.0, .0.1)]
29 OutOfBounds(GridPos2),
30 #[error("position ({}, {}) is already occupied", .0.0, .0.1)]
32 Occupied(GridPos2),
33}
34
35#[derive(Debug, Clone)]
45pub struct Grid2D {
46 width: usize,
47 height: usize,
48 periodic: bool,
49 cells: Vec<Vec<AgentId>>,
50}
51
52impl Grid2D {
53 pub fn new(width: usize, height: usize, periodic: bool) -> Self {
59 assert!(width > 0 && height > 0, "grid dimensions must be positive");
60 let cells = vec![Vec::new(); width * height];
61 Self {
62 width,
63 height,
64 periodic,
65 cells,
66 }
67 }
68
69 pub fn dimensions(&self) -> (usize, usize) {
71 (self.width, self.height)
72 }
73
74 pub fn periodic(&self) -> bool {
76 self.periodic
77 }
78
79 pub fn is_in_bounds(&self, pos: GridPos2) -> bool {
81 pos.0 < self.width && pos.1 < self.height
82 }
83
84 pub fn is_empty(&self, pos: GridPos2) -> bool {
86 match self.normalize_pos(pos) {
87 Some(pos) => self.cells[self.index(pos)].is_empty(),
88 None => true,
89 }
90 }
91
92 pub fn ids_in_position(&self, pos: GridPos2) -> Option<&[AgentId]> {
94 let pos = self.normalize_pos(pos)?;
95 Some(&self.cells[self.index(pos)])
96 }
97
98 pub fn add_agent(&mut self, pos: GridPos2, id: AgentId) -> Result<(), GridError> {
100 let pos = self.normalize_pos(pos).ok_or(GridError::OutOfBounds(pos))?;
101 let index = self.index(pos);
102 self.cells[index].push(id);
103 Ok(())
104 }
105
106 pub fn remove_agent(&mut self, pos: GridPos2, id: AgentId) -> Result<bool, GridError> {
108 let pos = self.normalize_pos(pos).ok_or(GridError::OutOfBounds(pos))?;
109 let index = self.index(pos);
110 let cell = &mut self.cells[index];
111 if let Some(index) = cell.iter().position(|value| *value == id) {
112 cell.swap_remove(index);
113 Ok(true)
114 } else {
115 Ok(false)
116 }
117 }
118
119 fn normalize_pos(&self, pos: GridPos2) -> Option<GridPos2> {
120 if self.periodic {
121 Some((pos.0 % self.width, pos.1 % self.height))
122 } else if self.is_in_bounds(pos) {
123 Some(pos)
124 } else {
125 None
126 }
127 }
128
129 fn index(&self, pos: GridPos2) -> usize {
130 pos.1 * self.width + pos.0
131 }
132}
133
134impl Space for Grid2D {}
135
136impl<A> SpaceInteraction<A> for Grid2D
137where
138 A: PositionedAgent<Position = GridPos2>,
139{
140 type Error = GridError;
141
142 fn random_position<R: rand::RngCore>(&self, rng: &mut R) -> A::Position {
143 let x = rng.gen_range(0..self.width);
144 let y = rng.gen_range(0..self.height);
145 (x, y)
146 }
147
148 fn add_agent(&mut self, agent: &A) -> Result<(), Self::Error> {
149 self.add_agent(*agent.position(), agent.id())
150 }
151
152 fn remove_agent(&mut self, agent: &A) -> Result<(), Self::Error> {
153 self.remove_agent(*agent.position(), agent.id()).map(|_| ())
154 }
155
156 fn nearby_ids(&self, position: &A::Position, radius: usize) -> Vec<AgentId> {
157 let mut ids = Vec::new();
158 let radius = radius as i32;
159 let (cx, cy) = (position.0 as i32, position.1 as i32);
160 for dy in -radius..=radius {
161 for dx in -radius..=radius {
162 let x = cx + dx;
163 let y = cy + dy;
164 let pos = if self.periodic {
165 let px = ((x % self.width as i32) + self.width as i32) % self.width as i32;
166 let py = ((y % self.height as i32) + self.height as i32) % self.height as i32;
167 Some((px as usize, py as usize))
168 } else if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
169 Some((x as usize, y as usize))
170 } else {
171 None
172 };
173
174 if let Some(pos) = pos {
175 ids.extend(self.cells[self.index(pos)].iter().copied());
176 }
177 }
178 }
179 ids
180 }
181}
182
183#[derive(Debug, Clone)]
190pub struct Grid2DSingle {
191 width: usize,
192 height: usize,
193 periodic: bool,
194 cells: Vec<Option<AgentId>>,
195}
196
197impl Grid2DSingle {
198 pub fn new(width: usize, height: usize, periodic: bool) -> Self {
204 assert!(width > 0 && height > 0, "grid dimensions must be positive");
205 let cells = vec![None; width * height];
206 Self {
207 width,
208 height,
209 periodic,
210 cells,
211 }
212 }
213
214 pub fn dimensions(&self) -> (usize, usize) {
216 (self.width, self.height)
217 }
218
219 pub fn periodic(&self) -> bool {
221 self.periodic
222 }
223
224 pub fn is_in_bounds(&self, pos: GridPos2) -> bool {
226 pos.0 < self.width && pos.1 < self.height
227 }
228
229 pub fn is_empty(&self, pos: GridPos2) -> bool {
231 match self.normalize_pos(pos) {
232 Some(pos) => self.cells[self.index(pos)].is_none(),
233 None => true,
234 }
235 }
236
237 pub fn id_in_position(&self, pos: GridPos2) -> Option<AgentId> {
239 let pos = self.normalize_pos(pos)?;
240 self.cells[self.index(pos)]
241 }
242
243 pub fn add_agent(&mut self, pos: GridPos2, id: AgentId) -> Result<(), GridError> {
245 let pos = self.normalize_pos(pos).ok_or(GridError::OutOfBounds(pos))?;
246 let index = self.index(pos);
247 let cell = &mut self.cells[index];
248 if cell.is_some() {
249 return Err(GridError::Occupied(pos));
250 }
251 *cell = Some(id);
252 Ok(())
253 }
254
255 pub fn remove_agent(&mut self, pos: GridPos2, id: AgentId) -> Result<bool, GridError> {
257 let pos = self.normalize_pos(pos).ok_or(GridError::OutOfBounds(pos))?;
258 let index = self.index(pos);
259 let cell = &mut self.cells[index];
260 if cell == &Some(id) {
261 *cell = None;
262 Ok(true)
263 } else {
264 Ok(false)
265 }
266 }
267
268 fn normalize_pos(&self, pos: GridPos2) -> Option<GridPos2> {
269 if self.periodic {
270 Some((pos.0 % self.width, pos.1 % self.height))
271 } else if self.is_in_bounds(pos) {
272 Some(pos)
273 } else {
274 None
275 }
276 }
277
278 fn index(&self, pos: GridPos2) -> usize {
279 pos.1 * self.width + pos.0
280 }
281}
282
283impl Space for Grid2DSingle {}
284
285impl<A> SpaceInteraction<A> for Grid2DSingle
286where
287 A: PositionedAgent<Position = GridPos2>,
288{
289 type Error = GridError;
290
291 fn random_position<R: rand::RngCore>(&self, rng: &mut R) -> A::Position {
292 let x = rng.gen_range(0..self.width);
293 let y = rng.gen_range(0..self.height);
294 (x, y)
295 }
296
297 fn add_agent(&mut self, agent: &A) -> Result<(), Self::Error> {
298 self.add_agent(*agent.position(), agent.id())
299 }
300
301 fn remove_agent(&mut self, agent: &A) -> Result<(), Self::Error> {
302 self.remove_agent(*agent.position(), agent.id()).map(|_| ())
303 }
304
305 fn nearby_ids(&self, position: &A::Position, radius: usize) -> Vec<AgentId> {
306 let mut ids = Vec::new();
307 let radius = radius as i32;
308 let (cx, cy) = (position.0 as i32, position.1 as i32);
309 for dy in -radius..=radius {
310 for dx in -radius..=radius {
311 let x = cx + dx;
312 let y = cy + dy;
313 let pos = if self.periodic {
314 let px = ((x % self.width as i32) + self.width as i32) % self.width as i32;
315 let py = ((y % self.height as i32) + self.height as i32) % self.height as i32;
316 Some((px as usize, py as usize))
317 } else if x >= 0 && y >= 0 && x < self.width as i32 && y < self.height as i32 {
318 Some((x as usize, y as usize))
319 } else {
320 None
321 };
322
323 if let Some(pos) = pos {
324 if let Some(id) = self.cells[self.index(pos)] {
325 ids.push(id);
326 }
327 }
328 }
329 }
330 ids
331 }
332}