1use alloc::vec;
5use alloc::vec::Vec;
6
7use core::fmt;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(
14 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
15)]
16#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
17pub struct Pos {
18 pub x: i32,
20 pub y: i32,
22}
23
24#[derive(
28 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Serialize, Deserialize,
29)]
30#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
31pub struct TerrainId(pub u16);
32
33impl Pos {
34 pub const fn new(x: i32, y: i32) -> Self {
36 Self { x, y }
37 }
38
39 pub fn manhattan(a: Pos, b: Pos) -> u32 {
41 a.x.abs_diff(b.x) + a.y.abs_diff(b.y)
42 }
43
44 #[must_use]
46 pub fn step(self, dir: Dir) -> Pos {
47 let (dx, dy) = dir.delta();
48 Pos::new(self.x + dx, self.y + dy)
49 }
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
54#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
55pub enum Dir {
56 Right,
58 Down,
60 Left,
62 Up,
64}
65
66impl Dir {
67 pub const ALL: [Dir; 4] = [Dir::Right, Dir::Down, Dir::Left, Dir::Up];
69
70 pub const fn delta(self) -> (i32, i32) {
72 match self {
73 Dir::Right => (1, 0),
74 Dir::Down => (0, 1),
75 Dir::Left => (-1, 0),
76 Dir::Up => (0, -1),
77 }
78 }
79}
80
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
85#[serde(try_from = "RawGrid<T>")]
86pub struct Grid<T> {
87 width: u16,
88 height: u16,
89 cells: Vec<T>,
90}
91
92impl<T> Grid<T> {
93 pub fn from_cells(width: u16, height: u16, cells: Vec<T>) -> Option<Self> {
96 (cells.len() == usize::from(width) * usize::from(height)).then_some(Self {
97 width,
98 height,
99 cells,
100 })
101 }
102
103 pub fn filled(width: u16, height: u16, value: T) -> Self
105 where
106 T: Clone,
107 {
108 Self {
109 width,
110 height,
111 cells: vec![value; usize::from(width) * usize::from(height)],
112 }
113 }
114
115 pub fn width(&self) -> u16 {
117 self.width
118 }
119
120 pub fn height(&self) -> u16 {
122 self.height
123 }
124
125 pub fn cells(&self) -> &[T] {
127 &self.cells
128 }
129
130 pub fn in_bounds(&self, pos: Pos) -> bool {
132 pos.x >= 0 && pos.y >= 0 && pos.x < i32::from(self.width) && pos.y < i32::from(self.height)
133 }
134
135 pub fn get(&self, pos: Pos) -> Option<&T> {
137 self.cells.get(self.index(pos)?)
138 }
139
140 pub fn get_mut(&mut self, pos: Pos) -> Option<&mut T> {
142 let i = self.index(pos)?;
143 self.cells.get_mut(i)
144 }
145
146 fn index(&self, pos: Pos) -> Option<usize> {
148 if !self.in_bounds(pos) {
149 return None;
150 }
151 let x = usize::try_from(pos.x).ok()?;
153 let y = usize::try_from(pos.y).ok()?;
154 Some(y * usize::from(self.width) + x)
155 }
156
157 pub fn neighbors4(&self, pos: Pos) -> impl Iterator<Item = Pos> + '_ {
159 Dir::ALL
160 .into_iter()
161 .map(move |d| pos.step(d))
162 .filter(|&p| self.in_bounds(p))
163 }
164
165 pub fn positions(&self) -> impl Iterator<Item = Pos> + use<T> {
167 let (w, h) = (i32::from(self.width), i32::from(self.height));
168 (0..h).flat_map(move |y| (0..w).map(move |x| Pos::new(x, y)))
169 }
170}
171
172#[derive(Deserialize)]
174#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
175struct RawGrid<T> {
176 width: u16,
177 height: u16,
178 cells: Vec<T>,
179}
180
181#[derive(Debug, Clone, Copy, PartialEq, Eq)]
183pub struct GridSizeError;
184
185impl fmt::Display for GridSizeError {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 f.write_str("the grid's cell count is not width × height")
188 }
189}
190
191impl<T> TryFrom<RawGrid<T>> for Grid<T> {
192 type Error = GridSizeError;
193
194 fn try_from(raw: RawGrid<T>) -> Result<Self, GridSizeError> {
195 Grid::from_cells(raw.width, raw.height, raw.cells).ok_or(GridSizeError)
196 }
197}
198
199#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct TileSet {
203 width: u16,
204 height: u16,
205 words: Vec<u64>,
206}
207
208impl TileSet {
209 pub fn new(width: u16, height: u16) -> Self {
211 let bits = usize::from(width) * usize::from(height);
212 Self {
213 width,
214 height,
215 words: vec![0; bits.div_ceil(64)],
216 }
217 }
218
219 pub fn width(&self) -> u16 {
221 self.width
222 }
223
224 pub fn height(&self) -> u16 {
226 self.height
227 }
228
229 pub fn contains(&self, pos: Pos) -> bool {
231 self.bit(pos)
232 .is_some_and(|(w, mask)| self.words.get(w).is_some_and(|word| word & mask != 0))
233 }
234
235 pub fn insert(&mut self, pos: Pos) -> bool {
238 let Some((w, mask)) = self.bit(pos) else {
239 return false;
240 };
241 let Some(word) = self.words.get_mut(w) else {
242 return false;
243 };
244 let added = *word & mask == 0;
245 *word |= mask;
246 added
247 }
248
249 pub fn union_with(&mut self, other: &TileSet) {
251 for pos in other.iter() {
252 self.insert(pos);
253 }
254 }
255
256 pub fn len(&self) -> usize {
258 self.words.iter().map(|w| w.count_ones() as usize).sum()
259 }
260
261 pub fn is_empty(&self) -> bool {
263 self.words.iter().all(|&w| w == 0)
264 }
265
266 pub fn iter(&self) -> impl Iterator<Item = Pos> + '_ {
268 let width = usize::from(self.width);
269 self.words
270 .iter()
271 .enumerate()
272 .filter(|&(_, &word)| word != 0)
273 .flat_map(move |(w, &word)| {
274 (0..64)
275 .filter(move |bit| word >> bit & 1 == 1)
276 .filter_map(move |bit| {
277 let index = w * 64 + bit;
278 let x = i32::try_from(index % width).ok()?;
280 let y = i32::try_from(index / width).ok()?;
281 Some(Pos::new(x, y))
282 })
283 })
284 }
285
286 fn bit(&self, pos: Pos) -> Option<(usize, u64)> {
288 let x = u16::try_from(pos.x).ok().filter(|&x| x < self.width)?;
289 let y = u16::try_from(pos.y).ok().filter(|&y| y < self.height)?;
290 let index = usize::from(y) * usize::from(self.width) + usize::from(x);
291 Some((index / 64, 1 << (index % 64)))
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use alloc::collections::BTreeSet;
298 use alloc::string::ToString;
299
300 use proptest::prelude::*;
301
302 use super::*;
303
304 fn grid(w: u16, h: u16) -> Grid<u32> {
305 let n = u32::from(w) * u32::from(h);
306 Grid::from_cells(w, h, (0..n).collect()).unwrap_or_else(|| unreachable!())
307 }
308
309 fn p(x: i32, y: i32) -> Pos {
310 Pos::new(x, y)
311 }
312
313 #[test]
314 fn manhattan_distance() {
315 assert_eq!(Pos::manhattan(Pos::new(0, 0), Pos::new(3, 4)), 7);
316 assert_eq!(Pos::manhattan(Pos::new(3, 4), Pos::new(0, 0)), 7);
317 assert_eq!(Pos::manhattan(Pos::new(-2, 5), Pos::new(1, 1)), 7);
318 assert_eq!(Pos::manhattan(Pos::new(2, 2), Pos::new(2, 2)), 0);
319 }
320
321 #[test]
322 fn step_follows_dir_delta() {
323 let p = Pos::new(5, 5);
324 assert_eq!(p.step(Dir::Right), Pos::new(6, 5));
325 assert_eq!(p.step(Dir::Down), Pos::new(5, 6));
326 assert_eq!(p.step(Dir::Left), Pos::new(4, 5));
327 assert_eq!(p.step(Dir::Up), Pos::new(5, 4));
328 }
329
330 #[test]
331 fn from_cells_checks_count() {
332 assert!(Grid::from_cells(2, 3, vec![0; 6]).is_some());
333 assert!(Grid::from_cells(2, 3, vec![0; 5]).is_none());
334 assert!(Grid::from_cells(2, 3, vec![0; 7]).is_none());
335 let g = Grid::from_cells(0, 0, Vec::<u8>::new());
336 assert!(g.is_some_and(|g| !g.in_bounds(Pos::new(0, 0))));
337 }
338
339 #[test]
340 fn dimensions_and_cells() {
341 let g = grid(3, 2);
342 assert_eq!((g.width(), g.height()), (3, 2));
343 assert_eq!(g.cells(), &[0, 1, 2, 3, 4, 5]);
344 }
345
346 #[test]
347 fn bounds_and_get() {
348 let g = grid(3, 2);
349 assert!(g.in_bounds(Pos::new(0, 0)));
350 assert!(g.in_bounds(Pos::new(2, 1)));
351 assert!(!g.in_bounds(Pos::new(3, 0)));
352 assert!(!g.in_bounds(Pos::new(0, 2)));
353 assert!(!g.in_bounds(Pos::new(-1, 0)));
354 assert!(!g.in_bounds(Pos::new(0, -1)));
355 assert_eq!(g.get(Pos::new(0, 0)), Some(&0));
356 assert_eq!(g.get(Pos::new(2, 0)), Some(&2));
357 assert_eq!(g.get(Pos::new(1, 1)), Some(&4));
358 assert_eq!(g.get(Pos::new(2, 1)), Some(&5));
359 assert_eq!(g.get(Pos::new(3, 0)), None);
360 assert_eq!(g.get(Pos::new(-1, 1)), None);
361 }
362
363 #[test]
364 fn filled_and_get_mut() {
365 let mut g = Grid::filled(3, 2, 7u8);
366 assert_eq!((g.width(), g.height()), (3, 2));
367 assert_eq!(g.cells(), &[7; 6]);
368 if let Some(c) = g.get_mut(Pos::new(2, 1)) {
369 *c = 9;
370 }
371 if let Some(c) = g.get_mut(Pos::new(1, 0)) {
372 *c = 5;
373 }
374 assert_eq!(g.cells(), &[7, 5, 7, 7, 7, 9]);
375 assert!(g.get_mut(Pos::new(3, 0)).is_none());
376 assert!(g.get_mut(Pos::new(0, -1)).is_none());
377 }
378
379 #[test]
380 fn neighbors_in_fixed_order() {
381 let g = grid(3, 3);
382 let n: Vec<Pos> = g.neighbors4(Pos::new(1, 1)).collect();
383 assert_eq!(
384 n,
385 [
386 Pos::new(2, 1),
387 Pos::new(1, 2),
388 Pos::new(0, 1),
389 Pos::new(1, 0)
390 ]
391 );
392 let corner: Vec<Pos> = g.neighbors4(Pos::new(0, 0)).collect();
393 assert_eq!(corner, [Pos::new(1, 0), Pos::new(0, 1)]);
394 let far: Vec<Pos> = g.neighbors4(Pos::new(2, 2)).collect();
395 assert_eq!(far, [Pos::new(1, 2), Pos::new(2, 1)]);
396 }
397
398 #[test]
399 fn positions_are_row_major() {
400 let g = grid(2, 2);
401 let p: Vec<Pos> = g.positions().collect();
402 assert_eq!(
403 p,
404 [
405 Pos::new(0, 0),
406 Pos::new(1, 0),
407 Pos::new(0, 1),
408 Pos::new(1, 1)
409 ]
410 );
411 for (pos, cell) in g.positions().zip(g.cells()) {
412 assert_eq!(g.get(pos), Some(cell));
413 }
414 }
415
416 #[test]
417 fn grid_serde_round_trips_and_checks_the_cell_count() {
418 let g = grid(3, 2);
419 let text = ron::to_string(&g).unwrap();
420 assert_eq!(text, "(width:3,height:2,cells:[0,1,2,3,4,5])");
421 assert_eq!(ron::from_str::<Grid<u32>>(&text).unwrap(), g);
422 let bad = ron::from_str::<Grid<u32>>("(width:3,height:2,cells:[0,1])");
423 assert!(bad.unwrap_err().to_string().contains("not width × height"));
424 assert_eq!(ron::from_str::<Pos>("(x:-1,y:4)").unwrap(), Pos::new(-1, 4));
425 }
426
427 #[test]
428 fn tile_set_basics() {
429 let mut set = TileSet::new(10, 10);
430 assert_eq!((set.width(), set.height()), (10, 10));
431 assert!(set.is_empty(), "{set:?}");
432 assert_eq!(set.len(), 0);
433 assert!(set.insert(p(4, 6)));
434 assert!(!set.insert(p(4, 6)));
435 assert!(set.insert(p(3, 6)));
436 assert!(set.insert(p(9, 9)));
437 assert!(set.insert(p(0, 0)));
438 for outside in [p(-1, 0), p(10, 0), p(0, 10), p(0, -1), p(10, 10)] {
439 assert!(!set.insert(outside));
440 assert!(!set.contains(outside));
441 }
442 assert!(!set.is_empty(), "{set:?}");
443 assert_eq!(set.len(), 4);
444 assert!(set.contains(p(4, 6)));
445 assert!(set.contains(p(3, 6)));
446 assert!(!set.contains(p(5, 6)));
447 assert!(!set.contains(p(6, 4)));
448 let members: Vec<Pos> = set.iter().collect();
450 assert_eq!(members, [p(0, 0), p(3, 6), p(4, 6), p(9, 9)]);
451 }
452
453 #[test]
454 fn tile_set_word_boundaries() {
455 let mut set = TileSet::new(3, 43);
457 let all: Vec<Pos> = (0..43).flat_map(|y| (0..3).map(move |x| p(x, y))).collect();
458 for &pos in &all {
459 assert!(set.insert(pos));
460 }
461 assert_eq!(set.len(), 129);
462 assert_eq!(set.iter().collect::<Vec<_>>(), all);
463 assert!(!set.contains(p(0, 43)));
464 let found = TileSet::new(0, 0);
465 assert!(found.is_empty(), "{found:?}");
466 assert_eq!(TileSet::new(0, 5).iter().count(), 0);
467 }
468
469 #[test]
470 fn tile_set_union() {
471 let mut a = TileSet::new(3, 2);
472 a.insert(p(0, 0));
473 let mut b = TileSet::new(4, 4);
474 b.insert(p(2, 1));
475 b.insert(p(3, 3)); b.insert(p(0, 0));
477 a.union_with(&b);
478 assert_eq!(a.iter().collect::<Vec<_>>(), [p(0, 0), p(2, 1)]);
479 assert_eq!(a.len(), 2);
480 }
481
482 proptest! {
483 #[test]
484 fn neighbors_stay_in_bounds(w in 0u16..20, h in 0u16..20, x in -3i32..25, y in -3i32..25) {
485 let g = grid(w, h);
486 let pos = Pos::new(x, y);
487 for n in g.neighbors4(pos) {
488 prop_assert!(g.in_bounds(n));
489 prop_assert_eq!(Pos::manhattan(pos, n), 1);
490 }
491 if g.in_bounds(pos) {
492 let expected = Dir::ALL.iter().filter(|&&d| g.in_bounds(pos.step(d))).count();
493 prop_assert_eq!(g.neighbors4(pos).count(), expected);
494 }
495 }
496
497 #[test]
498 fn tile_set_matches_a_btree_set(
499 w in 0u16..20,
500 h in 0u16..20,
501 ops in prop::collection::vec((-2i32..22, -2i32..22), 0..60),
502 ) {
503 let mut set = TileSet::new(w, h);
504 let mut model = BTreeSet::new();
505 for (x, y) in ops {
506 let pos = p(x, y);
507 let inside = x >= 0 && y >= 0 && x < i32::from(w) && y < i32::from(h);
508 let added = inside && model.insert((y, x));
509 prop_assert_eq!(set.insert(pos), added);
510 }
511 prop_assert_eq!(set.len(), model.len());
512 prop_assert_eq!(set.is_empty(), model.is_empty());
513 let members: Vec<(i32, i32)> = set.iter().map(|q| (q.y, q.x)).collect();
514 prop_assert_eq!(members, model.into_iter().collect::<Vec<_>>());
515 }
516 }
517}