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}