cogs_gamedev/grids/
coords.rs

1//! Integer-based coordinates.
2
3use super::{Direction4, Direction8};
4
5use itertools::Itertools;
6#[cfg(feature = "serde")]
7use serde::{Deserialize, Serialize};
8
9use std::{
10    convert::TryFrom,
11    convert::TryInto,
12    fmt::Display,
13    num::TryFromIntError,
14    ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign},
15};
16
17/// Unsigned-int coordinates
18#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
19#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
20pub struct Coord {
21    pub x: usize,
22    pub y: usize,
23}
24
25impl Coord {
26    /// Make a new coord.
27    pub fn new(x: usize, y: usize) -> Self {
28        Self { x, y }
29    }
30
31    /// Get this as an index into an array representing a 2d array.
32    ///
33    /// (AKA, `y * width + x`.)
34    pub fn to_2d_idx(self, width: usize) -> usize {
35        // what did you think i was kidding or something
36        self.y * width + self.x
37    }
38
39    /// Convert this into an ICoord.
40    pub fn to_icoord(self) -> ICoord {
41        self.into()
42    }
43
44    /// Get a list of this coordinate's orthagonal neighbors.
45    /// They are given in clockwise order starting with the neighbor to the north,
46    /// as if each of [`Direction4::DIRECTIONS`] had been added to them.
47    ///
48    /// If a neighbor is out of bounds, it is skipped in the output.
49    ///
50    /// There may be 2, 3, or 4 neighbors:
51    /// - 2 if this is at `(0, 0)`
52    /// - 3 if this is on an edge (`x` or `y` are 0)
53    /// - 4 otherwise.
54    ///
55    /// ```
56    /// # use cogs_gamedev::grids::{Coord, Direction4};
57    ///
58    /// assert_eq!(
59    ///     Coord::new(5, 7).neighbors4(),
60    ///     &[
61    ///         Coord::new(5, 6),
62    ///         Coord::new(6, 7),
63    ///         Coord::new(5, 8),
64    ///         Coord::new(4, 7),
65    ///     ]
66    /// );
67    ///
68    /// // May return fewer than 4 neighbors
69    /// assert_eq!(
70    ///     Coord::new(0, 5).neighbors4(),
71    ///     &[
72    ///         Coord::new(0, 4),
73    ///         Coord::new(1, 5),
74    ///         Coord::new(0, 6),
75    ///         // Skip (-1, 5) for being out of bounds
76    ///     ]
77    /// );
78    /// ```
79    ///
80    /// [`Direction4::DIRECTIONS`]: super::Direction4::DIRECTIONS
81    pub fn neighbors4(self) -> Vec<Coord> {
82        Direction4::DIRECTIONS
83            .iter()
84            .filter_map(|dir| {
85                let iself = self.to_icoord();
86                let ineighbor = iself + *dir;
87                ineighbor.to_coord() // conveniently already returns an option.
88            })
89            .collect_vec()
90    }
91
92    /// Get a list of this coordinate's orthagonal and diagonal neighbors.
93    /// They are given in clockwise order starting with the neighbor to the north,
94    /// as if each of [`Direction8::DIRECTIONS`] had been added to them.
95    ///
96    /// If a neighbor is out of bounds, it is skipped in the output.
97    ///
98    /// There may be 3, 5, or 8 neighbors:
99    /// - 3 if this is at `(0, 0)`
100    /// - 5 if this is on an edge (`x` or `y` are 0)
101    /// - 8 otherwise.
102    ///
103    /// ```
104    /// # use cogs_gamedev::grids::Coord;
105    /// # use cogs_gamedev::grids::Direction8;
106    ///
107    /// assert_eq!(
108    ///     Coord::new(5, 7).neighbors8(),
109    ///     [
110    ///         Coord::new(5, 6),
111    ///         Coord::new(6, 6),
112    ///         Coord::new(6, 7),
113    ///         Coord::new(6, 8),
114    ///         Coord::new(5, 8),
115    ///         Coord::new(4, 8),
116    ///         Coord::new(4, 7),
117    ///         Coord::new(4, 6),
118    ///     ]
119    /// );
120    ///
121    /// // May return fewer than 8 neighbors
122    /// assert_eq!(
123    ///     Coord::new(0, 5).neighbors8(),
124    ///     &[
125    ///         Coord::new(0, 4),
126    ///         Coord::new(1, 4),
127    ///         Coord::new(1, 5),
128    ///         Coord::new(1, 6),
129    ///         Coord::new(0, 6),
130    ///         // Skip (-1, 6) for being out of bounds
131    ///         // Skip (-1, 5)
132    ///         // Skip (-1, 4)
133    ///     ]
134    /// );
135    /// ```
136    ///
137    /// [`Direction8::DIRECTIONS`]: super::Direction8::DIRECTIONS
138    pub fn neighbors8(self) -> Vec<Coord> {
139        Direction8::DIRECTIONS
140            .iter()
141            .filter_map(|dir| {
142                let iself = self.to_icoord();
143                let ineighbor = iself + *dir;
144                ineighbor.to_coord() // conveniently already returns an option.
145            })
146            .collect_vec()
147    }
148}
149
150impl Add for Coord {
151    type Output = Self;
152    fn add(self, rhs: Self) -> Self::Output {
153        Self {
154            x: self.x + rhs.x,
155            y: self.y + rhs.y,
156        }
157    }
158}
159
160impl AddAssign for Coord {
161    fn add_assign(&mut self, rhs: Self) {
162        self.x += rhs.x;
163        self.y += rhs.y;
164    }
165}
166
167impl Sub for Coord {
168    type Output = Self;
169    fn sub(self, rhs: Self) -> Self::Output {
170        Self {
171            x: self.x - rhs.x,
172            y: self.y - rhs.y,
173        }
174    }
175}
176
177impl SubAssign for Coord {
178    fn sub_assign(&mut self, rhs: Self) {
179        self.x -= rhs.x;
180        self.y -= rhs.y;
181    }
182}
183
184impl Mul<usize> for Coord {
185    type Output = Self;
186    fn mul(self, rhs: usize) -> Self::Output {
187        Self {
188            x: self.x * rhs,
189            y: self.y * rhs,
190        }
191    }
192}
193
194impl MulAssign<usize> for Coord {
195    fn mul_assign(&mut self, rhs: usize) {
196        self.x *= rhs;
197        self.y *= rhs;
198    }
199}
200
201/// Try to convert an ICoord to a Coord.
202/// Will return Error if the ICoord has any negatives in it.
203impl TryFrom<ICoord> for Coord {
204    type Error = TryFromIntError;
205    fn try_from(value: ICoord) -> Result<Self, Self::Error> {
206        Ok(Self {
207            x: value.x.try_into()?,
208            y: value.y.try_into()?,
209        })
210    }
211}
212
213impl Display for Coord {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        write!(f, "({}, {})", self.x, self.y)
216    }
217}
218
219/// Signed-int coordinates
220#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
221#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
222pub struct ICoord {
223    pub x: isize,
224    pub y: isize,
225}
226
227impl ICoord {
228    /// Create a new ICoord
229    pub fn new(x: isize, y: isize) -> Self {
230        Self { x, y }
231    }
232
233    /// Return the quadrant this coordinate is in.
234    ///
235    /// - 1: +X, +Y
236    /// - 2: -X, +Y
237    /// - 3: -X, -Y
238    /// - 4: +X, -Y
239    ///
240    /// Zeroes are treated as positive.
241    ///
242    /// ```
243    /// # use cogs_gamedev::grids::ICoord;
244    /// assert_eq!(ICoord::new(4, 5).quadrant(), 1);
245    /// assert_eq!(ICoord::new(-3, -2).quadrant(), 3);
246    /// // Zero is treated as positive
247    /// assert_eq!(ICoord::new(0, -8).quadrant(), 4);
248    /// assert_eq!(ICoord::new(0, 0).quadrant(), 1);
249    /// ```
250    pub fn quadrant(self) -> usize {
251        match (self.x >= 0, self.y >= 0) {
252            (true, true) => 1,
253            (false, true) => 2,
254            (false, false) => 3,
255            (true, false) => 4,
256        }
257    }
258
259    /// Try to convert this to a Coord.
260    /// Returns `None` in case any part is negative.
261    pub fn to_coord(self) -> Option<Coord> {
262        self.try_into().ok()
263    }
264
265    /// Get a list of this coordinate's orthagonal neighbors.
266    /// They are given in clockwise order starting with the neighbor to the north,
267    /// as if each of [`Direction4::DIRECTIONS`] had been added to them.
268    ///
269    /// ```
270    /// # use cogs_gamedev::grids::ICoord;
271    /// # use cogs_gamedev::grids::Direction4;
272    ///
273    /// assert_eq!(
274    ///     ICoord::new(5, 7).neighbors4(),
275    ///     [
276    ///         ICoord::new(5, 6),
277    ///         ICoord::new(6, 7),
278    ///         ICoord::new(5, 8),
279    ///         ICoord::new(4, 7),
280    ///     ]
281    /// );
282    ///
283    /// let origin = ICoord::new(-7, -12);
284    /// assert_eq!(
285    ///     origin.neighbors4()[..],
286    ///     Direction4::DIRECTIONS.iter().map(|dir| origin + *dir).collect::<Vec<_>>()[..],
287    /// );
288    /// ```
289    ///
290    /// [`Direction4::DIRECTIONS`]: super::Direction4::DIRECTIONS
291    pub fn neighbors4(self) -> [ICoord; 4] {
292        [
293            self + Direction4::North,
294            self + Direction4::East,
295            self + Direction4::South,
296            self + Direction4::West,
297        ]
298    }
299
300    /// Get a list of this coordinate's orthagonal and diagonal neighbors.
301    /// They are given in clockwise order starting with the neighbor to the north,
302    /// as if each of [`Direction8::DIRECTIONS`] had been added to them.
303    ///
304    /// ```
305    /// # use cogs_gamedev::grids::ICoord;
306    /// # use cogs_gamedev::grids::Direction8;
307    ///
308    /// assert_eq!(
309    ///     ICoord::new(5, 7).neighbors8(),
310    ///     [
311    ///         ICoord::new(5, 6),
312    ///         ICoord::new(6, 6),
313    ///         ICoord::new(6, 7),
314    ///         ICoord::new(6, 8),
315    ///         ICoord::new(5, 8),
316    ///         ICoord::new(4, 8),
317    ///         ICoord::new(4, 7),
318    ///         ICoord::new(4, 6),
319    ///     ]
320    /// );
321    ///
322    /// let origin = ICoord::new(-7, -12);
323    /// assert_eq!(
324    ///     origin.neighbors8()[..],
325    ///     Direction8::DIRECTIONS.iter().map(|dir| origin + *dir).collect::<Vec<_>>()[..],
326    /// );
327    /// ```
328    ///
329    /// [`Direction8::DIRECTIONS`]: super::Direction8::DIRECTIONS
330    pub fn neighbors8(self) -> [ICoord; 8] {
331        [
332            self + Direction8::North,
333            self + Direction8::NorthEast,
334            self + Direction8::East,
335            self + Direction8::SouthEast,
336            self + Direction8::South,
337            self + Direction8::SouthWest,
338            self + Direction8::West,
339            self + Direction8::NorthWest,
340        ]
341    }
342}
343
344impl Add for ICoord {
345    type Output = Self;
346    fn add(self, rhs: Self) -> Self::Output {
347        Self {
348            x: self.x + rhs.x,
349            y: self.y + rhs.y,
350        }
351    }
352}
353
354impl AddAssign for ICoord {
355    fn add_assign(&mut self, rhs: Self) {
356        self.x += rhs.x;
357        self.y += rhs.y;
358    }
359}
360
361impl Sub for ICoord {
362    type Output = Self;
363    fn sub(self, rhs: Self) -> Self::Output {
364        Self {
365            x: self.x - rhs.x,
366            y: self.y - rhs.y,
367        }
368    }
369}
370
371impl SubAssign for ICoord {
372    fn sub_assign(&mut self, rhs: Self) {
373        self.x -= rhs.x;
374        self.y -= rhs.y;
375    }
376}
377
378impl Add<Direction4> for ICoord {
379    type Output = Self;
380    fn add(self, rhs: Direction4) -> Self::Output {
381        self + rhs.deltas()
382    }
383}
384
385impl AddAssign<Direction4> for ICoord {
386    fn add_assign(&mut self, rhs: Direction4) {
387        *self += rhs.deltas();
388    }
389}
390
391impl Add<Direction8> for ICoord {
392    type Output = Self;
393    fn add(self, rhs: Direction8) -> Self::Output {
394        self + rhs.deltas()
395    }
396}
397
398impl AddAssign<Direction8> for ICoord {
399    fn add_assign(&mut self, rhs: Direction8) {
400        *self += rhs.deltas();
401    }
402}
403
404impl Mul<isize> for ICoord {
405    type Output = Self;
406    fn mul(self, rhs: isize) -> Self::Output {
407        Self {
408            x: self.x * rhs,
409            y: self.y * rhs,
410        }
411    }
412}
413
414impl MulAssign<isize> for ICoord {
415    fn mul_assign(&mut self, rhs: isize) {
416        self.x *= rhs;
417        self.y *= rhs;
418    }
419}
420
421impl From<Coord> for ICoord {
422    fn from(value: Coord) -> Self {
423        Self {
424            x: value.x as isize,
425            y: value.y as isize,
426        }
427    }
428}
429
430impl Display for ICoord {
431    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
432        write!(f, "({}, {})", self.x, self.y)
433    }
434}