use core::ops::{Add, Mul, Neg, Sub};
use crate::rotation::Rotation;
use crate::vector::{Columns, Rows, Vector, VectorLike};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Direction {
Up,
Right,
Down,
Left,
}
pub use Direction::*;
macro_rules! string_match {
($input:expr => $($($pattern:literal)+ => $result:expr;)*) => {
if false {None}
$($(
else if $input.eq_ignore_ascii_case($pattern) {Some($result)}
)+)*
else {None}
}
}
impl Direction {
#[inline]
#[must_use]
fn as_rotation(self) -> Rotation {
use Rotation::*;
match self {
Up => None,
Right => Clockwise,
Down => Flip,
Left => Anticlockwise,
}
}
#[must_use]
#[inline]
pub fn from_name(name: &str) -> Option<Self> {
string_match! {
name =>
"up" "u" "north" "n" => Up;
"down" "d" "south" "s" => Down;
"left" "l" "west" "w" => Left;
"right" "r" "east" "e" => Right;
}
}
#[must_use]
#[inline]
pub fn sized_vec(self, length: isize) -> Vector {
match self {
Up => Vector::upward(length),
Down => Vector::downward(length),
Left => Vector::leftward(length),
Right => Vector::rightward(length),
}
}
#[must_use]
#[inline]
pub fn unit_vec(self) -> Vector {
self.sized_vec(1)
}
#[must_use]
#[inline]
pub fn is_vertical(self) -> bool {
match self {
Up | Down => true,
Left | Right => false,
}
}
#[must_use]
#[inline]
pub fn is_horizontal(self) -> bool {
!self.is_vertical()
}
#[must_use]
#[inline]
pub fn reverse(self) -> Direction {
match self {
Up => Down,
Down => Up,
Left => Right,
Right => Left,
}
}
#[must_use]
#[inline]
pub fn clockwise(self) -> Direction {
match self {
Up => Right,
Right => Down,
Down => Left,
Left => Up,
}
}
#[must_use]
#[inline]
pub fn anticlockwise(self) -> Direction {
match self {
Up => Left,
Left => Down,
Down => Right,
Right => Up,
}
}
#[must_use]
#[inline]
pub fn rotate(self, rotation: Rotation) -> Direction {
use Rotation::*;
match rotation {
None => self,
Flip => self.reverse(),
Clockwise => self.clockwise(),
Anticlockwise => self.anticlockwise(),
}
}
#[must_use]
#[inline]
pub fn rotation_to(self, target: Direction) -> Rotation {
target.as_rotation() - self.as_rotation()
}
}
impl<T: VectorLike> Add<T> for Direction {
type Output = Vector;
#[must_use]
#[inline]
fn add(self, rhs: T) -> Vector {
rhs.as_vector() + self.unit_vec()
}
}
impl<T: VectorLike> Sub<T> for Direction {
type Output = Vector;
#[must_use]
#[inline]
fn sub(self, rhs: T) -> Vector {
self.unit_vec() - rhs.as_vector()
}
}
impl Neg for Direction {
type Output = Direction;
#[must_use]
#[inline]
fn neg(self) -> Direction {
self.reverse()
}
}
impl Mul<isize> for Direction {
type Output = Vector;
#[must_use]
#[inline]
fn mul(self, amount: isize) -> Vector {
self.sized_vec(amount)
}
}
impl VectorLike for Direction {
#[must_use]
#[inline]
fn rows(&self) -> Rows {
match self {
Up => Rows(-1),
Down => Rows(1),
Left | Right => Rows(0),
}
}
#[must_use]
#[inline]
fn columns(&self) -> Columns {
match self {
Left => Columns(-1),
Right => Columns(1),
Up | Down => Columns(0),
}
}
#[must_use]
#[inline]
fn as_vector(&self) -> Vector {
self.unit_vec()
}
#[must_use]
#[inline(always)]
fn manhattan_length(&self) -> isize {
1
}
#[must_use]
#[inline(always)]
fn checked_manhattan_length(&self) -> Option<isize> {
Some(1)
}
#[must_use]
#[inline]
fn clockwise(&self) -> Vector {
Direction::clockwise(*self).unit_vec()
}
#[must_use]
#[inline]
fn anticlockwise(&self) -> Vector {
Direction::anticlockwise(*self).unit_vec()
}
#[must_use]
#[inline]
fn reverse(&self) -> Vector {
Direction::reverse(*self).unit_vec()
}
#[must_use]
#[inline]
fn transpose(&self) -> Vector {
match self {
Down => Right,
Right => Down,
Up => Left,
Left => Up,
}
.unit_vec()
}
#[must_use]
#[inline]
fn direction(&self) -> Option<Direction> {
Some(*self)
}
}
#[test]
fn test_from_str() {
for variant in &[
"up", "u", "north", "n", "UP", "U", "NORTH", "N", "Up", "U", "North", "N",
] {
assert_eq!(Direction::from_name(variant), Some(Up));
}
for variant in &[
"down", "d", "south", "s", "DOWN", "D", "SOUTH", "S", "Down", "D", "South", "S",
] {
assert_eq!(Direction::from_name(variant), Some(Down));
}
for variant in &[
"left", "l", "west", "w", "LEFT", "L", "WEST", "W", "Left", "L", "West", "W",
] {
assert_eq!(Direction::from_name(variant), Some(Left));
}
for variant in &[
"right", "r", "east", "e", "RIGHT", "R", "EAST", "E", "Right", "R", "East", "E",
] {
assert_eq!(Direction::from_name(variant), Some(Right));
}
assert_eq!(Direction::from_name("foo"), None);
}
#[cfg(test)]
mod test_vectorlike {
use crate::direction::EACH_DIRECTION;
use crate::vector::VectorLike;
#[test]
fn test_row_column_vector_compatible() {
for direction in &EACH_DIRECTION {
assert_eq!(
direction.rows() + direction.columns(),
direction.as_vector()
);
assert_eq!(direction.as_vector(), direction.unit_vec());
}
}
mod custom_impls {
use crate::direction::EACH_DIRECTION;
use crate::vector::VectorLike;
macro_rules! test_vectorlike_method {
($method:ident) => {
#[test]
fn $method() {
for direction in &EACH_DIRECTION {
let vector = direction.unit_vec();
assert_eq!(vector.$method(), VectorLike::$method(direction),);
}
}
};
}
test_vectorlike_method! {manhattan_length}
test_vectorlike_method! {checked_manhattan_length}
test_vectorlike_method! {clockwise}
test_vectorlike_method! {anticlockwise}
test_vectorlike_method! {reverse}
test_vectorlike_method! {transpose}
test_vectorlike_method! {direction}
}
}
pub static EACH_DIRECTION: [Direction; 4] = [Up, Right, Down, Left];