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}