Skip to content

Commit

Permalink
(Improvement) Add additional calculation methods to RoomXY (#521)
Browse files Browse the repository at this point in the history
  • Loading branch information
jciskey authored Jun 28, 2024
1 parent 12aed92 commit 21b044b
Show file tree
Hide file tree
Showing 5 changed files with 607 additions and 1 deletion.
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,28 @@ Unreleased
every position set to a given `u8` value
- Implement `JsCollectionIntoValue` and `JsCollectionFromValue` for `IntershardResourceType` and
`u32` to allow `game::resources()` return value to be used as expected
- Add static function `RoomXY::new` to allow creating a new `RoomXY` from `RoomCoordinates`
- Add static function `RoomXY::checked_new` to allow creating a new `RoomXY` from a (u8, u8)
pair, while checking the validity of the coordinates provided
- Add function `RoomXY::towards` which returns a `RoomXY` between two `RoomXY` positions,
rounding towards the start position if necessary
- Add function `RoomXY::between` which returns a `RoomXY` between two `RoomXY` positions,
rounding towards the target position if necessary
- Add function `RoomXY::midpoint_between` which returns the `RoomXY` midpoint between
two `RoomXY` positions, rounding towards the target position if necessary
- Add function `RoomXY::offset` which modifies a `RoomXY` in-place by a (i8, i8) offset
- Add function `RoomXY::get_direction_to` which returns a `Direction` that is closest to
a given `RoomXY` position
- Add function `RoomXY::get_range_to` which returns the Chebyshev Distance to a given
`RoomXY` position
- Add function `RoomXY::in_range_to` which returns whether the Chebyshev Distance to a given
`RoomXY` position is less-than-or-equal-to a given distance
- Add function `RoomXY::is_near_to` which returns whether a given `RoomXY` position is adjacent
- Add function `RoomXY::is_equal_to` which returns whether a given `RoomXY` position is
the same position
- Implement the `PartialOrd` and `Ord` traits for `RoomXY`
- Implement the `Add<(i8, i8)>`, `Add<Direction>`, `Sub<(i8, i8)>`, `Sub<Direction>`,
and `Sub<RoomXY>` traits for `RoomXY`

0.21.0 (2024-05-14)
===================
Expand Down
32 changes: 31 additions & 1 deletion src/local/room_xy.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
use std::fmt;
use std::{cmp::Ordering, fmt};

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

use super::room_coordinate::{OutOfBoundsError, RoomCoordinate};
use crate::constants::{Direction, ROOM_SIZE};

mod approximate_offsets;
mod extra_math;
mod game_math;

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
Expand Down Expand Up @@ -67,6 +71,19 @@ pub struct RoomXY {
}

impl RoomXY {
/// Create a new `RoomXY` from a pair of `RoomCoordinate`.
#[inline]
pub fn new(x: RoomCoordinate, y: RoomCoordinate) -> Self {
RoomXY { x, y }
}

/// Create a new `RoomXY` from a pair of `u8`, checking that they're in
/// the range of valid values.
#[inline]
pub fn checked_new(x: u8, y: u8) -> Result<RoomXY, OutOfBoundsError> {
RoomXY::try_from((x, y))
}

/// Create a `RoomXY` from a pair of `u8`, without checking whether it's in
/// the range of valid values.
///
Expand Down Expand Up @@ -229,6 +246,19 @@ impl RoomXY {
}
}

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)
Expand Down
236 changes: 236 additions & 0 deletions src/local/room_xy/approximate_offsets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
//! Methods related to approximating in-room positions
//! between other in-room positions.

use super::RoomXY;

