screeps/local/
room_coordinate.rs

1use std::{
2    error::Error,
3    fmt,
4    hint::assert_unchecked,
5    ops::{Index, IndexMut, Neg, Sub},
6};
7
8use serde::{Deserialize, Serialize};
9use wasm_bindgen::UnwrapThrowExt;
10
11use crate::constants::{ROOM_AREA, ROOM_SIZE, ROOM_USIZE};
12
13#[derive(Debug, Clone, Copy)]
14pub struct OutOfBoundsError(pub u8);
15
16impl fmt::Display for OutOfBoundsError {
17    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
18        write!(f, "Out of bounds coordinate: {}", self.0)
19    }
20}
21
22impl Error for OutOfBoundsError {}
23
24/// An X or Y coordinate in a room, restricted to the valid range of
25/// coordinates. This restriction can be used in safety constraints, and is
26/// enforced by all safe `RoomCoordinate` constructors.
27#[derive(
28    Debug, Hash, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
29)]
30#[serde(try_from = "u8", into = "u8")]
31#[repr(transparent)]
32pub struct RoomCoordinate(u8);
33
34impl RoomCoordinate {
35    pub const MAX: Self = Self(ROOM_SIZE - 1);
36    pub const MIN: Self = Self(0);
37
38    /// Create a `RoomCoordinate` from a `u8`, returning an error if the
39    /// coordinate is not in the valid room size range
40    #[inline]
41    pub const fn new(coord: u8) -> Result<Self, OutOfBoundsError> {
42        if coord < ROOM_SIZE {
43            Ok(RoomCoordinate(coord))
44        } else {
45            Err(OutOfBoundsError(coord))
46        }
47    }
48
49    /// Create a `RoomCoordinate` from a `u8`, without checking whether it's in
50    /// the range of valid values.
51    ///
52    /// # Safety
53    /// Calling this method with `coord >= ROOM_SIZE` can result in undefined
54    /// behaviour when the resulting `RoomCoordinate` is used.
55    #[inline]
56    pub unsafe fn unchecked_new(coord: u8) -> Self {
57        debug_assert!(
58            coord < ROOM_SIZE,
59            "Out of bounds unchecked coordinate: {coord}"
60        );
61        RoomCoordinate(coord)
62    }
63
64    /// Provides a hint to the compiler that the contained `u8` is smaller than
65    /// `ROOM_SIZE`. Allows for better optimized safe code that uses this
66    /// property.
67    pub fn assume_bounds_constraint(self) {
68        debug_assert!(self.0 < ROOM_SIZE);
69        // SAFETY: It is only safe to construct `RoomCoordinate` when self.0 <
70        // ROOM_SIZE.
71        unsafe {
72            assert_unchecked(self.0 < ROOM_SIZE);
73        }
74    }
75
76    /// Get the integer value of this coordinate
77    pub const fn u8(self) -> u8 {
78        self.0
79    }
80
81    /// Get whether this coordinate represents an edge position (0 or 49)
82    pub const fn is_room_edge(self) -> bool {
83        self.0 == 0 || self.0 == ROOM_SIZE - 1
84    }
85
86    /// Get the coordinate adjusted by a certain value, returning `None` if the
87    /// result is outside the valid range.
88    ///
89    /// Example usage:
90    ///
91    /// ```
92    /// use screeps::local::RoomCoordinate;
93    ///
94    /// let zero = RoomCoordinate::new(0).unwrap();
95    /// let forty_nine = RoomCoordinate::new(49).unwrap();
96    ///
97    /// assert_eq!(zero.checked_add(1), Some(RoomCoordinate::new(1).unwrap()));
98    /// assert_eq!(zero.checked_add(-1), None);
99    /// assert_eq!(zero.checked_add(49), Some(forty_nine));
100    /// assert_eq!(forty_nine.checked_add(1), None);
101    /// ```
102    pub fn checked_add(self, rhs: i8) -> Option<RoomCoordinate> {
103        self.assume_bounds_constraint();
104        // Why this works, assuming ROOM_SIZE < i8::MAX + 1 == 128 and ignoring the
105        // test:
106        //   - if rhs < 0: the smallest value this can produce is -128, which casted to
107        //     u8 is 128. The closer rhs is to 0, the larger the cast sum is. So if
108        //     ROOM_SIZE <= i8::MAX, any underflow will fail the x < ROOM_SIZE check.
109        //   - if rhs > 0: as long as self.0 <= i8::MAX, self.0 + rhs <= 2 * i8::MAX <
110        //     256, so there isn't unsigned overflow.
111        RoomCoordinate::new(self.0.wrapping_add_signed(rhs)).ok()
112    }
113
114    /// [`checked_add`](Self::checked_add) that accepts a [`RoomOffset`].
115    pub fn checked_add_offset(self, rhs: RoomOffset) -> Option<RoomCoordinate> {
116        self.assume_bounds_constraint();
117        rhs.assume_bounds_constraint();
118        RoomCoordinate::new(self.0.wrapping_add_signed(rhs.0)).ok()
119    }
120
121    /// Get the coordinate adjusted by a certain value, saturating at the edges
122    /// of the room if the result would be outside of the valid range.
123    ///
124    /// Example usage:
125    ///
126    /// ```
127    /// use screeps::local::RoomCoordinate;
128    ///
129    /// let zero = RoomCoordinate::new(0).unwrap();
130    /// let forty_nine = RoomCoordinate::new(49).unwrap();
131    ///
132    /// assert_eq!(zero.saturating_add(1), RoomCoordinate::new(1).unwrap());
133    /// assert_eq!(zero.saturating_add(-1), zero);
134    /// assert_eq!(zero.saturating_add(i8::MAX), forty_nine);
135    /// assert_eq!(forty_nine.saturating_add(1), forty_nine);
136    /// assert_eq!(forty_nine.saturating_add(i8::MIN), zero);
137    /// ```
138    pub fn saturating_add(self, rhs: i8) -> RoomCoordinate {
139        self.assume_bounds_constraint();
140        let (res, overflow) = self.0.overflowing_add_signed(rhs);
141        if overflow {
142            RoomCoordinate::MIN
143        } else {
144            // Optimizer will see the return is always Ok
145            RoomCoordinate::new(res.min(ROOM_SIZE - 1)).unwrap_throw()
146        }
147    }
148
149    /// [`saturating_add`](Self::saturating_add) that accepts a [`RoomOffset`].
150    pub fn saturating_add_offset(self, rhs: RoomOffset) -> Self {
151        self.assume_bounds_constraint();
152        rhs.assume_bounds_constraint();
153        let result = (self.0 as i8 + rhs.0).clamp(0, ROOM_SIZE_I8 - 1);
154        RoomCoordinate::new(result as u8).unwrap_throw()
155    }
156
157    /// Get the coordinate adjusted by a certain value, wrapping around ta the
158    /// edges of the room if the result would be outside of the valid range.
159    /// Returns a [`bool`] indicating whether there was wrapping.
160    ///
161    /// Can be used to e.g. implement addition for
162    /// [`Position`](crate::Position)s.
163    ///
164    /// Example usage:
165    ///
166    /// ```
167    /// use screeps::local::RoomCoordinate;
168    ///
169    /// assert_eq!(
170    ///     RoomCoordinate::MIN.overflowing_add(1),
171    ///     (RoomCoordinate::new(1).unwrap(), false)
172    /// );
173    /// assert_eq!(
174    ///     RoomCoordinate::MIN.overflowing_add(-1),
175    ///     (RoomCoordinate::MAX, true)
176    /// );
177    /// assert_eq!(
178    ///     RoomCoordinate::MAX.overflowing_add(1),
179    ///     (RoomCoordinate::MIN, true)
180    /// );
181    /// ```
182    pub fn overflowing_add(self, rhs: i8) -> (RoomCoordinate, bool) {
183        self.assume_bounds_constraint();
184        let raw = self.0 as i16 + rhs as i16;
185        if raw >= ROOM_SIZE as i16 {
186            (
187                RoomCoordinate::new((raw % ROOM_SIZE as i16) as u8).unwrap_throw(),
188                true,
189            )
190        } else if raw < 0 {
191            (
192                RoomCoordinate::new(((raw + 150) % ROOM_SIZE as i16) as u8).unwrap_throw(),
193                true,
194            )
195        } else {
196            (RoomCoordinate::new(raw as u8).unwrap_throw(), false)
197        }
198    }
199
200    /// [`overflowing_add`](Self::overflowing_add) that accepts a
201    /// [`RoomOffset`].
202    pub fn overflowing_add_offset(self, rhs: RoomOffset) -> (RoomCoordinate, bool) {
203        self.assume_bounds_constraint();
204        rhs.assume_bounds_constraint();
205        let raw = self.0 as i8 + rhs.0;
206        if raw >= ROOM_SIZE_I8 {
207            (
208                RoomCoordinate::new((raw - ROOM_SIZE_I8) as u8).unwrap_throw(),
209                true,
210            )
211        } else if raw < 0 {
212            (
213                RoomCoordinate::new((raw + ROOM_SIZE_I8) as u8).unwrap_throw(),
214                true,
215            )
216        } else {
217            (RoomCoordinate::new(raw as u8).unwrap_throw(), false)
218        }
219    }
220
221    /// Get the coordinate adjusted by a certain value, wrapping around ta the
222    /// edges of the room if the result would be outside of the valid range.
223    ///
224    /// Example usage:
225    ///
226    /// ```
227    /// use screeps::local::RoomCoordinate;
228    ///
229    /// assert_eq!(
230    ///     RoomCoordinate::MIN.wrapping_add(1),
231    ///     RoomCoordinate::new(1).unwrap()
232    /// );
233    /// assert_eq!(RoomCoordinate::MIN.wrapping_add(-1), RoomCoordinate::MAX);
234    /// assert_eq!(RoomCoordinate::MAX.wrapping_add(1), RoomCoordinate::MIN);
235    /// ```
236    pub fn wrapping_add(self, rhs: i8) -> Self {
237        self.overflowing_add(rhs).0
238    }
239
240    /// [`wrapping_add`](Self::wrapping_add) that accepts a [`RoomOffset`].
241    pub fn wrapping_add_offset(self, rhs: RoomOffset) -> Self {
242        self.overflowing_add_offset(rhs).0
243    }
244
245    /// Get the coordinate adjusted by a certain value.
246    ///
247    /// # Safety
248    ///
249    /// After adding rhs to the integer coordinate of self, the result must lie
250    /// within `[0, ROOM_SIZE)`.
251    pub unsafe fn unchecked_add(self, rhs: i8) -> Self {
252        self.assume_bounds_constraint();
253        Self::unchecked_new((self.0 as i8).unchecked_add(rhs) as u8)
254    }
255
256    /// [`unchecked_add`](Self::unchecked_add) that accepts a [`RoomOffset`].
257    ///
258    /// # Safety
259    ///
260    /// The result of adding the integer coordinate of self and the integer
261    /// offset in `rhs` must lie within `[0, ROOM_SIZE)`.
262    pub unsafe fn unchecked_add_offset(self, rhs: RoomOffset) -> Self {
263        self.assume_bounds_constraint();
264        rhs.assume_bounds_constraint();
265        Self::unchecked_new((self.0 as i8).unchecked_add(rhs.0) as u8)
266    }
267}
268
269impl fmt::Display for RoomCoordinate {
270    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
271        self.0.fmt(f)
272    }
273}
274
275impl From<RoomCoordinate> for u8 {
276    fn from(coord: RoomCoordinate) -> u8 {
277        coord.0
278    }
279}
280
281impl TryFrom<u8> for RoomCoordinate {
282    type Error = OutOfBoundsError;
283
284    fn try_from(coord: u8) -> Result<Self, Self::Error> {
285        RoomCoordinate::new(coord)
286    }
287}
288
289impl AsRef<u8> for RoomCoordinate {
290    fn as_ref(&self) -> &u8 {
291        &self.0
292    }
293}
294
295impl<T> Index<RoomCoordinate> for [T; ROOM_USIZE] {
296    type Output = T;
297
298    fn index(&self, index: RoomCoordinate) -> &Self::Output {
299        index.assume_bounds_constraint();
300        &self[index.0 as usize]
301    }
302}
303
304impl<T> IndexMut<RoomCoordinate> for [T; ROOM_USIZE] {
305    fn index_mut(&mut self, index: RoomCoordinate) -> &mut Self::Output {
306        index.assume_bounds_constraint();
307        &mut self[index.0 as usize]
308    }
309}
310
311impl<T> Index<RoomCoordinate> for [T; ROOM_AREA] {
312    type Output = [T; ROOM_USIZE];
313
314    fn index(&self, index: RoomCoordinate) -> &Self::Output {
315        // SAFETY: ROOM_USIZE * ROOM_USIZE = ROOM_AREA, so [T; ROOM_AREA] and [[T;
316        // ROOM_USIZE]; ROOM_USIZE] have the same layout.
317        let this =
318            unsafe { &*(self as *const [T; ROOM_AREA] as *const [[T; ROOM_USIZE]; ROOM_USIZE]) };
319        &this[index]
320    }
321}
322
323impl<T> IndexMut<RoomCoordinate> for [T; ROOM_AREA] {
324    fn index_mut(&mut self, index: RoomCoordinate) -> &mut Self::Output {
325        // SAFETY: ROOM_USIZE * ROOM_USIZE = ROOM_AREA, so [T; ROOM_AREA] and [[T;
326        // ROOM_USIZE]; ROOM_USIZE] have the same layout.
327        let this =
328            unsafe { &mut *(self as *mut [T; ROOM_AREA] as *mut [[T; ROOM_USIZE]; ROOM_USIZE]) };
329        &mut this[index]
330    }
331}
332
333impl Sub for RoomCoordinate {
334    type Output = RoomOffset;
335
336    fn sub(self, rhs: Self) -> Self::Output {
337        self.assume_bounds_constraint();
338        rhs.assume_bounds_constraint();
339        RoomOffset::new(self.0 as i8 - rhs.0 as i8).unwrap_throw()
340    }
341}
342
343const ROOM_SIZE_I8: i8 = {
344    // If this fails, we need to rework the arithmetic code
345    debug_assert!(2 * ROOM_SIZE <= i8::MAX as u8);
346    ROOM_SIZE as i8
347};
348
349/// An offset between two coordinates in a room. Restricted to the open range
350/// (-[`ROOM_SIZE`], [`ROOM_SIZE`]). This bound can be used in safety
351/// constraints.
352#[derive(
353    Debug, Hash, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize,
354)]
355#[serde(try_from = "i8", into = "i8")]
356#[repr(transparent)]
357pub struct RoomOffset(i8);
358
359impl RoomOffset {
360    pub const MAX: Self = Self(ROOM_SIZE_I8 - 1);
361    pub const MIN: Self = Self(1 - ROOM_SIZE_I8);
362
363    /// Create a `RoomOffset` from an `i8`, returning an error if it's not
364    /// within the valid range.
365    pub const fn new(offset: i8) -> Result<Self, OffsetOutOfBoundsError> {
366        if -ROOM_SIZE_I8 < offset && offset < ROOM_SIZE_I8 {
367            Ok(Self(offset))
368        } else {
369            Err(OffsetOutOfBoundsError(offset))
370        }
371    }
372
373    /// Create a `RoomOffset` from an `i8`, without checking whether it's in the
374    /// range of valid values.
375    ///
376    /// # Safety
377    /// Calling this method with `offset.abs() >= ROOM_SIZE_I8` can result in
378    /// undefined behaviour when the resulting `RoomOffset` is used.
379    pub unsafe fn unchecked_new(offset: i8) -> Self {
380        debug_assert!(
381            -ROOM_SIZE_I8 < offset && offset < ROOM_SIZE_I8,
382            "Out of bounds unchecked offset: {offset}"
383        );
384        Self(offset)
385    }
386
387    /// Provides a hint to the compiler that the contained `i8` is within
388    /// `(-ROOM_SIZE_I8, ROOM_SIZE_I8)`. Allows for better optimized safe code
389    /// that uses this property.
390    pub fn assume_bounds_constraint(self) {
391        debug_assert!(-ROOM_SIZE_I8 < self.0 && self.0 < ROOM_SIZE_I8);
392        // SAFETY: It is only safe to construct `RoomOffset` when `-ROOM_SIZE_I8 <
393        // self.0 < ROOM_SIZE_I8`.
394        unsafe {
395            assert_unchecked(-ROOM_SIZE_I8 < self.0 && self.0 < ROOM_SIZE_I8);
396        }
397    }
398
399    /// Add two offsets together, returning `None` if the result would be
400    /// outside the valid range.
401    ///
402    /// Example usage:
403    ///
404    /// ```
405    /// use screeps::local::RoomOffset;
406    ///
407    /// let zero = RoomOffset::new(0).unwrap();
408    /// let one = RoomOffset::new(1).unwrap();
409    ///
410    /// assert_eq!(RoomOffset::MIN.checked_add(RoomOffset::MAX), Some(zero));
411    /// assert_eq!(RoomOffset::MAX.checked_add(one), None);
412    /// assert_eq!(RoomOffset::MIN.checked_add(-one), None);
413    /// ```
414    pub fn checked_add(self, rhs: Self) -> Option<Self> {
415        self.assume_bounds_constraint();
416        rhs.assume_bounds_constraint();
417        Self::new(self.0 + rhs.0).ok()
418    }
419
420    /// Add two offsets together, saturating at the boundaries of the valid
421    /// range if the result would be outside.
422    ///
423    /// Example usage:
424    ///
425    /// ```
426    /// use screeps::local::RoomOffset;
427    ///
428    /// let zero = RoomOffset::new(0).unwrap();
429    /// let one = RoomOffset::new(1).unwrap();
430    ///
431    /// assert_eq!(RoomOffset::MIN.saturating_add(RoomOffset::MAX), zero);
432    /// assert_eq!(RoomOffset::MAX.saturating_add(one), RoomOffset::MAX);
433    /// assert_eq!(RoomOffset::MIN.saturating_add(-one), RoomOffset::MIN);
434    /// ```
435    pub fn saturating_add(self, rhs: Self) -> Self {
436        self.assume_bounds_constraint();
437        rhs.assume_bounds_constraint();
438        Self::new((self.0 + rhs.0).clamp(-ROOM_SIZE_I8 + 1, ROOM_SIZE_I8 - 1)).unwrap_throw()
439    }
440
441    /// Add two offsets together, wrapping around at the ends of the valid
442    /// range. Returns a [`bool`] indicating whether there was wrapping.
443    ///
444    /// Example usage:
445    ///
446    /// ```
447    /// use screeps::local::RoomOffset;
448    ///
449    /// let zero = RoomOffset::new(0).unwrap();
450    /// let one = RoomOffset::new(1).unwrap();
451    ///
452    /// assert_eq!(
453    ///     RoomOffset::MAX.overflowing_add(one),
454    ///     (RoomOffset::MIN, true)
455    /// );
456    /// assert_eq!(
457    ///     RoomOffset::MIN.overflowing_add(-one),
458    ///     (RoomOffset::MAX, true)
459    /// );
460    /// assert_eq!(
461    ///     RoomOffset::MIN.overflowing_add(RoomOffset::MAX),
462    ///     (zero, false)
463    /// );
464    /// ```
465    pub fn overflowing_add(self, rhs: Self) -> (Self, bool) {
466        const RANGE_WIDTH: i8 = 2 * ROOM_SIZE_I8 - 1;
467        self.assume_bounds_constraint();
468        rhs.assume_bounds_constraint();
469        let raw = self.0 + rhs.0;
470        if raw <= -ROOM_SIZE_I8 {
471            (Self::new(raw + RANGE_WIDTH).unwrap_throw(), true)
472        } else if raw >= ROOM_SIZE_I8 {
473            (Self::new(raw - RANGE_WIDTH).unwrap_throw(), true)
474        } else {
475            (Self::new(raw).unwrap_throw(), false)
476        }
477    }
478
479    /// Add two offsets together, wrapping around at the ends of the valid
480    /// range.
481    ///
482    /// Example usage:
483    ///
484    /// ```
485    /// use screeps::local::RoomOffset;
486    ///
487    /// let zero = RoomOffset::new(0).unwrap();
488    /// let one = RoomOffset::new(1).unwrap();
489    ///
490    /// assert_eq!(RoomOffset::MAX.wrapping_add(one), RoomOffset::MIN);
491    /// assert_eq!(RoomOffset::MIN.wrapping_add(-one), RoomOffset::MAX);
492    /// assert_eq!(RoomOffset::MIN.wrapping_add(RoomOffset::MAX), zero);
493    /// ```
494    pub fn wrapping_add(self, rhs: Self) -> Self {
495        self.overflowing_add(rhs).0
496    }
497
498    /// Add two offsets together, without checking that the result is in the
499    /// valid range.
500    ///
501    /// # Safety
502    ///
503    /// The result of adding the two offsets as integers must lie within
504    /// `(-ROOM_SIZE_I8, ROOM_SIZE_I8)`.
505    pub unsafe fn unchecked_add(self, rhs: Self) -> Self {
506        self.assume_bounds_constraint();
507        rhs.assume_bounds_constraint();
508        Self::unchecked_new(self.0.unchecked_add(rhs.0))
509    }
510
511    /// Get the absolute value of the offset.
512    ///
513    /// Can be used for distance computations, e.g.
514    /// ```
515    /// use screeps::local::{RoomOffset, RoomXY};
516    ///
517    /// fn get_movement_distance(a: RoomXY, b: RoomXY) -> u8 {
518    ///     (a.x - b.x).abs().max((a.y - b.y).abs())
519    /// }
520    /// ```
521    pub fn abs(self) -> u8 {
522        self.assume_bounds_constraint();
523        self.0.unsigned_abs()
524    }
525}
526
527impl From<RoomOffset> for i8 {
528    fn from(offset: RoomOffset) -> i8 {
529        offset.0
530    }
531}
532
533#[derive(Debug, Clone, Copy)]
534pub struct OffsetOutOfBoundsError(pub i8);
535
536impl fmt::Display for OffsetOutOfBoundsError {
537    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
538        write!(f, "Out of bounds offset: {}", self.0)
539    }
540}
541
542impl TryFrom<i8> for RoomOffset {
543    type Error = OffsetOutOfBoundsError;
544
545    fn try_from(offset: i8) -> Result<Self, Self::Error> {
546        Self::new(offset)
547    }
548}
549
550impl AsRef<i8> for RoomOffset {
551    fn as_ref(&self) -> &i8 {
552        &self.0
553    }
554}
555
556impl Neg for RoomOffset {
557    type Output = Self;
558
559    fn neg(self) -> Self::Output {
560        self.assume_bounds_constraint();
561        Self::new(-self.0).unwrap_throw()
562    }
563}
564
565#[cfg(test)]
566mod test {
567    use super::*;
568
569    #[test]
570    fn checked_add() {
571        for coord_inner in 0..ROOM_SIZE {
572            let coord = RoomCoordinate::new(coord_inner).unwrap();
573            for rhs in i8::MIN..=i8::MAX {
574                let sum = coord.checked_add(rhs);
575                assert_eq!(
576                    sum.is_some(),
577                    (0..ROOM_SIZE as i16).contains(&(coord_inner as i16 + rhs as i16))
578                );
579                if let Some(res) = sum {
580                    assert_eq!(res.u8(), (coord_inner as i16 + rhs as i16) as u8);
581                }
582            }
583        }
584    }
585
586    #[test]
587    fn saturating_add() {
588        for coord_inner in 0..ROOM_SIZE {
589            let coord = RoomCoordinate::new(coord_inner).unwrap();
590            for rhs in i8::MIN..=i8::MAX {
591                assert_eq!(
592                    coord.saturating_add(rhs).u8(),
593                    (coord_inner as i16 + rhs as i16).clamp(0, ROOM_SIZE as i16 - 1) as u8
594                )
595            }
596        }
597    }
598
599    #[test]
600    fn index_room_size() {
601        let mut base: Box<[u8; ROOM_USIZE]> = (0..50)
602            .collect::<Vec<u8>>()
603            .into_boxed_slice()
604            .try_into()
605            .unwrap();
606        for i in 0..ROOM_SIZE {
607            let coord = RoomCoordinate::new(i).unwrap();
608            assert_eq!(base[coord], i);
609            base[coord] += 1;
610        }
611        base.iter()
612            .copied()
613            .zip(1..(ROOM_SIZE + 1))
614            .for_each(|(actual, expected)| assert_eq!(actual, expected));
615    }
616
617    #[test]
618    fn index_room_area() {
619        let mut base: Box<[u16; ROOM_AREA]> = Box::new([0; ROOM_AREA]);
620        for i in 0..ROOM_USIZE {
621            for j in 0..ROOM_USIZE {
622                base[i * ROOM_USIZE + j] = i as u16 * ROOM_SIZE as u16;
623            }
624        }
625
626        for i in 0..ROOM_SIZE {
627            let coord = RoomCoordinate::new(i).unwrap();
628            assert!(base[coord]
629                .iter()
630                .copied()
631                .all(|val| val == i as u16 * ROOM_SIZE as u16));
632            for j in 0..ROOM_USIZE {
633                base[coord][j] += j as u16;
634            }
635        }
636
637        assert_eq!(
638            (0..ROOM_AREA as u16).collect::<Vec<u16>>().as_slice(),
639            base.as_slice()
640        );
641    }
642}