screeps/local/
room_xy.rs

1use std::{
2    cmp::Ordering,
3    fmt,
4    ops::{Index, IndexMut},
5};
6
7use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
8
9use super::room_coordinate::{OutOfBoundsError, RoomCoordinate};
10use crate::constants::{Direction, ROOM_AREA, ROOM_USIZE};
11
12mod approximate_offsets;
13mod extra_math;
14mod game_math;
15
16/// Converts a [`RoomXY`] coordinate pair to a linear index appropriate for use
17/// with the internal representation of a [`CostMatrix`] or [`LocalCostMatrix`].
18///
19/// [`CostMatrix`]: crate::objects::CostMatrix
20/// [`LocalCostMatrix`]: crate::local::LocalCostMatrix
21#[inline]
22pub const fn xy_to_linear_index(xy: RoomXY) -> usize {
23    xy.x.u8() as usize * ROOM_USIZE + xy.y.u8() as usize
24}
25
26/// Converts a linear index from the internal representation of a [`CostMatrix`]
27/// or [`LocalCostMatrix`] to a [`RoomXY`] coordinate pair for the position the
28/// index represents.
29///
30/// [`CostMatrix`]: crate::objects::CostMatrix
31/// [`LocalCostMatrix`]: crate::local::LocalCostMatrix
32#[inline]
33pub fn linear_index_to_xy(idx: usize) -> RoomXY {
34    assert!(idx < ROOM_AREA, "Out of bounds index: {idx}");
35    // SAFETY: bounds checking above ensures both are within range.
36    RoomXY {
37        x: unsafe { RoomCoordinate::unchecked_new((idx / (ROOM_USIZE)) as u8) },
38        y: unsafe { RoomCoordinate::unchecked_new((idx % (ROOM_USIZE)) as u8) },
39    }
40}
41
42/// Converts a [`RoomXY`] coordinate pair to a terrain index appropriate for use
43/// with the internal representation of [`RoomTerrain`] or [`LocalRoomTerrain`].
44///
45/// [`RoomTerrain`]: crate::objects::RoomTerrain
46/// [`LocalRoomTerrain`]: crate::local::LocalRoomTerrain
47#[inline]
48pub const fn xy_to_terrain_index(xy: RoomXY) -> usize {
49    xy.y.u8() as usize * ROOM_USIZE + xy.x.u8() as usize
50}
51
52/// Converts a terrain index from the internal representation of a
53/// [`RoomTerrain`] or [`LocalRoomTerrain`] to a [`RoomXY`] coordinate pair for
54/// the position the index represents.
55///
56/// [`RoomTerrain`]: crate::objects::RoomTerrain
57/// [`LocalRoomTerrain`]: crate::local::LocalRoomTerrain
58#[inline]
59pub fn terrain_index_to_xy(idx: usize) -> RoomXY {
60    assert!(idx < ROOM_AREA, "Out of bounds index: {idx}");
61    // SAFETY: bounds checking above ensures both are within range.
62    RoomXY {
63        x: unsafe { RoomCoordinate::unchecked_new((idx % (ROOM_USIZE)) as u8) },
64        y: unsafe { RoomCoordinate::unchecked_new((idx / (ROOM_USIZE)) as u8) },
65    }
66}
67
68/// An X/Y pair representing a given coordinate relative to any room.
69#[derive(Debug, Default, Hash, Clone, Copy, PartialEq, Eq)]
70pub struct RoomXY {
71    pub x: RoomCoordinate,
72    pub y: RoomCoordinate,
73}
74
75impl RoomXY {
76    /// Create a new `RoomXY` from a pair of `RoomCoordinate`.
77    #[inline]
78    pub fn new(x: RoomCoordinate, y: RoomCoordinate) -> Self {
79        RoomXY { x, y }
80    }
81
82    /// Create a new `RoomXY` from a pair of `u8`, checking that they're in
83    /// the range of valid values.
84    #[inline]
85    pub fn checked_new(x: u8, y: u8) -> Result<RoomXY, OutOfBoundsError> {
86        RoomXY::try_from((x, y))
87    }
88
89    /// Create a `RoomXY` from a pair of `u8`, without checking whether it's in
90    /// the range of valid values.
91    ///
92    /// # Safety
93    /// Calling this method with `x >= ROOM_SIZE` or `y >= ROOM_SIZE` can
94    /// result in undefined behaviour when the resulting `RoomXY` is used.
95    #[inline]
96    pub unsafe fn unchecked_new(x: u8, y: u8) -> Self {
97        RoomXY {
98            x: RoomCoordinate::unchecked_new(x),
99            y: RoomCoordinate::unchecked_new(y),
100        }
101    }
102
103    /// Get whether this coordinate pair represents an edge position (0 or 49
104    /// for either coordinate)
105    pub const fn is_room_edge(self) -> bool {
106        self.x.is_room_edge() || self.y.is_room_edge()
107    }
108
109    /// Get the coordinate adjusted by a certain value, returning `None` if the
110    /// result is outside the valid room area.
111    ///
112    /// Example usage:
113    ///
114    /// ```
115    /// use screeps::local::RoomXY;
116    ///
117    /// let zero = unsafe { RoomXY::unchecked_new(0, 0) };
118    /// let one = unsafe { RoomXY::unchecked_new(1, 1) };
119    /// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) };
120    ///
121    /// assert_eq!(zero.checked_add((1, 1)), Some(one));
122    /// assert_eq!(zero.checked_add((-1, 0)), None);
123    /// assert_eq!(zero.checked_add((49, 49)), Some(forty_nine));
124    /// assert_eq!(forty_nine.checked_add((1, 1)), None);
125    /// ```
126    pub fn checked_add(self, rhs: (i8, i8)) -> Option<RoomXY> {
127        let x = self.x.checked_add(rhs.0)?;
128        let y = self.y.checked_add(rhs.1)?;
129        Some(RoomXY { x, y })
130    }
131
132    /// Get the coordinate adjusted by a certain value, saturating at the edges
133    /// of the room if the result would be outside the valid room area.
134    ///
135    /// Example usage:
136    ///
137    /// ```
138    /// use screeps::local::RoomXY;
139    ///
140    /// let zero = unsafe { RoomXY::unchecked_new(0, 0) };
141    /// let one = unsafe { RoomXY::unchecked_new(1, 1) };
142    /// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) };
143    ///
144    /// assert_eq!(zero.saturating_add((1, 1)), one);
145    /// assert_eq!(zero.saturating_add((-1, 0)), zero);
146    /// assert_eq!(zero.saturating_add((49, 49)), forty_nine);
147    /// assert_eq!(zero.saturating_add((i8::MAX, i8::MAX)), forty_nine);
148    /// assert_eq!(forty_nine.saturating_add((1, 1)), forty_nine);
149    /// assert_eq!(forty_nine.saturating_add((i8::MIN, i8::MIN)), zero);
150    /// ```
151    pub fn saturating_add(self, rhs: (i8, i8)) -> RoomXY {
152        let x = self.x.saturating_add(rhs.0);
153        let y = self.y.saturating_add(rhs.1);
154        RoomXY { x, y }
155    }
156
157    /// Get the neighbor of a given `RoomXY` in the given direction, returning
158    /// `None` if the result is outside the valid room area.
159    ///
160    /// Example usage:
161    ///
162    /// ```
163    /// use screeps::{constants::Direction::*, local::RoomXY};
164    ///
165    /// let zero = unsafe { RoomXY::unchecked_new(0, 0) };
166    /// let one = unsafe { RoomXY::unchecked_new(1, 1) };
167    /// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) };
168    ///
169    /// assert_eq!(zero.checked_add_direction(BottomRight), Some(one));
170    /// assert_eq!(zero.checked_add_direction(TopLeft), None);
171    /// assert_eq!(one.checked_add_direction(TopLeft), Some(zero));
172    /// assert_eq!(forty_nine.checked_add_direction(BottomRight), None);
173    /// ```
174    pub fn checked_add_direction(self, rhs: Direction) -> Option<RoomXY> {
175        let (dx, dy) = rhs.into();
176        self.checked_add((dx as i8, dy as i8))
177    }
178
179    /// Get the neighbor of a given `RoomXY` in the given direction, saturating
180    /// at the edges if the result is outside the valid room area.
181    ///
182    /// Example usage:
183    ///
184    /// ```
185    /// use screeps::{constants::Direction::*, local::RoomXY};
186    ///
187    /// let zero = unsafe { RoomXY::unchecked_new(0, 0) };
188    /// let one = unsafe { RoomXY::unchecked_new(1, 1) };
189    /// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) };
190    ///
191    /// assert_eq!(zero.saturating_add_direction(BottomRight), one);
192    /// assert_eq!(zero.saturating_add_direction(TopLeft), zero);
193    /// assert_eq!(one.saturating_add_direction(TopLeft), zero);
194    /// assert_eq!(forty_nine.saturating_add_direction(BottomRight), forty_nine);
195    /// ```
196    pub fn saturating_add_direction(self, rhs: Direction) -> RoomXY {
197        let (dx, dy) = rhs.into();
198        self.saturating_add((dx as i8, dy as i8))
199    }
200
201    /// Get all the valid neighbors of a given `RoomXY`.
202    ///
203    /// Example usage:
204    ///
205    /// ```
206    /// use screeps::local::RoomXY;
207    ///
208    /// let zero_zero = unsafe { RoomXY::unchecked_new(0, 0) };
209    /// let zero_one = unsafe { RoomXY::unchecked_new(0, 1) };
210    /// let one_zero = unsafe { RoomXY::unchecked_new(1, 0) };
211    /// let one_one = unsafe { RoomXY::unchecked_new(1, 1) };
212    ///
213    /// let zero_two = unsafe { RoomXY::unchecked_new(0, 2) };
214    /// let one_two = unsafe { RoomXY::unchecked_new(1, 2) };
215    /// let two_two = unsafe { RoomXY::unchecked_new(2, 2) };
216    /// let two_one = unsafe { RoomXY::unchecked_new(2, 1) };
217    /// let two_zero = unsafe { RoomXY::unchecked_new(2, 0) };
218    ///
219    /// let zero_zero_neighbors = zero_zero.neighbors();
220    ///
221    /// assert_eq!(zero_zero_neighbors.len(), 3);
222    /// assert!(zero_zero_neighbors.contains(&zero_one));
223    /// assert!(zero_zero_neighbors.contains(&one_one));
224    /// assert!(zero_zero_neighbors.contains(&one_zero));
225    ///
226    /// let one_one_neighbors = one_one.neighbors();
227    ///
228    /// assert_eq!(one_one_neighbors.len(), 8);
229    /// assert!(one_one_neighbors.contains(&zero_zero));
230    /// assert!(one_one_neighbors.contains(&zero_one));
231    /// assert!(one_one_neighbors.contains(&one_zero));
232    /// assert!(one_one_neighbors.contains(&zero_two));
233    /// assert!(one_one_neighbors.contains(&one_two));
234    /// assert!(one_one_neighbors.contains(&two_two));
235    /// assert!(one_one_neighbors.contains(&two_one));
236    /// assert!(one_one_neighbors.contains(&two_zero));
237    /// ```
238    pub fn neighbors(self) -> Vec<RoomXY> {
239        Direction::iter()
240            .filter_map(|dir| self.checked_add_direction(*dir))
241            .collect()
242    }
243}
244
245impl PartialOrd for RoomXY {
246    #[inline]
247    fn partial_cmp(&self, other: &RoomXY) -> Option<Ordering> {
248        Some(self.cmp(other))
249    }
250}
251
252impl Ord for RoomXY {
253    fn cmp(&self, other: &Self) -> Ordering {
254        (self.y, self.x).cmp(&(other.y, other.x))
255    }
256}
257
258impl fmt::Display for RoomXY {
259    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
260        write!(f, "({}, {})", self.x, self.y)
261    }
262}
263
264impl From<RoomXY> for (u8, u8) {
265    fn from(xy: RoomXY) -> (u8, u8) {
266        (xy.x.u8(), xy.y.u8())
267    }
268}
269
270impl TryFrom<(u8, u8)> for RoomXY {
271    type Error = OutOfBoundsError;
272
273    fn try_from(xy: (u8, u8)) -> Result<RoomXY, OutOfBoundsError> {
274        Ok(RoomXY {
275            x: RoomCoordinate::try_from(xy.0)?,
276            y: RoomCoordinate::try_from(xy.1)?,
277        })
278    }
279}
280
281impl From<(RoomCoordinate, RoomCoordinate)> for RoomXY {
282    fn from(xy: (RoomCoordinate, RoomCoordinate)) -> RoomXY {
283        RoomXY { x: xy.0, y: xy.1 }
284    }
285}
286
287impl From<RoomXY> for (RoomCoordinate, RoomCoordinate) {
288    fn from(xy: RoomXY) -> (RoomCoordinate, RoomCoordinate) {
289        (xy.x, xy.y)
290    }
291}
292
293#[derive(Serialize, Deserialize)]
294struct ReadableXY {
295    x: RoomCoordinate,
296    y: RoomCoordinate,
297}
298
299impl From<ReadableXY> for RoomXY {
300    fn from(ReadableXY { x, y }: ReadableXY) -> RoomXY {
301        RoomXY { x, y }
302    }
303}
304
305impl From<RoomXY> for ReadableXY {
306    fn from(RoomXY { x, y }: RoomXY) -> ReadableXY {
307        ReadableXY { x, y }
308    }
309}
310
311impl Serialize for RoomXY {
312    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
313    where
314        S: Serializer,
315    {
316        if serializer.is_human_readable() {
317            ReadableXY::from(*self).serialize(serializer)
318        } else {
319            let xy: (u8, u8) = (*self).into();
320            let packed: u16 = ((xy.0 as u16) << 8) | (xy.1 as u16);
321            packed.serialize(serializer)
322        }
323    }
324}
325
326impl<'de> Deserialize<'de> for RoomXY {
327    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
328    where
329        D: Deserializer<'de>,
330    {
331        if deserializer.is_human_readable() {
332            ReadableXY::deserialize(deserializer).map(Into::into)
333        } else {
334            let packed = u16::deserialize(deserializer)?;
335            let xy = (((packed >> 8) & 0xFF) as u8, (packed & 0xFF) as u8);
336            RoomXY::try_from(xy).map_err(|err: OutOfBoundsError| {
337                de::Error::invalid_value(
338                    de::Unexpected::Unsigned(err.0 as u64),
339                    &format!("a non-negative integer less-than {ROOM_USIZE}").as_str(),
340                )
341            })
342        }
343    }
344}
345
346/// A wrapper struct indicating that the inner array should be indexed X major,
347/// i.e. ```
348/// use screeps::{
349///     constants::ROOM_USIZE,
350///     local::{XMajor, XY},
351/// };
352///
353/// let mut x_major = XMajor([[0_u8; ROOM_USIZE]; ROOM_USIZE]);
354/// x_major.0[10][0] = 1;
355/// let xy = RoomXY::checked_new(10, 0).unwrap();
356/// assert_eq!(x_major[xy], 1);
357/// ```
358#[repr(transparent)]
359pub struct XMajor<T>(pub [[T; ROOM_USIZE]; ROOM_USIZE]);
360
361impl<T> XMajor<T> {
362    pub fn from_ref(arr: &[[T; ROOM_USIZE]; ROOM_USIZE]) -> &Self {
363        // SAFETY: XMajor is a repr(transparent) wrapper around [[T; ROOM_USIZE];
364        // ROOM_USIZE], so casting references of one to the other is safe.
365        unsafe { &*(arr as *const [[T; ROOM_USIZE]; ROOM_USIZE] as *const Self) }
366    }
367
368    pub fn from_flat_ref(arr: &[T; ROOM_AREA]) -> &Self {
369        // SAFETY: ROOM_AREA = ROOM_USIZE * ROOM_USIZE, so [T; ROOM_AREA] is identical
370        // in data layout to [[T; ROOM_USIZE]; ROOM_USIZE].
371        Self::from_ref(unsafe {
372            &*(arr as *const [T; ROOM_AREA] as *const [[T; ROOM_USIZE]; ROOM_USIZE])
373        })
374    }
375
376    pub fn from_mut(arr: &mut [[T; ROOM_USIZE]; ROOM_USIZE]) -> &mut Self {
377        // SAFETY: XMajor is a repr(transparent) wrapper around [[T; ROOM_USIZE];
378        // ROOM_USIZE], so casting references of one to the other is safe.
379        unsafe { &mut *(arr as *mut [[T; ROOM_USIZE]; ROOM_USIZE] as *mut Self) }
380    }
381
382    pub fn from_flat_mut(arr: &mut [T; ROOM_AREA]) -> &mut Self {
383        // SAFETY: ROOM_AREA = ROOM_USIZE * ROOM_USIZE, so [T; ROOM_AREA] is identical
384        // in data layout to [[T; ROOM_USIZE]; ROOM_USIZE].
385        Self::from_mut(unsafe {
386            &mut *(arr as *mut [T; ROOM_AREA] as *mut [[T; ROOM_USIZE]; ROOM_USIZE])
387        })
388    }
389}
390
391impl<T> Index<RoomXY> for XMajor<T> {
392    type Output = T;
393
394    fn index(&self, index: RoomXY) -> &Self::Output {
395        &self.0[index.x][index.y]
396    }
397}
398
399impl<T> IndexMut<RoomXY> for XMajor<T> {
400    fn index_mut(&mut self, index: RoomXY) -> &mut Self::Output {
401        &mut self.0[index.x][index.y]
402    }
403}
404
405/// A wrapper struct indicating that the inner array should be indexed Y major,
406/// i.e. ```
407/// use screeps::{
408///     constants::ROOM_USIZE,
409///     local::{YMajor, XY},
410/// };
411///
412/// let mut y_major = YMajor([[0_u8; ROOM_USIZE]; ROOM_USIZE]);
413/// y_major.0[0][10] = 1;
414/// let xy = RoomXY::checked_new(10, 0).unwrap();
415/// assert_eq!(y_major[xy], 1);
416/// ```
417#[repr(transparent)]
418pub struct YMajor<T>(pub [[T; ROOM_USIZE]; ROOM_USIZE]);
419
420impl<T> YMajor<T> {
421    pub fn from_ref(arr: &[[T; ROOM_USIZE]; ROOM_USIZE]) -> &Self {
422        // SAFETY: XMajor is a repr(transparent) wrapper around [[T; ROOM_USIZE];
423        // ROOM_USIZE], so casting references of one to the other is safe.
424        unsafe { &*(arr as *const [[T; ROOM_USIZE]; ROOM_USIZE] as *const Self) }
425    }
426
427    pub fn from_flat_ref(arr: &[T; ROOM_AREA]) -> &Self {
428        // SAFETY: ROOM_AREA = ROOM_USIZE * ROOM_USIZE, so [T; ROOM_AREA] is identical
429        // in data layout to [[T; ROOM_USIZE]; ROOM_USIZE].
430        Self::from_ref(unsafe {
431            &*(arr as *const [T; ROOM_AREA] as *const [[T; ROOM_USIZE]; ROOM_USIZE])
432        })
433    }
434
435    pub fn from_mut(arr: &mut [[T; ROOM_USIZE]; ROOM_USIZE]) -> &mut Self {
436        // SAFETY: XMajor is a repr(transparent) wrapper around [[T; ROOM_USIZE];
437        // ROOM_USIZE], so casting references of one to the other is safe.
438        unsafe { &mut *(arr as *mut [[T; ROOM_USIZE]; ROOM_USIZE] as *mut Self) }
439    }
440
441    pub fn from_flat_mut(arr: &mut [T; ROOM_AREA]) -> &mut Self {
442        // SAFETY: ROOM_AREA = ROOM_USIZE * ROOM_USIZE, so [T; ROOM_AREA] is identical
443        // in data layout to [[T; ROOM_USIZE]; ROOM_USIZE].
444        Self::from_mut(unsafe {
445            &mut *(arr as *mut [T; ROOM_AREA] as *mut [[T; ROOM_USIZE]; ROOM_USIZE])
446        })
447    }
448}
449
450impl<T> Index<RoomXY> for YMajor<T> {
451    type Output = T;
452
453    fn index(&self, index: RoomXY) -> &Self::Output {
454        &self.0[index.y][index.x]
455    }
456}
457
458impl<T> IndexMut<RoomXY> for YMajor<T> {
459    fn index_mut(&mut self, index: RoomXY) -> &mut Self::Output {
460        &mut self.0[index.y][index.x]
461    }
462}