/// A position in a 2d grid. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Position2d { pub x: usize, pub y: usize, } /// An alignment in a 2d grid. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Alignment { Horizontal, Vertical, } /// A direction in a 2d grid. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Direction { Up, Down, Left, Right, } impl Alignment { /// Returns the direction to the start of an alignment. pub fn start(self) -> Direction { match self { Alignment::Horizontal => Direction::Left, Alignment::Vertical => Direction::Up, } } /// Returns the direction to the end of an alignment. pub fn end(self) -> Direction { match self { Alignment::Horizontal => Direction::Right, Alignment::Vertical => Direction::Down, } } /// Test if two positions are aligned in this direction. pub fn is_aligned(self, a: Position2d, b: Position2d) -> bool { match self { Alignment::Horizontal => a.y == b.y, Alignment::Vertical => a.x == b.x, } } } impl Position2d { pub fn new(x: usize, y: usize) -> Self { Self { x, y } } /// Returns the position relative to this position in the given direction. /// If the position is out of bounds, returns None. pub fn relative(self, dir: Direction, sized: &impl Grid2d) -> Option { let (x, y) = match dir { Direction::Up => (self.x, self.y.checked_sub(1)?), Direction::Down => (self.x, self.y.checked_add(1)?), Direction::Left => (self.x.checked_sub(1)?, self.y), Direction::Right => (self.x.checked_add(1)?, self.y), }; if x < sized.width() && y < sized.height() { Some(Position2d::new(x, y)) } else { None } } /// Returns an iterator over all positions relative to this position in the given direction. /// /// If the positions are not aligned in any direction, the iterator will be empty. /// If the end position is before the start position, the iterator will be empty. pub fn iterate_to(self, end: Position2d, offset: (usize, usize)) -> RelativeIterator { if (self.x != end.x && self.y != end.y) || self.x > end.x || self.y > end.y { return RelativeIterator { pos: self, end: self, offset: (0, 0), }; } RelativeIterator { pos: self, end, offset, } } pub fn manhattan_distance(self, other: Position2d) -> usize { self.x.abs_diff(other.x) + self.y.abs_diff(other.y) } pub fn is_contiguous(self, other: Position2d) -> bool { self.manhattan_distance(other) == 1 } } pub struct RelativeIterator { pos: Position2d, end: Position2d, offset: (usize, usize), } impl Iterator for RelativeIterator { type Item = Position2d; fn next(&mut self) -> Option { if self.pos == self.end { None } else { let pos = self.pos; self.pos = Position2d::new(self.pos.x + self.offset.0, self.pos.y + self.offset.1); Some(pos) } } } impl From<(usize, usize)> for Position2d { fn from((x, y): (usize, usize)) -> Self { Self { x, y } } } /// Trait for elements that have a size. pub trait Grid2d { /// Returns the grid width. fn width(&self) -> usize; /// Returns the grid height. fn height(&self) -> usize; } #[cfg(test)] mod tests { use super::*; struct Rectangle { width: usize, height: usize, } impl Rectangle { fn new(width: usize, height: usize) -> Self { Self { width, height } } } impl Grid2d for Rectangle { fn width(&self) -> usize { self.width } fn height(&self) -> usize { self.height } } #[test] fn test_relative() { let rect = Rectangle::new(3, 3); let pos = Position2d::new(0, 0); assert_eq!(pos.relative(Direction::Up, &rect), None); assert_eq!( pos.relative(Direction::Down, &rect), Some(Position2d::new(0, 1)) ); assert_eq!(pos.relative(Direction::Left, &rect), None); assert_eq!( pos.relative(Direction::Right, &rect), Some(Position2d::new(1, 0)) ); let pos = Position2d::new(2, 2); assert_eq!( pos.relative(Direction::Up, &rect), Some(Position2d::new(2, 1)) ); assert_eq!(pos.relative(Direction::Down, &rect), None); assert_eq!( pos.relative(Direction::Left, &rect), Some(Position2d::new(1, 2)) ); assert_eq!(pos.relative(Direction::Right, &rect), None); } }