use std::{
cmp::Ordering,
fmt,
ops::{Index, IndexMut},
};
use serde::{de, Deserialize, Deserializer, Serialize, Serializer};
use super::room_coordinate::{OutOfBoundsError, RoomCoordinate};
use crate::constants::{Direction, ROOM_AREA, ROOM_USIZE};
mod approximate_offsets;
mod extra_math;
mod game_math;
#[inline]
pub const fn xy_to_linear_index(xy: RoomXY) -> usize {
xy.x.u8() as usize * ROOM_USIZE + xy.y.u8() as usize
}
#[inline]
pub fn linear_index_to_xy(idx: usize) -> RoomXY {
assert!(idx < ROOM_AREA, "Out of bounds index: {idx}");
RoomXY {
x: unsafe { RoomCoordinate::unchecked_new((idx / (ROOM_USIZE)) as u8) },
y: unsafe { RoomCoordinate::unchecked_new((idx % (ROOM_USIZE)) as u8) },
}
}
#[inline]
pub const fn xy_to_terrain_index(xy: RoomXY) -> usize {
xy.y.u8() as usize * ROOM_USIZE + xy.x.u8() as usize
}
#[inline]
pub fn terrain_index_to_xy(idx: usize) -> RoomXY {
assert!(idx < ROOM_AREA, "Out of bounds index: {idx}");
RoomXY {
x: unsafe { RoomCoordinate::unchecked_new((idx % (ROOM_USIZE)) as u8) },
y: unsafe { RoomCoordinate::unchecked_new((idx / (ROOM_USIZE)) as u8) },
}
}
#[derive(Debug, Default, Hash, Clone, Copy, PartialEq, Eq)]
pub struct RoomXY {
pub x: RoomCoordinate,
pub y: RoomCoordinate,
}
impl RoomXY {
#[inline]
pub fn new(x: RoomCoordinate, y: RoomCoordinate) -> Self {
RoomXY { x, y }
}
#[inline]
pub fn checked_new(x: u8, y: u8) -> Result<RoomXY, OutOfBoundsError> {
RoomXY::try_from((x, y))
}
#[inline]
pub unsafe fn unchecked_new(x: u8, y: u8) -> Self {
RoomXY {
x: RoomCoordinate::unchecked_new(x),
y: RoomCoordinate::unchecked_new(y),
}
}
pub const fn is_room_edge(self) -> bool {
self.x.is_room_edge() || self.y.is_room_edge()
}
pub fn checked_add(self, rhs: (i8, i8)) -> Option<RoomXY> {
let x = self.x.checked_add(rhs.0)?;
let y = self.y.checked_add(rhs.1)?;
Some(RoomXY { x, y })
}
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 }
}
pub fn checked_add_direction(self, rhs: Direction) -> Option<RoomXY> {
let (dx, dy) = rhs.into();
self.checked_add((dx as i8, dy as i8))
}
pub fn saturating_add_direction(self, rhs: Direction) -> RoomXY {
let (dx, dy) = rhs.into();
self.saturating_add((dx as i8, dy as i8))
}
pub fn neighbors(self) -> Vec<RoomXY> {
Direction::iter()
.filter_map(|dir| self.checked_add_direction(*dir))
.collect()
}
}
impl PartialOrd for RoomXY {
#[inline]
fn partial_cmp(&self, other: &RoomXY) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for RoomXY {
fn cmp(&self, other: &Self) -> Ordering {
(self.y, self.x).cmp(&(other.y, other.x))
}
}
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.u8(), xy.y.u8())
}
}
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_USIZE}").as_str(),
)
})
}
}
}
#[repr(transparent)]
pub struct XMajor<T>(pub [[T; ROOM_USIZE]; ROOM_USIZE]);
impl<T> XMajor<T> {
pub fn from_ref(arr: &[[T; ROOM_USIZE]; ROOM_USIZE]) -> &Self {
unsafe { &*(arr as *const [[T; ROOM_USIZE]; ROOM_USIZE] as *const Self) }
}
pub fn from_flat_ref(arr: &[T; ROOM_AREA]) -> &Self {
Self::from_ref(unsafe {
&*(arr as *const [T; ROOM_AREA] as *const [[T; ROOM_USIZE]; ROOM_USIZE])
})
}
pub fn from_mut(arr: &mut [[T; ROOM_USIZE]; ROOM_USIZE]) -> &mut Self {
unsafe { &mut *(arr as *mut [[T; ROOM_USIZE]; ROOM_USIZE] as *mut Self) }
}
pub fn from_flat_mut(arr: &mut [T; ROOM_AREA]) -> &mut Self {
Self::from_mut(unsafe {
&mut *(arr as *mut [T; ROOM_AREA] as *mut [[T; ROOM_USIZE]; ROOM_USIZE])
})
}
}
impl<T> Index<RoomXY> for XMajor<T> {
type Output = T;
fn index(&self, index: RoomXY) -> &Self::Output {
&self.0[index.x][index.y]
}
}
impl<T> IndexMut<RoomXY> for XMajor<T> {
fn index_mut(&mut self, index: RoomXY) -> &mut Self::Output {
&mut self.0[index.x][index.y]
}
}
#[repr(transparent)]
pub struct YMajor<T>(pub [[T; ROOM_USIZE]; ROOM_USIZE]);
impl<T> YMajor<T> {
pub fn from_ref(arr: &[[T; ROOM_USIZE]; ROOM_USIZE]) -> &Self {
unsafe { &*(arr as *const [[T; ROOM_USIZE]; ROOM_USIZE] as *const Self) }
}
pub fn from_flat_ref(arr: &[T; ROOM_AREA]) -> &Self {
Self::from_ref(unsafe {
&*(arr as *const [T; ROOM_AREA] as *const [[T; ROOM_USIZE]; ROOM_USIZE])
})
}
pub fn from_mut(arr: &mut [[T; ROOM_USIZE]; ROOM_USIZE]) -> &mut Self {
unsafe { &mut *(arr as *mut [[T; ROOM_USIZE]; ROOM_USIZE] as *mut Self) }
}
pub fn from_flat_mut(arr: &mut [T; ROOM_AREA]) -> &mut Self {
Self::from_mut(unsafe {
&mut *(arr as *mut [T; ROOM_AREA] as *mut [[T; ROOM_USIZE]; ROOM_USIZE])
})
}
}
impl<T> Index<RoomXY> for YMajor<T> {
type Output = T;
fn index(&self, index: RoomXY) -> &Self::Output {
&self.0[index.y][index.x]
}
}
impl<T> IndexMut<RoomXY> for YMajor<T> {
fn index_mut(&mut self, index: RoomXY) -> &mut Self::Output {
&mut self.0[index.y][index.x]
}
}