diff --git a/CHANGELOG.md b/CHANGELOG.md
index 30774372..30da01fa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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)
 ===================
diff --git a/src/local/room_xy.rs b/src/local/room_xy.rs
index 368c9156..c11bb441 100644
--- a/src/local/room_xy.rs
+++ b/src/local/room_xy.rs
@@ -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
@@ -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.
     ///
@@ -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)
diff --git a/src/local/room_xy/approximate_offsets.rs b/src/local/room_xy/approximate_offsets.rs
new file mode 100644
index 00000000..b141b6e6
--- /dev/null
+++ b/src/local/room_xy/approximate_offsets.rs
@@ -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));
+    }
+}
diff --git a/src/local/room_xy/extra_math.rs b/src/local/room_xy/extra_math.rs
new file mode 100644
index 00000000..49b75b39
--- /dev/null
+++ b/src/local/room_xy/extra_math.rs
@@ -0,0 +1,167 @@
+//! Math utilities on `RoomXY` which don't exist in the Screeps API
+//! proper.
+
+use std::ops::{Add, Sub};
+
+use super::RoomXY;
+use crate::constants::Direction;
+
+impl RoomXY {
+    /// Returns a new position offset from this position by the specified x
+    /// coords and y coords.
+    ///
+    /// Unlike [`Position::offset`], this function operates on room coordinates,
+    /// and will panic if the new position overflows the room.
+    ///
+    /// To return a new position rather than modifying in place, use `pos + (x,
+    /// y)`. See the implementation of `Add<(i8, i8)>` for
+    /// [`RoomXY`] further down on this page.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if the new position overflows the room.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use screeps::RoomXY;
+    ///
+    /// let mut pos = RoomXY::checked_new(21, 21).unwrap();
+    /// pos.offset(5, 5);
+    /// assert_eq!(pos, RoomXY::checked_new(26, 26).unwrap());
+    ///
+    /// let mut pos = RoomXY::checked_new(21, 21).unwrap();
+    /// pos.offset(-5, 5);
+    /// assert_eq!(pos, RoomXY::checked_new(16, 26).unwrap());
+    /// ```
+    #[inline]
+    #[track_caller]
+    pub fn offset(&mut self, x: i8, y: i8) {
+        *self = *self + (x, y);
+    }
+}
+
+impl Add<(i8, i8)> for RoomXY {
+    type Output = RoomXY;
+
+    /// Adds an `(x, y)` pair to this position's coordinates.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if the new position is outside standard room bounds.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use screeps::RoomXY;
+    ///
+    /// let pos1 = RoomXY::checked_new(42, 42).unwrap();
+    /// let pos2 = pos1 + (7, 7);
+    /// assert_eq!(pos2, RoomXY::checked_new(49, 49).unwrap());
+    /// ```
+    #[inline]
+    #[track_caller]
+    fn add(self, (x, y): (i8, i8)) -> Self {
+        self.checked_add((x, y)).unwrap()
+    }
+}
+
+impl Add<Direction> for RoomXY {
+    type Output = RoomXY;
+
+    /// Adds a `Direction` to this position's coordinates.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if the new position is outside standard room bounds.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use screeps::{RoomXY, Direction};
+    ///
+    /// let pos1 = RoomXY::checked_new(49, 40).unwrap();
+    /// let pos2 = pos1 + Direction::Top;
+    /// assert_eq!(pos2, RoomXY::checked_new(49, 39).unwrap());
+    /// ```
+    #[inline]
+    #[track_caller]
+    fn add(self, direction: Direction) -> Self {
+        self.checked_add_direction(direction).unwrap()
+    }
+}
+
+impl Sub<(i8, i8)> for RoomXY {
+    type Output = RoomXY;
+
+    /// Subtracts an `(x, y)` pair from this position's coordinates.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if the new position is outside standard room bounds.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use screeps::RoomXY;
+    ///
+    /// let pos1 = RoomXY::checked_new(49, 40).unwrap();
+    /// let pos2 = pos1 - (49, 0);
+    /// assert_eq!(pos2, RoomXY::checked_new(0, 40).unwrap());
+    /// ```
+    #[inline]
+    #[track_caller]
+    fn sub(self, (x, y): (i8, i8)) -> Self {
+        self.checked_add((-x, -y)).unwrap()
+    }
+}
+
+impl Sub<Direction> for RoomXY {
+    type Output = RoomXY;
+
+    /// Subtracts a `Direction` from this position's coordinates.
+    ///
+    /// # Panics
+    ///
+    /// Will panic if the new position is outside standard room bounds.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use screeps::{RoomXY, Direction};
+    ///
+    /// let pos1 = RoomXY::checked_new(49, 40).unwrap();
+    /// let pos2 = pos1 - Direction::Top;
+    /// assert_eq!(pos2, RoomXY::checked_new(49, 41).unwrap());
+    /// ```
+    #[inline]
+    fn sub(self, direction: Direction) -> Self {
+        self.checked_add_direction(-direction).unwrap()
+    }
+}
+
+impl Sub<RoomXY> for RoomXY {
+    type Output = (i8, i8);
+
+    /// Subtracts the other position from this one, extracting the
+    /// difference as the output.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use screeps::RoomXY;
+    ///
+    /// let pos1 = RoomXY::checked_new(40, 40).unwrap();
+    /// let pos2 = RoomXY::checked_new(0, 20).unwrap();
+    /// assert_eq!(pos1 - pos2, (40, 20));
+    ///
+    /// let pos3 = RoomXY::checked_new(45, 45).unwrap();
+    /// assert_eq!(pos1 - pos3, (-5, -5));
+    /// ```
+    #[inline]
+    fn sub(self, other: RoomXY) -> (i8, i8) {
+        let dx = self.x.0.wrapping_sub(other.x.0) as i8;
+        let dy = self.y.0.wrapping_sub(other.y.0) as i8;
+        (dx, dy)
+    }
+}
diff --git a/src/local/room_xy/game_math.rs b/src/local/room_xy/game_math.rs
new file mode 100644
index 00000000..46abcf1d
--- /dev/null
+++ b/src/local/room_xy/game_math.rs
@@ -0,0 +1,151 @@
+//! Utilities for doing math on [`RoomXY`]s which are present in the
+//! JavaScript API.
+
+use crate::constants::Direction;
+
+use super::RoomXY;
+
+impl RoomXY {
+    /// 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: RoomXY) -> 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).
+    ///
+    /// # Examples
+    /// ```rust
+    /// # use screeps::RoomXY;
+    /// let pos_1 = RoomXY::checked_new(5, 10).unwrap();
+    /// let pos_2 = RoomXY::checked_new(8, 15).unwrap();
+    /// // 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: RoomXY) -> 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).
+    ///
+    /// # Examples
+    /// ```rust
+    /// # use screeps::RoomXY;
+    /// let pos_1 = RoomXY::checked_new(5, 10).unwrap();
+    /// let pos_2 = RoomXY::checked_new(8, 10).unwrap();
+    ///
+    /// // 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);
+    ///
+    /// let pos_3 = RoomXY::checked_new(8, 15).unwrap();
+    ///
+    /// // 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);
+    ///
+    /// let pos_4 = RoomXY::checked_new(20, 20).unwrap();
+    /// // 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);
+    /// assert_eq!(pos_1.in_range_to(pos_4, 10), false);
+    /// assert_eq!(pos_1.in_range_to(pos_4, 15), true);
+    /// ```
+    #[doc(alias = "distance")]
+    #[inline]
+    pub fn in_range_to(self, target: RoomXY, range: u32) -> bool {
+        self.get_range_to(target) <= range
+    }
+
+    /// Checks whether this position is the same as the specified position.
+    ///
+    /// # Examples
+    /// ```rust
+    /// # use screeps::RoomXY;
+    /// let pos_1 = RoomXY::checked_new(5, 10).unwrap();
+    /// let pos_2 = RoomXY::checked_new(5, 10).unwrap();
+    /// let pos_3 = RoomXY::checked_new(4, 9).unwrap();
+    ///
+    /// assert_eq!(pos_1.is_equal_to(pos_2), true);
+    /// assert_eq!(pos_1.is_equal_to(pos_3), false);
+    /// ```
+    #[inline]
+    pub fn is_equal_to(self, target: RoomXY) -> bool {
+        self == target
+    }
+
+    /// True if the range from this position to the target is at most 1.
+    ///
+    /// # Examples
+    /// ```rust
+    /// # use screeps::RoomXY;
+    /// let pos_1 = RoomXY::checked_new(5, 10).unwrap();
+    /// let pos_2 = RoomXY::checked_new(6, 10).unwrap();
+    /// let pos_3 = RoomXY::checked_new(4, 9).unwrap();
+    /// let pos_4 = RoomXY::checked_new(20, 20).unwrap();
+    ///
+    /// assert_eq!(pos_1.is_near_to(pos_2), true);
+    /// assert_eq!(pos_1.is_near_to(pos_3), true);
+    /// assert_eq!(pos_1.is_near_to(pos_4), false);
+    /// ```
+    #[inline]
+    pub fn is_near_to(self, target: RoomXY) -> bool {
+        (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::{Direction, RoomXY};
+
+    #[test]
+    fn test_direction_to() {
+        let a = RoomXY::checked_new(1, 1).unwrap();
+        let b = RoomXY::checked_new(2, 2).unwrap();
+        assert_eq!(a.get_direction_to(b), Some(Direction::BottomRight));
+    }
+}