1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
//! Utilities for doing math on [`Position`]s which are present in the
//! JavaScript API.
use crate::constants::Direction;
use super::Position;
impl Position {
/// Gets linear direction to the specified position.
///
/// Note that this chooses between `Top`/`Bottom`/`Left`/`Right` and
/// `TopLeft`/`TopRight`/`BottomLeft`/`BottomRight` by the magnitude in both
/// directions. For instance, [`Direction::Top`] can be returned even
/// if the target has a slightly different `x` coordinate.
pub fn get_direction_to(self, target: Position) -> Option<Direction> {
// Logic copied from https://github.com/screeps/engine/blob/020ba168a1fde9a8072f9f1c329d5c0be8b440d7/src/utils.js#L73-L107
let (dx, dy) = target - self;
if dx.abs() > dy.abs() * 2 {
if dx > 0 {
Some(Direction::Right)
} else {
Some(Direction::Left)
}
} else if dy.abs() > dx.abs() * 2 {
if dy > 0 {
Some(Direction::Bottom)
} else {
Some(Direction::Top)
}
} else if dx > 0 && dy > 0 {
Some(Direction::BottomRight)
} else if dx > 0 && dy < 0 {
Some(Direction::TopRight)
} else if dx < 0 && dy > 0 {
Some(Direction::BottomLeft)
} else if dx < 0 && dy < 0 {
Some(Direction::TopLeft)
} else {
None
}
}
/// Gets linear range to the specified position.
///
/// Linear range (also called Chebyshev Distance) is an alternate
/// calculation of distance, calculated as the greater of the distance along
/// the x axis or the y axis. Most calculations in Screeps use this distance
/// metric. For more information see [Chebeshev Distance](https://en.wikipedia.org/wiki/Chebyshev_distance).
///
/// This operates on positions as "world positions", and will return an
/// accurate range for positions in different rooms. Note that the
/// corresponding JavaScript method, `RoomPosition.getRangeTo` returns
/// `Infinity` if given positions in different rooms.
///
/// # Examples
/// ```rust
/// # use screeps::Position;
/// // (5, 10) in E0N0
/// let pos_1 = Position::from_world_coords(5, 10);
/// // (8, 15) in E0N0
/// let pos_2 = Position::from_world_coords(8, 15);
/// // The differences are 3 along the X axis and 5 along the Y axis
/// // so the linear distance is 5.
/// assert_eq!(pos_1.get_range_to(pos_2), 5);
/// ```
#[doc(alias = "distance")]
#[inline]
pub fn get_range_to(self, target: Position) -> u32 {
let (dx, dy) = self - target;
dx.abs().max(dy.abs()) as u32
}
/// Checks whether this position is in the given range of another position.
///
/// Linear range (also called Chebyshev Distance) is an alternate
/// calculation of distance, calculated as the greater of the distance along
/// the x axis or the y axis. Most calculations in Screeps use this distance
/// metric. For more information see [Chebeshev Distance](https://en.wikipedia.org/wiki/Chebyshev_distance).
///
/// This operates on positions as "world positions", and may return true for
/// positions in different rooms which are still within the given range.
/// Note that the corresponding JavaScript method, `RoomPosition.inRangeTo`,
/// will always return `false` for positions from different rooms.
///
/// # Examples
/// ```rust
/// # use screeps::Position;
/// // (5, 10) in E0N0
/// let pos_1 = Position::from_world_coords(5, 10);
/// // (8, 10) in E0N0
/// let pos_2 = Position::from_world_coords(8, 15);
///
/// // The differences are 3 along the X axis and 0 along the Y axis
/// // so the linear distance is 3.
/// assert_eq!(pos_1.in_range_to(pos_2, 5), true);
///
/// // (8, 15) in E0N0
/// let pos_3 = Position::from_world_coords(8, 15);
///
/// // The differences are 3 along the X axis and 5 along the Y axis
/// // so the linear distance is 5.
/// // `in_range_to` returns true if the linear distance is equal to the range
/// assert_eq!(pos_1.in_range_to(pos_3, 5), true);
///
/// // (20, 20) in E0N0
/// let pos_4 = Position::from_world_coords(20, 20);
/// // The differences are 15 along the X axis and 10 along the Y axis
/// // so the linear distance is 15.
/// assert_eq!(pos_1.in_range_to(pos_4, 5), false);
/// ```
#[doc(alias = "distance")]
#[inline]
pub fn in_range_to(self, target: Position, range: u32) -> bool {
self.get_range_to(target) <= range
}
/// Checks whether this position is the same as the specified position.
///
/// Note that this is equivalent to `this_pos == target.pos()`.
#[inline]
pub fn is_equal_to(self, target: Position) -> bool {
self == target
}
/// True if this position is in the same room as the target, and the range
/// is at most 1.
#[inline]
pub fn is_near_to(self, target: Position) -> bool {
self.room_name() == target.room_name()
&& (u8::from(self.x()) as i32 - u8::from(target.x()) as i32).abs() <= 1
&& (u8::from(self.y()) as i32 - u8::from(target.y()) as i32).abs() <= 1
}
}
#[cfg(test)]
mod test {
use crate::{local::RoomCoordinate, Direction, Position, RoomName};
#[test]
fn test_direction_to() {
let one = unsafe { RoomCoordinate::unchecked_new(1) };
let two = unsafe { RoomCoordinate::unchecked_new(2) };
let a = Position::new(one, one, RoomName::from_coords(1, 1).unwrap());
let b = Position::new(two, two, RoomName::from_coords(1, 1).unwrap());
assert_eq!(a.get_direction_to(b), Some(Direction::BottomRight));
}
}