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