1use crate::{CanonicalWrite, StateHasher};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum GridError {
8 IncorrectCellCount,
10}
11
12#[derive(Clone, Debug, PartialEq, Eq)]
14pub struct Grid2<T> {
15 width: u16,
16 height: u16,
17 cells: Vec<T>,
18}
19
20impl<T> Grid2<T> {
21 pub fn from_cells(width: u16, height: u16, cells: Vec<T>) -> Result<Self, GridError> {
23 let expected = Self::cell_count(width, height);
24 if cells.len() != expected {
25 return Err(GridError::IncorrectCellCount);
26 }
27 Ok(Self {
28 width,
29 height,
30 cells,
31 })
32 }
33
34 #[must_use]
36 pub const fn width(&self) -> u16 {
37 self.width
38 }
39
40 #[must_use]
42 pub const fn height(&self) -> u16 {
43 self.height
44 }
45
46 #[must_use]
48 pub fn get(&self, x: u16, y: u16) -> Option<&T> {
49 self.cells.get(self.index(x, y)?)
50 }
51
52 #[must_use]
54 pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut T> {
55 let index = self.index(x, y)?;
56 self.cells.get_mut(index)
57 }
58
59 #[must_use]
61 pub fn row(&self, y: u16) -> Option<&[T]> {
62 let start = self.index(0, y)?;
63 self.cells
64 .get(start..start.checked_add(usize::from(self.width))?)
65 }
66
67 #[must_use]
69 pub fn cells(&self) -> &[T] {
70 &self.cells
71 }
72
73 fn cell_count(width: u16, height: u16) -> usize {
74 let count = u32::from(width)
75 .checked_mul(u32::from(height))
76 .expect("u16 grid dimensions have a representable product");
77 usize::try_from(count).expect("u16 grid dimensions fit usize")
78 }
79
80 fn index(&self, x: u16, y: u16) -> Option<usize> {
82 if y >= self.height || (x >= self.width && !(self.width == 0 && x == 0)) {
83 return None;
84 }
85 usize::try_from(u32::from(y) * u32::from(self.width) + u32::from(x)).ok()
86 }
87}
88
89impl<T: Clone> Grid2<T> {
90 #[must_use]
92 pub fn filled(width: u16, height: u16, value: T) -> Self {
93 Self {
94 width,
95 height,
96 cells: vec![value; Self::cell_count(width, height)],
97 }
98 }
99}
100
101impl<T: CanonicalWrite> CanonicalWrite for Grid2<T> {
102 fn canonical_write(&self, out: &mut StateHasher) {
103 out.u16(self.width);
104 out.u16(self.height);
105 out.sequence(&self.cells);
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112 use wasm_bindgen_test::wasm_bindgen_test;
113
114 #[wasm_bindgen_test(unsupported = test)]
116 fn dimensions_are_reported() {
117 let grid = Grid2::filled(3, 5, 0u8);
118 assert_eq!((grid.width(), grid.height()), (3, 5));
119 assert_eq!(grid.cells().len(), 15);
120 }
121
122 #[wasm_bindgen_test(unsupported = test)]
123 fn construction_and_access_preserve_row_major_invariants() {
124 let mut grid = Grid2::from_cells(3, 2, vec![0, 1, 2, 3, 4, 5]).expect("matching shape");
125 assert_eq!(grid.get(2, 1), Some(&5));
126 assert_eq!(grid.row(1), Some(&[3, 4, 5][..]));
127 *grid.get_mut(1, 0).expect("in bounds") = 9;
128 assert_eq!(grid.cells(), &[0, 9, 2, 3, 4, 5]);
129 assert_eq!(grid.get(3, 0), None);
130 assert_eq!(grid.get(0, 2), None);
131 assert_eq!(
132 Grid2::from_cells(2, 2, vec![1, 2, 3]),
133 Err(GridError::IncorrectCellCount)
134 );
135 }
136
137 #[wasm_bindgen_test(unsupported = test)]
138 fn dimensions_have_checked_widened_size_arithmetic() {
139 assert_eq!(Grid2::<()>::cell_count(u16::MAX, u16::MAX), 4_294_836_225);
140 }
141
142 #[wasm_bindgen_test(unsupported = test)]
143 fn zero_width_rows_are_valid_and_empty() {
144 let grid = Grid2::from_cells(0, 2, Vec::<u8>::new()).expect("zero-width shape");
145 assert_eq!(grid.row(0), Some(&[][..]));
146 assert_eq!(grid.row(1), Some(&[][..]));
147 assert_eq!(grid.row(2), None);
148 assert_eq!(grid.get(0, 0), None);
149 }
150}