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}