Skip to content

Commit

Permalink
Add checked and saturating add functions on RoomCoordinate/RoomXY (#452)
Browse files Browse the repository at this point in the history
  • Loading branch information
shanemadden authored Oct 12, 2023
1 parent 828a759 commit 7c18449
Showing 1 changed file with 167 additions and 1 deletion.
168 changes: 167 additions & 1 deletion src/local/room_coordinate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{convert::TryFrom, error::Error, fmt};

use serde::{de, Deserialize, Deserializer, Serialize, Serializer};

use crate::constants::ROOM_SIZE;
use crate::constants::{Direction, ROOM_SIZE};

pub(crate) const ROOM_AREA: usize = (ROOM_SIZE as usize) * (ROOM_SIZE as usize);

Expand Down Expand Up @@ -64,6 +64,8 @@ pub fn terrain_index_to_xy(idx: usize) -> RoomXY {
pub struct RoomCoordinate(u8);

impl RoomCoordinate {
/// Create a `RoomCoordinate` from a `u8`, returning an error if the
/// coordinate is not in the valid room size range
#[inline]
pub const fn new(coord: u8) -> Result<Self, OutOfBoundsError> {
if coord < ROOM_SIZE {
Expand All @@ -73,6 +75,9 @@ impl RoomCoordinate {
}
}

/// Create a `RoomCoordinate` from a `u8`, without checking whether it's in
/// the range of valid values.
///
/// # Safety
/// Calling this method with `coord >= ROOM_SIZE` can result in undefined
/// behaviour when the resulting `RoomCoordinate` is used.
Expand All @@ -85,9 +90,69 @@ impl RoomCoordinate {
RoomCoordinate(coord)
}

/// Get the integer value of this coordinate
pub const fn u8(self) -> u8 {
self.0
}

/// Get the coordinate adjusted by a certain value, returning `None` if the
/// result is outside the valid range.
///
/// Example usage:
///
/// ```
/// use screeps::local::RoomCoordinate;
///
/// let zero = RoomCoordinate::new(0).unwrap();
/// let forty_nine = RoomCoordinate::new(49).unwrap();
///
/// assert_eq!(zero.checked_add(1), Some(RoomCoordinate::new(1).unwrap()));
/// assert_eq!(zero.checked_add(-1), None);
/// assert_eq!(zero.checked_add(49), Some(forty_nine));
/// assert_eq!(forty_nine.checked_add(1), None);
/// ```
pub fn checked_add(self, rhs: i8) -> Option<RoomCoordinate> {
match (self.0 as i8).checked_add(rhs) {
Some(result) => match result {
// less than 0
i8::MIN..=-1 => None,
// greater than 49
50..=i8::MAX => None,
// SAFETY: we've checked that this coord is in the valid range
c => Some(unsafe { RoomCoordinate::unchecked_new(c as u8) }),
},
None => None,
}
}

/// Get the coordinate adjusted by a certain value, saturating at the edges
/// of the room if the result would be outside of the valid range.
///
/// Example usage:
///
/// ```
/// use screeps::local::RoomCoordinate;
///
/// let zero = RoomCoordinate::new(0).unwrap();
/// let forty_nine = RoomCoordinate::new(49).unwrap();
///
/// assert_eq!(zero.saturating_add(1), RoomCoordinate::new(1).unwrap());
/// assert_eq!(zero.saturating_add(-1), zero);
/// assert_eq!(zero.saturating_add(i8::MAX), forty_nine);
/// assert_eq!(forty_nine.saturating_add(1), forty_nine);
/// assert_eq!(forty_nine.saturating_add(i8::MIN), zero);
/// ```
pub fn saturating_add(self, rhs: i8) -> RoomCoordinate {
let result = match (self.0 as i8).saturating_add(rhs) {
// less than 0, saturate to 0
i8::MIN..=-1 => 0,
// greater than 49, saturate to 49
50..=i8::MAX => ROOM_SIZE - 1,
c => c as u8,
};
// SAFETY: we've ensured that this coord is in the valid range
unsafe { RoomCoordinate::unchecked_new(result) }
}
}

impl fmt::Display for RoomCoordinate {
Expand All @@ -103,6 +168,9 @@ pub struct RoomXY {
}

impl RoomXY {
/// Create a `RoomXY` from a pair of `u8`, without checking whether it's in
/// the range of valid values.
///
/// # Safety
/// Calling this method with `x >= ROOM_SIZE` or `y >= ROOM_SIZE` can result
/// in undefined behaviour when the resulting `RoomXY` is used.
Expand All @@ -113,6 +181,104 @@ impl RoomXY {
y: RoomCoordinate::unchecked_new(y),
}
}

/// Get the coordinate adjusted by a certain value, returning `None` if the
/// result is outside the valid room area.
///
/// Example usage:
///
/// ```
/// use screeps::local::RoomXY;
///
/// let zero = unsafe { RoomXY::unchecked_new(0, 0) };
/// let one = unsafe { RoomXY::unchecked_new(1, 1) };
/// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) };
///
/// assert_eq!(zero.checked_add((1, 1)), Some(one));
/// assert_eq!(zero.checked_add((-1, 0)), None);
/// assert_eq!(zero.checked_add((49, 49)), Some(forty_nine));
/// assert_eq!(forty_nine.checked_add((1, 1)), None);
/// ```
pub fn checked_add(self, rhs: (i8, i8)) -> Option<RoomXY> {
let x = match self.x.checked_add(rhs.0) {
Some(x) => x,
None => return None,
};
let y = match self.y.checked_add(rhs.1) {
Some(y) => y,
None => return None,
};
Some(RoomXY { x, y })
}

/// Get the coordinate adjusted by a certain value, saturating at the edges
/// of the room if the result would be outside the valid room area.
///
/// Example usage:
///
/// ```
/// use screeps::local::RoomXY;
///
/// let zero = unsafe { RoomXY::unchecked_new(0, 0) };
/// let one = unsafe { RoomXY::unchecked_new(1, 1) };
/// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) };
///
/// assert_eq!(zero.saturating_add((1, 1)), one);
/// assert_eq!(zero.saturating_add((-1, 0)), zero);
/// assert_eq!(zero.saturating_add((49, 49)), forty_nine);
/// assert_eq!(zero.saturating_add((i8::MAX, i8::MAX)), forty_nine);
/// assert_eq!(forty_nine.saturating_add((1, 1)), forty_nine);
/// assert_eq!(forty_nine.saturating_add((i8::MIN, i8::MIN)), zero);
/// ```
pub fn saturating_add(self, rhs: (i8, i8)) -> RoomXY {
let x = self.x.saturating_add(rhs.0);
let y = self.y.saturating_add(rhs.1);
RoomXY { x, y }
}

/// Get the neighbor of a given `RoomXY` in the given direction, returning
/// `None` if the result is outside the valid room area.
///
/// Example usage:
///
/// ```
/// use screeps::{constants::Direction::*, local::RoomXY};
///
/// let zero = unsafe { RoomXY::unchecked_new(0, 0) };
/// let one = unsafe { RoomXY::unchecked_new(1, 1) };
/// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) };
///
/// assert_eq!(zero.checked_add_direction(BottomRight), Some(one));
/// assert_eq!(zero.checked_add_direction(TopLeft), None);
/// assert_eq!(one.checked_add_direction(TopLeft), Some(zero));
/// assert_eq!(forty_nine.checked_add_direction(BottomRight), None);
/// ```
pub fn checked_add_direction(self, rhs: Direction) -> Option<RoomXY> {
let (dx, dy) = rhs.into();
self.checked_add((dx as i8, dy as i8))
}

/// Get the neighbor of a given `RoomXY` in the given direction, saturating
/// at the edges if the result is outside the valid room area.
///
/// Example usage:
///
/// ```
/// use screeps::{constants::Direction::*, local::RoomXY};
///
/// let zero = unsafe { RoomXY::unchecked_new(0, 0) };
/// let one = unsafe { RoomXY::unchecked_new(1, 1) };
/// let forty_nine = unsafe { RoomXY::unchecked_new(49, 49) };
///
/// assert_eq!(zero.saturating_add_direction(BottomRight), one);
/// assert_eq!(zero.saturating_add_direction(TopLeft), zero);
/// assert_eq!(one.saturating_add_direction(TopLeft), zero);
/// assert_eq!(forty_nine.saturating_add_direction(BottomRight), forty_nine);
/// ```
pub fn saturating_add_direction(self, rhs: Direction) -> RoomXY {
let (dx, dy) = rhs.into();
self.saturating_add((dx as i8, dy as i8))
}
}

impl fmt::Display for RoomXY {
Expand Down

0 comments on commit 7c18449

Please sign in to comment.