use core::fmt::Debug;
use std::{cmp::Ordering, fmt};
use crate::{constants::ROOM_SIZE, objects::RoomPosition, HasPosition};
use super::{RoomCoordinate, RoomName, RoomXY, HALF_WORLD_SIZE};
mod approximate_offsets;
mod extra_math;
mod game_math;
mod game_methods;
mod pair_utils;
mod world_utils;
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
#[repr(transparent)]
pub struct Position {
packed: u32,
}
impl fmt::Debug for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Position")
.field("packed", &self.packed)
.field("x", &self.x())
.field("y", &self.y())
.field("room_name", &self.room_name())
.finish()
}
}
impl fmt::Display for Position {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"[room {} pos {},{}]",
self.room_name(),
self.x(),
self.y()
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WorldPositionOutOfBoundsError(pub i32, pub i32);
impl Position {
#[inline]
pub fn new(x: RoomCoordinate, y: RoomCoordinate, room_name: RoomName) -> Self {
Self::from_coords_adjusted_and_room_packed(x.into(), y.into(), room_name.packed_repr())
}
#[inline]
const fn from_coords_and_world_coords_adjusted(x: u8, y: u8, room_x: u32, room_y: u32) -> Self {
Position {
packed: (room_x << 24) | (room_y << 16) | ((x as u32) << 8) | (y as u32),
}
}
#[inline]
const fn from_coords_adjusted_and_room_packed(x: u8, y: u8, room_repr_packed: u16) -> Self {
Position {
packed: ((room_repr_packed as u32) << 16) | ((x as u32) << 8) | (y as u32),
}
}
#[inline]
pub const fn packed_repr(self) -> u32 {
self.packed
}
#[inline]
pub fn from_packed(packed: u32) -> Self {
let x = packed >> 8 & 0xFF;
let y = packed & 0xFF;
assert!(x < ROOM_SIZE as u32, "out of bounds x: {x}");
assert!(y < ROOM_SIZE as u32, "out of bounds y: {y}");
Position { packed }
}
#[inline]
fn room_x(self) -> i32 {
self.room_name().x_coord()
}
#[inline]
fn room_y(self) -> i32 {
self.room_name().y_coord()
}
#[inline]
pub fn x(self) -> RoomCoordinate {
unsafe { RoomCoordinate::unchecked_new((self.packed >> 8 & 0xFF) as u8) }
}
#[inline]
pub fn y(self) -> RoomCoordinate {
unsafe { RoomCoordinate::unchecked_new((self.packed & 0xFF) as u8) }
}
#[inline]
pub fn xy(self) -> RoomXY {
unsafe {
RoomXY::unchecked_new((self.packed >> 8 & 0xFF) as u8, (self.packed & 0xFF) as u8)
}
}
#[inline]
pub fn room_name(self) -> RoomName {
RoomName::from_packed(((self.packed >> 16) & 0xFFFF) as u16)
}
#[inline]
pub fn set_x(&mut self, x: RoomCoordinate) {
self.packed = (self.packed & !(0xFF << 8)) | ((u8::from(x) as u32) << 8);
}
#[inline]
pub fn set_y(&mut self, y: RoomCoordinate) {
self.packed = (self.packed & !0xFF) | (u8::from(y) as u32);
}
#[inline]
pub fn set_room_name(&mut self, room_name: RoomName) {
let room_repr_packed = room_name.packed_repr() as u32;
self.packed = (self.packed & 0xFFFF) | (room_repr_packed << 16);
}
#[inline]
pub fn with_x(mut self, x: RoomCoordinate) -> Self {
self.set_x(x);
self
}
#[inline]
pub fn with_y(mut self, y: RoomCoordinate) -> Self {
self.set_y(y);
self
}
#[inline]
pub fn with_room_name(mut self, room_name: RoomName) -> Self {
self.set_room_name(room_name);
self
}
}
impl PartialOrd for Position {
#[inline]
fn partial_cmp(&self, other: &Position) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Position {
fn cmp(&self, other: &Self) -> Ordering {
self.world_y()
.cmp(&other.world_y())
.then_with(|| self.world_x().cmp(&other.world_x()))
}
}
impl HasPosition for Position {
fn pos(&self) -> Position {
*self
}
}
impl From<RoomPosition> for Position {
fn from(js_pos: RoomPosition) -> Self {
Position::from_packed(js_pos.packed())
}
}
impl From<&RoomPosition> for Position {
fn from(js_pos: &RoomPosition) -> Self {
Position::from_packed(js_pos.packed())
}
}
mod serde {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::{Position, RoomCoordinate, RoomName};
#[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct ReadableFormat {
room_name: RoomName,
x: RoomCoordinate,
y: RoomCoordinate,
}
impl From<ReadableFormat> for Position {
fn from(ReadableFormat { room_name, x, y }: ReadableFormat) -> Self {
Position::new(x, y, room_name)
}
}
impl From<Position> for ReadableFormat {
fn from(pos: Position) -> Self {
ReadableFormat {
room_name: pos.room_name(),
x: pos.x(),
y: pos.y(),
}
}
}
impl Serialize for Position {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
ReadableFormat::from(*self).serialize(serializer)
} else {
self.packed_repr().serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for Position {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
ReadableFormat::deserialize(deserializer).map(Into::into)
} else {
u32::deserialize(deserializer).map(Position::from_packed)
}
}
}
}
pub mod serde_position_packed {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use super::Position;
pub fn serialize<S>(pos: &Position, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
pos.packed_repr().serialize(serializer)
}
pub fn deserialize<'de, D>(deserializer: D) -> Result<Position, D::Error>
where
D: Deserializer<'de>,
{
u32::deserialize(deserializer).map(Position::from_packed)
}
}
#[cfg(test)]
mod test {
use super::{Position, RoomCoordinate};
fn gen_test_positions() -> Vec<(u32, (RoomCoordinate, RoomCoordinate, &'static str))> {
unsafe {
vec![
(
2172526892u32,
(
RoomCoordinate::unchecked_new(33),
RoomCoordinate::unchecked_new(44),
"E1N1",
),
),
(
2491351576u32,
(
RoomCoordinate::unchecked_new(2),
RoomCoordinate::unchecked_new(24),
"E20N0",
),
),
(
2139029504u32,
(
RoomCoordinate::unchecked_new(0),
RoomCoordinate::unchecked_new(0),
"W0N0",
),
),
(
2155806720u32,
(
RoomCoordinate::unchecked_new(0),
RoomCoordinate::unchecked_new(0),
"E0N0",
),
),
(
2139095040u32,
(
RoomCoordinate::unchecked_new(0),
RoomCoordinate::unchecked_new(0),
"W0S0",
),
),
(
2155872256u32,
(
RoomCoordinate::unchecked_new(0),
RoomCoordinate::unchecked_new(0),
"E0S0",
),
),
(
2021333800u32,
(
RoomCoordinate::unchecked_new(27),
RoomCoordinate::unchecked_new(40),
"W7N4",
),
),
(
1285u32,
(
RoomCoordinate::unchecked_new(5),
RoomCoordinate::unchecked_new(5),
if cfg!(feature = "sim") {
"sim"
} else {
"W127N127"
},
),
),
]
}
}
#[test]
fn from_u32_accurate() {
for (packed, (x, y, name)) in gen_test_positions().iter().copied() {
let pos = Position::from_packed(packed);
assert_eq!(pos.x(), x);
assert_eq!(pos.y(), y);
assert_eq!(&*pos.room_name().to_array_string(), name);
}
}
#[test]
fn from_args_accurate() {
for (packed, (x, y, name)) in gen_test_positions().iter().copied() {
let pos = Position::new(x, y, name.parse().unwrap());
assert_eq!(pos.packed_repr(), packed);
}
}
}