simple_grid/
index.rs

1/// A struct used for indexing into a grid.
2#[derive(Debug, Copy, Clone, PartialEq, Hash, Eq)]
3pub struct GridIndex(usize, usize);
4
5impl GridIndex {
6    /// Construct a new GridIndex.
7    pub fn new(column: usize, row: usize) -> Self {
8        Self(column, row)
9    }
10
11    /// Get the column (x) index.
12    pub fn column(&self) -> usize {
13        self.0
14    }
15
16    /// Get the row (y) index.
17    pub fn row(&self) -> usize {
18        self.1
19    }
20
21    /// Returns an iterator over the cardinal and ordinal neighbors of `self`.
22    ///
23    /// Returns the neighbors in clockwise order: `[up, up_right, right, down_right, down, down_left, left, up_left]`.
24    ///
25    /// ## Example
26    /// ```rust
27    /// # use simple_grid::GridIndex;
28    /// let idx = GridIndex::new(0, 1);
29    /// let neighbors: Vec<_> = idx.neighbors().collect();
30    /// assert_eq!(neighbors, vec![
31    ///     (0, 0).into(), // up
32    ///     (1, 0).into(), // up_right
33    ///     (1, 1).into(), // right
34    ///     (1, 2).into(), // down_right
35    ///     (0, 2).into(), // down
36    ///                    // nothing to the left since `idx` has column=0
37    /// ]);
38    /// ```
39    pub fn neighbors(self) -> impl Iterator<Item = Self> {
40        use std::iter::once;
41        once::<fn(&Self) -> Option<Self>>(Self::up as _)
42            .chain(once(Self::up_right as _))
43            .chain(once(Self::right as _))
44            .chain(once(Self::down_right as _))
45            .chain(once(Self::down as _))
46            .chain(once(Self::down_left as _))
47            .chain(once(Self::left as _))
48            .chain(once(Self::up_left as _))
49            .filter_map(move |f| f(&self))
50    }
51
52    /// Returns an iterator over the cardinal neighbors of `self`.
53    ///
54    /// Returns the neighbors in clockwise order: `[up, right, down, left]`.
55    ///
56    /// ## Example
57    /// ```rust
58    /// # use simple_grid::GridIndex;
59    /// let idx = GridIndex::new(0, 1);
60    /// let neighbors: Vec<_> = idx.cardinal_neighbors().collect();
61    /// assert_eq!(neighbors, vec![
62    ///     (0, 0).into(), // up
63    ///     (1, 1).into(), // right
64    ///     (0, 2).into(), // down
65    ///                    // nothing to the left since `idx` has column=0
66    /// ]);
67    /// ```
68    pub fn cardinal_neighbors(self) -> impl Iterator<Item = Self> {
69        use std::iter::once;
70        once::<fn(&Self) -> Option<Self>>(Self::up as _)
71            .chain(once(Self::right as _))
72            .chain(once(Self::down as _))
73            .chain(once(Self::left as _))
74            .filter_map(move |f| f(&self))
75    }
76
77    /// Get the `GridIndex` above, if it exists.
78    ///
79    /// ## Example
80    /// ```rust
81    /// # use simple_grid::GridIndex;
82    /// let row_5 = GridIndex::new(17, 5);
83    /// assert_eq!(row_5.up(), Some(GridIndex::new(17, 4)));
84    /// let row_0 = GridIndex::new(38, 0);
85    /// assert_eq!(row_0.up(), None);
86    /// ```
87    pub fn up(&self) -> Option<Self> {
88        if self.row() > 0 {
89            Some(Self::new(self.column(), self.row() - 1))
90        } else {
91            None
92        }
93    }
94
95    /// Get the `GridIndex` to the right, if it exists.
96    ///
97    /// ## Example
98    /// ```rust
99    /// # use simple_grid::GridIndex;
100    /// let column_17 = GridIndex::new(17, 11);
101    /// assert_eq!(column_17.right(), Some(GridIndex::new(18, 11)));
102    /// ```
103    pub fn right(&self) -> Option<Self> {
104        self.column()
105            .checked_add(1)
106            .map(|right| Self::new(right, self.row()))
107    }
108
109    /// Get the `GridIndex` below, if it exists.
110    ///
111    /// ## Example
112    /// ```rust
113    /// # use simple_grid::GridIndex;
114    /// let row_15 = GridIndex::new(3, 15);
115    /// assert_eq!(row_15.down(), Some(GridIndex::new(3, 16)));
116    /// ```
117    pub fn down(&self) -> Option<Self> {
118        self.row()
119            .checked_add(1)
120            .map(|down| Self::new(self.column(), down))
121    }
122
123    /// Get the `GridIndex` to the left, if it exists.
124    /// 0).
125    ///
126    /// ## Example
127    /// ```rust
128    /// # use simple_grid::GridIndex;
129    /// let column_9 = GridIndex::new(9, 10);
130    /// assert_eq!(column_9.left(), Some(GridIndex::new(8, 10)));
131    /// let column_0 = GridIndex::new(0, 10);
132    /// assert_eq!(column_0.left(), None);
133    /// ```
134    pub fn left(&self) -> Option<Self> {
135        if self.column() > 0 {
136            Some(Self::new(self.column() - 1, self.row()))
137        } else {
138            None
139        }
140    }
141
142    /// Get the `GridIndex` above and to the left, if it exists.
143    ///
144    /// ## Example
145    /// ```rust
146    /// # use simple_grid::{Grid, GridIndex};
147    /// let column_5_row_4 = GridIndex::new(5, 4);
148    /// assert_eq!(column_5_row_4.up_left(), Some(GridIndex::new(4, 3)));
149    /// let column_0_row_4 = GridIndex::new(0, 4);
150    /// assert_eq!(column_0_row_4.up_left(), None);
151    /// ```
152    pub fn up_left(&self) -> Option<Self> {
153        self.up().and_then(|up| up.left())
154    }
155
156    /// Get the `GridIndex` above and to the right, if it exists.
157    ///
158    /// ## Example
159    /// ```rust
160    /// # use simple_grid::{Grid, GridIndex};
161    /// let column_5_row_4 = GridIndex::new(5, 4);
162    /// assert_eq!(column_5_row_4.up_right(), Some(GridIndex::new(6, 3)));
163    /// let column_5_row_0 = GridIndex::new(5, 0);
164    /// assert_eq!(column_5_row_0.up_right(), None);
165    /// ```
166    pub fn up_right(&self) -> Option<Self> {
167        if self.row() > 0 {
168            Some(Self::new(self.column() + 1, self.row() - 1))
169        } else {
170            None
171        }
172    }
173
174    /// Get the `GridIndex` below and to the right, if it exists.
175    ///
176    /// ## Example
177    /// ```rust
178    /// # use simple_grid::{Grid, GridIndex};
179    /// let column_5_row_4 = GridIndex::new(5, 4);
180    /// assert_eq!(column_5_row_4.down_right(), Some(GridIndex::new(6, 5)));
181    /// ```
182    pub fn down_right(&self) -> Option<Self> {
183        if let (Some(right), Some(down)) = (self.column().checked_add(1), self.row().checked_add(1))
184        {
185            Some(Self::new(right, down))
186        } else {
187            None
188        }
189    }
190
191    /// Get the `GridIndex` below and to the left, if it exists.
192    ///
193    /// ## Example
194    /// ```rust
195    /// # use simple_grid::{Grid, GridIndex};
196    /// let column_5_row_4 = GridIndex::new(5, 4);
197    /// assert_eq!(column_5_row_4.down_left(), Some(GridIndex::new(4, 5)));
198    /// let column_0_row_0 = GridIndex::new(0, 0);
199    /// assert_eq!(column_0_row_0.down_left(), None);
200    /// ```
201    pub fn down_left(&self) -> Option<Self> {
202        if self.column() > 0 {
203            Some(Self::new(self.column() - 1, self.row() + 1))
204        } else {
205            None
206        }
207    }
208
209    /// Convert this GridIndex into a linear index in a Grid of the given width.
210    ///
211    /// ## Panics
212    /// * If `self.column() >= width`
213    pub(crate) fn to_linear_idx_in(self, width: usize) -> usize {
214        if self.column() >= width {
215            panic!(
216                "can't convert {:?} to a linear index in a Grid of width {}",
217                self, width
218            );
219        }
220        self.row() * width + self.column()
221    }
222}
223
224impl From<(usize, usize)> for GridIndex {
225    fn from((column, row): (usize, usize)) -> Self {
226        GridIndex::new(column, row)
227    }
228}
229
230impl std::fmt::Display for GridIndex {
231    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
232        write!(f, "({},{})", self.column(), self.row())
233    }
234}
235
236#[derive(Debug, Copy, Clone)]
237pub(crate) enum LinearIndexError {
238    RowTooHigh,
239    ColumnTooHigh,
240}
241
242impl std::fmt::Display for LinearIndexError {
243    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
244        let output = match self {
245            LinearIndexError::RowTooHigh => "row index is too high",
246            LinearIndexError::ColumnTooHigh => "column index is too high",
247        };
248
249        write!(f, "{}", output)
250    }
251}
252
253#[cfg(test)]
254mod tests {
255    use super::*;
256
257    #[test]
258    fn to_linear_idx_in_test() {
259        let index = GridIndex::new(2, 3);
260        let linear = index.to_linear_idx_in(7);
261        assert_eq!(linear, 23);
262    }
263}