use std::{cmp::Ordering, fmt::Display, num::ParseIntError, ops::Add, str::FromStr};
use thiserror::Error;
use crate::algorithm::{
as_slice::AsAlgorithmSlice,
direction::{Direction, ParseDirectionError},
display::r#move::{DisplayLongSpaced, DisplayLongUnspaced, DisplayShort, MoveDisplay as _},
slice::AlgorithmSlice,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Move {
pub(crate) direction: Direction,
pub(crate) amount: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MoveSum {
Ok(Move),
Invalid,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum MoveError {
#[error("ZeroAmount: move amount must be greater than 0")]
ZeroAmount,
}
impl Move {
#[must_use]
pub fn new(direction: Direction, amount: u64) -> Self {
Self { direction, amount }
}
pub fn new_nonzero(direction: Direction, amount: u64) -> Result<Self, MoveError> {
if amount == 0 {
Err(MoveError::ZeroAmount)
} else {
Ok(Self { direction, amount })
}
}
#[must_use]
pub fn direction(&self) -> Direction {
self.direction
}
#[must_use]
pub fn amount(&self) -> u64 {
self.amount
}
#[must_use]
pub fn inverse(&self) -> Self {
Self {
direction: self.direction.inverse(),
amount: self.amount,
}
}
#[must_use]
pub fn transpose(&self) -> Self {
Self {
direction: self.direction.transpose(),
amount: self.amount,
}
}
#[must_use]
pub fn display_long_spaced(&self) -> DisplayLongSpaced {
DisplayLongSpaced::new(*self)
}
#[must_use]
pub fn display_long_unspaced(&self) -> DisplayLongUnspaced {
DisplayLongUnspaced::new(*self)
}
#[must_use]
pub fn display_short(&self) -> DisplayShort {
DisplayShort::new(*self)
}
}
impl Display for Move {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.display_short().fmt(f)
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
pub enum ParseMoveError {
#[error("ParseDirectionError: {0}")]
ParseDirectionError(ParseDirectionError),
#[error("ParseIntError: {0}")]
ParseIntError(ParseIntError),
#[error("Empty: input string is empty")]
Empty,
}
impl FromStr for Move {
type Err = ParseMoveError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut chars = s.chars();
let direction = chars
.next()
.ok_or(Self::Err::Empty)?
.try_into()
.map_err(Self::Err::ParseDirectionError)?;
let rest = chars.as_str();
let amount = if rest.is_empty() {
1
} else {
rest.parse().map_err(Self::Err::ParseIntError)?
};
Ok(Self::new(direction, amount))
}
}
impl From<Direction> for Move {
fn from(direction: Direction) -> Self {
Self {
direction,
amount: 1,
}
}
}
impl Add for Move {
type Output = MoveSum;
fn add(self, rhs: Self) -> Self::Output {
if self.direction == rhs.direction {
MoveSum::Ok(Self {
direction: self.direction,
amount: self.amount + rhs.amount,
})
} else if self.direction == rhs.direction.inverse() {
match self.amount.cmp(&rhs.amount) {
Ordering::Less => MoveSum::Ok(Self {
direction: rhs.direction,
amount: rhs.amount - self.amount,
}),
Ordering::Equal | Ordering::Greater => MoveSum::Ok(Self {
direction: self.direction,
amount: self.amount - rhs.amount,
}),
}
}
else if rhs.amount == 0 {
MoveSum::Ok(self)
} else if self.amount == 0 {
MoveSum::Ok(rhs)
} else {
MoveSum::Invalid
}
}
}
impl AsAlgorithmSlice<'_> for Move {
fn as_slice(&self) -> AlgorithmSlice<'_> {
AlgorithmSlice {
first: Some(*self),
middle: &[],
last: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new() {
let a = Move::new(Direction::Up, 2);
assert_eq!(
a,
Move {
direction: Direction::Up,
amount: 2
}
);
}
#[test]
fn test_new_2() {
let a = Move::new(Direction::Up, 0);
assert_eq!(
a,
Move {
direction: Direction::Up,
amount: 0
}
);
}
#[test]
fn test_new_nonzero() {
let a = Move::new_nonzero(Direction::Up, 2);
assert_eq!(
a,
Ok(Move {
direction: Direction::Up,
amount: 2
})
);
}
#[test]
fn test_new_nonzero_2() {
let a = Move::new_nonzero(Direction::Up, 0);
assert_eq!(a, Err(MoveError::ZeroAmount));
}
#[test]
fn test_inverse() {
let a = Move::new(Direction::Up, 3);
let b = Move::new(Direction::Down, 3);
assert_eq!(a.inverse(), b);
}
#[test]
fn test_transpose() {
let a = Move::new(Direction::Up, 3);
let b = Move::new(Direction::Left, 3);
assert_eq!(a.transpose(), b);
}
mod from_direction {
use super::*;
#[test]
fn test_from_direction() {
assert_eq!(Move::from(Direction::Up), Move::new(Direction::Up, 1));
}
}
mod add {
use super::*;
#[test]
fn test_add() {
let m1 = Move::new(Direction::Up, 3);
let m2 = Move::new(Direction::Up, 4);
assert_eq!(
m1 + m2,
MoveSum::Ok(Move {
direction: Direction::Up,
amount: 7
})
);
}
#[test]
fn test_add_2() {
let m1 = Move {
direction: Direction::Up,
amount: 3,
};
let m2 = Move {
direction: Direction::Down,
amount: 4,
};
assert_eq!(
m1 + m2,
MoveSum::Ok(Move {
direction: Direction::Down,
amount: 1
})
);
}
#[test]
fn test_add_3() {
let m1 = Move {
direction: Direction::Left,
amount: 5,
};
let m2 = Move {
direction: Direction::Right,
amount: 5,
};
assert_eq!(
m1 + m2,
MoveSum::Ok(Move {
direction: Direction::Left,
amount: 0
})
);
}
#[test]
fn test_add_4() {
let m1 = Move {
direction: Direction::Left,
amount: 2,
};
let m2 = Move {
direction: Direction::Up,
amount: 1,
};
assert_eq!(m1 + m2, MoveSum::Invalid);
}
#[test]
fn test_add_5() {
let m1 = Move {
direction: Direction::Up,
amount: 0,
};
let m2 = Move {
direction: Direction::Down,
amount: 2,
};
assert_eq!(m1 + m2, MoveSum::Ok(m2));
assert_eq!(m2 + m1, MoveSum::Ok(m2));
}
#[test]
fn test_add_6() {
let m1 = Move {
direction: Direction::Up,
amount: 0,
};
let m2 = Move {
direction: Direction::Left,
amount: 2,
};
assert_eq!(m1 + m2, MoveSum::Ok(m2));
assert_eq!(m2 + m1, MoveSum::Ok(m2));
}
#[test]
fn test_add_7() {
let m1 = Move {
direction: Direction::Up,
amount: 0,
};
let m2 = Move {
direction: Direction::Left,
amount: 0,
};
assert_eq!(m1 + m2, MoveSum::Ok(m1));
assert_eq!(m2 + m1, MoveSum::Ok(m2));
}
}
}
#[cfg(all(feature = "nightly", test))]
mod benchmarks {
extern crate test;
use test::Bencher;
use super::*;
#[bench]
fn bench_display_long_spaced(b: &mut Bencher) {
let m = Move::new(Direction::Up, 10);
b.iter(|| {
for _ in 0..100 {
DisplayLongSpaced::new(m).to_string();
}
});
}
#[bench]
fn bench_display_long_unspaced(b: &mut Bencher) {
let m = Move::new(Direction::Up, 10);
b.iter(|| {
for _ in 0..100 {
DisplayLongUnspaced::new(m).to_string();
}
});
}
#[bench]
fn bench_display_short(b: &mut Bencher) {
let m = Move::new(Direction::Up, 10);
b.iter(|| {
for _ in 0..100 {
DisplayShort::new(m).to_string();
}
});
}
}