use std::fmt;
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use super::room_coordinate::{OutOfBoundsError, RoomCoordinate};
use crate::constants::{Direction, ROOM_SIZE};
pub(crate) const ROOM_AREA: usize = (ROOM_SIZE as usize) * (ROOM_SIZE as usize);
/// Converts a [`RoomXY`] coordinate pair to a linear index appropriate for use
/// with the internal representation of a [`CostMatrix`] or [`LocalCostMatrix`].
///
/// [`CostMatrix`]: crate::objects::CostMatrix
/// [`LocalCostMatrix`]: crate::local::LocalCostMatrix
#[inline]
pub const fn xy_to_linear_index(xy: RoomXY) -> usize {
xy.x.u8() as usize * ROOM_SIZE as usize + xy.y.u8() as usize
}
/// Converts a linear index from the internal representation of a [`CostMatrix`]
/// or [`LocalCostMatrix`] to a [`RoomXY`] coordinate pair for the position the
/// index represents.
///
/// [`CostMatrix`]: crate::objects::CostMatrix
/// [`LocalCostMatrix`]: crate::local::LocalCostMatrix
#[inline]
pub fn linear_index_to_xy(idx: usize) -> RoomXY {
assert!(idx < ROOM_AREA, "Out of bounds index: {idx}");
// SAFETY: bounds checking above ensures both are within range.
RoomXY {
x: unsafe { RoomCoordinate::unchecked_new((idx / (ROOM_SIZE as usize)) as u8) },
y: unsafe { RoomCoordinate::unchecked_new((idx % (ROOM_SIZE as usize)) as u8) },
}
}
/// Converts a [`RoomXY`] coordinate pair to a terrain index appropriate for use
/// with the internal representation of [`RoomTerrain`] or [`LocalRoomTerrain`].
///
/// [`RoomTerrain`]: crate::objects::RoomTerrain
/// [`LocalRoomTerrain`]: crate::local::LocalRoomTerrain
#[inline]
pub const fn xy_to_terrain_index(xy: RoomXY) -> usize {
xy.y.u8() as usize * ROOM_SIZE as usize + xy.x.u8() as usize
}
/// Converts a terrain index from the internal representation of a
/// [`RoomTerrain`] or [`LocalRoomTerrain`] to a [`RoomXY`] coordinate pair for
/// the position the index represents.
///
/// [`RoomTerrain`]: crate::objects::RoomTerrain
/// [`LocalRoomTerrain`]: crate::local::LocalRoomTerrain
#[inline]
pub fn terrain_index_to_xy(idx: usize) -> RoomXY {
assert!(idx < ROOM_AREA, "Out of bounds index: {idx}");
// SAFETY: bounds checking above ensures both are within range.
RoomXY {
x: unsafe { RoomCoordinate::unchecked_new((idx % (ROOM_SIZE as usize)) as u8) },
y: unsafe { RoomCoordinate::unchecked_new((idx / (ROOM_SIZE as usize)) as u8) },
}
}
/// An X/Y pair representing a given coordinate relative to any room.
#[derive(Debug, Default, Hash, Clone, Copy, PartialEq, Eq)]
pub struct RoomXY {
pub x: RoomCoordinate,
pub y: RoomCoordinate,
}
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.
#[inline]
pub unsafe fn unchecked_new(x: u8, y: u8) -> Self {
RoomXY {
x: RoomCoordinate::unchecked_new(x),
y: RoomCoordinate::unchecked_new(y),
}
}
/// Get whether this coordinate pair represents an edge position (0 or 49
/// for either coordinate)
pub const fn is_room_edge(self) -> bool {
self.x.is_room_edge() || self.y.is_room_edge()
}
/// 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 {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
impl From<RoomXY> for (u8, u8) {
fn from(xy: RoomXY) -> (u8, u8) {
(xy.x.0, xy.y.0)
}
}
impl TryFrom<(u8, u8)> for RoomXY {
type Error = OutOfBoundsError;
fn try_from(xy: (u8, u8)) -> Result<RoomXY, OutOfBoundsError> {
Ok(RoomXY {
x: RoomCoordinate::try_from(xy.0)?,
y: RoomCoordinate::try_from(xy.1)?,
})
}
}
impl From<(RoomCoordinate, RoomCoordinate)> for RoomXY {
fn from(xy: (RoomCoordinate, RoomCoordinate)) -> RoomXY {
RoomXY { x: xy.0, y: xy.1 }
}
}
impl From<RoomXY> for (RoomCoordinate, RoomCoordinate) {
fn from(xy: RoomXY) -> (RoomCoordinate, RoomCoordinate) {
(xy.x, xy.y)
}
}
#[derive(Serialize, Deserialize)]
struct ReadableXY {
x: RoomCoordinate,
y: RoomCoordinate,
}
impl From<ReadableXY> for RoomXY {
fn from(ReadableXY { x, y }: ReadableXY) -> RoomXY {
RoomXY { x, y }
}
}
impl From<RoomXY> for ReadableXY {
fn from(RoomXY { x, y }: RoomXY) -> ReadableXY {
ReadableXY { x, y }
}
}
impl Serialize for RoomXY {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
ReadableXY::from(*self).serialize(serializer)
} else {
let xy: (u8, u8) = (*self).into();
let packed: u16 = ((xy.0 as u16) << 8) | (xy.1 as u16);
packed.serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for RoomXY {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
ReadableXY::deserialize(deserializer).map(Into::into)
} else {
let packed = u16::deserialize(deserializer)?;
let xy = (((packed >> 8) & 0xFF) as u8, (packed & 0xFF) as u8);
RoomXY::try_from(xy).map_err(|err: OutOfBoundsError| {
de::Error::invalid_value(
de::Unexpected::Unsigned(err.0 as u64),
&format!("a non-negative integer less-than {ROOM_SIZE}").as_str(),
)
})
}
}
}