impl RoomXY {
/// Calculates an approximate midpoint between this point and the target.
///
/// In case of a tie, rounds towards this point.
///
/// If `distance_towards_target` is bigger than the distance to the target,
/// the target is returned.
///
/// # Example
///
/// ```
/// # use screeps::RoomXY;
///
/// // Exact distances
/// let start = RoomXY::checked_new(10, 10).unwrap();
/// let target = RoomXY::checked_new(10, 15).unwrap();
/// assert_eq!(
/// start.towards(target, 1),
/// RoomXY::checked_new(10, 11).unwrap()
/// );
/// assert_eq!(
/// start.towards(target, 4),
/// RoomXY::checked_new(10, 14).unwrap()
/// );
/// assert_eq!(
/// start.towards(target, 10),
/// RoomXY::checked_new(10, 15).unwrap()
/// );
///
/// // Approximate/rounded distances
/// let start = RoomXY::checked_new(10, 10).unwrap();
/// let target_1 = RoomXY::checked_new(15, 20).unwrap();
/// let target_2 = RoomXY::checked_new(0, 5).unwrap();
/// assert_eq!(
/// start.towards(target_1, 1),
/// RoomXY::checked_new(10, 11).unwrap()
/// );
/// assert_eq!(
/// start.towards(target_1, 9),
/// RoomXY::checked_new(14, 19).unwrap()
/// );
/// assert_eq!(
/// start.towards(target_2, 1),
/// RoomXY::checked_new(9, 10).unwrap()
/// );
/// ```
pub fn towards(self, target: RoomXY, distance_towards_target: i8) -> RoomXY {
let (offset_x, offset_y) = target - self;
let total_distance = offset_x.abs().max(offset_y.abs());
if distance_towards_target > total_distance {
return target;
}

let new_offset_x = (offset_x * distance_towards_target) / total_distance;
let new_offset_y = (offset_y * distance_towards_target) / total_distance;

self + (new_offset_x, new_offset_y)
}

/// Calculates an approximate midpoint between this point and the target.
///
/// In case of a tie, rounds towards the target.
///
/// If `distance_from_target` is bigger than the distance to the target,
/// this position is returned.
///
/// Note: This is essentially the same as [`RoomXY::towards`], just rounding
/// towards the target instead of the starting position.
///
/// # Example
///
/// ```
/// # use screeps::RoomXY;
///
/// // Exact distances
/// let start = RoomXY::checked_new(10, 15).unwrap();
/// let target = RoomXY::checked_new(10, 10).unwrap();
/// assert_eq!(
/// start.between(target, 1),
/// RoomXY::checked_new(10, 11).unwrap()
/// );
/// assert_eq!(
/// start.between(target, 4),
/// RoomXY::checked_new(10, 14).unwrap()
/// );
/// assert_eq!(
/// start.between(target, 10),
/// RoomXY::checked_new(10, 15).unwrap()
/// );
///
/// // Approximate/rounded distances
/// let start_1 = RoomXY::checked_new(15, 20).unwrap();
/// let start_2 = RoomXY::checked_new(0, 5).unwrap();
/// let target = RoomXY::checked_new(10, 10).unwrap();
/// assert_eq!(
/// start_1.between(target, 1),
/// RoomXY::checked_new(10, 11).unwrap()
/// );
/// assert_eq!(
/// start_1.between(target, 9),
/// RoomXY::checked_new(14, 19).unwrap()
/// );
/// assert_eq!(
/// start_2.between(target, 1),
/// RoomXY::checked_new(9, 10).unwrap()
/// );
/// ```
pub fn between(self, target: RoomXY, distance_from_target: i8) -> RoomXY {
target.towards(self, distance_from_target)
}

/// Calculates an approximate midpoint between this point and the target.
///
/// In case of a tie, rounds towards the target.
///
/// # Example
///
/// ```
/// # use screeps::RoomXY;
///
/// // Exact distances
/// let start = RoomXY::checked_new(10, 10).unwrap();
///
/// let target_1 = RoomXY::checked_new(10, 16).unwrap();
/// assert_eq!(
/// start.midpoint_between(target_1),
/// RoomXY::checked_new(10, 13).unwrap()
/// );
///
/// let target_2 = RoomXY::checked_new(20, 10).unwrap();
/// assert_eq!(
/// start.midpoint_between(target_2),
/// RoomXY::checked_new(15, 10).unwrap()
/// );
///
/// let target_3 = RoomXY::checked_new(12, 12).unwrap();
/// assert_eq!(
/// start.midpoint_between(target_3),
/// RoomXY::checked_new(11, 11).unwrap()
/// );
///
/// let target_4 = RoomXY::checked_new(4, 4).unwrap();
/// assert_eq!(
/// start.midpoint_between(target_4),
/// RoomXY::checked_new(7, 7).unwrap()
/// );
///
/// // Approximate/rounded distances
/// let start = RoomXY::checked_new(10, 10).unwrap();
///
/// let target_1 = RoomXY::checked_new(10, 15).unwrap();
/// assert_eq!(
/// start.midpoint_between(target_1),
/// RoomXY::checked_new(10, 13).unwrap()
/// );
///
/// let target_2 = RoomXY::checked_new(19, 10).unwrap();
/// assert_eq!(
/// start.midpoint_between(target_2),
/// RoomXY::checked_new(15, 10).unwrap()
/// );
///
/// let target_3 = RoomXY::checked_new(11, 11).unwrap();
/// assert_eq!(
/// start.midpoint_between(target_3),
/// RoomXY::checked_new(11, 11).unwrap()
/// );
///
/// let target_4 = RoomXY::checked_new(15, 15).unwrap();
/// assert_eq!(
/// start.midpoint_between(target_4),
/// RoomXY::checked_new(13, 13).unwrap()
/// );
/// ```
pub fn midpoint_between(self, target: RoomXY) -> RoomXY {
let (offset_x, offset_y) = self - target;

let new_offset_x = offset_x / 2;
let new_offset_y = offset_y / 2;

target + (new_offset_x, new_offset_y)
}
}

#[cfg(test)]
mod test {
use super::RoomXY;

fn pos(x: u8, y: u8) -> RoomXY {
RoomXY::checked_new(x, y).unwrap()
}

#[test]
fn towards_accurate() {
let start = pos(10, 10);
assert_eq!(start.towards(pos(10, 15), 1), pos(10, 11));
assert_eq!(start.towards(pos(10, 15), 4), pos(10, 14));
assert_eq!(start.towards(pos(10, 15), 10), pos(10, 15));
assert_eq!(start.towards(pos(15, 15), 1), pos(11, 11));
assert_eq!(start.towards(pos(15, 15), 3), pos(13, 13));
assert_eq!(start.towards(pos(15, 20), 2), pos(11, 12));
assert_eq!(start.towards(pos(0, 5), 2), pos(8, 9));
}
#[test]
fn towards_approximate() {
let start = pos(10, 10);
assert_eq!(start.towards(pos(15, 20), 1), pos(10, 11));
assert_eq!(start.towards(pos(15, 20), 9), pos(14, 19));
assert_eq!(start.towards(pos(0, 5), 1), pos(9, 10));
}
#[test]
fn midpoint_accurate() {
let start = pos(10, 10);
assert_eq!(start.midpoint_between(pos(10, 16)), pos(10, 13));
assert_eq!(start.midpoint_between(pos(20, 10)), pos(15, 10));
assert_eq!(start.midpoint_between(pos(12, 12)), pos(11, 11));
assert_eq!(start.midpoint_between(pos(4, 4)), pos(7, 7));
}
#[test]
fn midpoint_approximate() {
let start = pos(10, 10);
assert_eq!(start.midpoint_between(pos(10, 15)), pos(10, 13));
assert_eq!(start.midpoint_between(pos(19, 10)), pos(15, 10));
assert_eq!(start.midpoint_between(pos(11, 11)), pos(11, 11));
assert_eq!(start.midpoint_between(pos(15, 15)), pos(13, 13));
assert_eq!(start.midpoint_between(pos(15, 25)), pos(13, 18));
assert_eq!(start.midpoint_between(pos(9, 10)), pos(9, 10));
assert_eq!(start.midpoint_between(pos(7, 10)), pos(8, 10));
assert_eq!(start.midpoint_between(pos(1, 3)), pos(5, 6));
}
}
Loading

0 comments on commit 21b044b

Please sign in to comment.