use std::fmt::Display;
use thiserror::Error;
pub const MAIN_DIRECTIONS: [Direction; 4] = [
Direction::Up,
Direction::Right,
Direction::Down,
Direction::Left,
];
pub const MINOR_DIRECTIONS: [Direction; 4] = [
Direction::UpRight,
Direction::DownRight,
Direction::DownLeft,
Direction::UpLeft,
];
pub trait PixelPositionInterface {
fn row(&self) -> usize;
fn column(&self) -> usize;
fn expand(&self) -> (usize, usize) {
(self.row(), self.column())
}
fn bound<const H: usize, const W: usize>(&self) -> StrictPositionValidationResult<H, W> {
PixelStrictPosition::new(self.row(), self.column())
}
fn up(&self, amount: usize) -> PixelPosition {
PixelPosition::new(
self.row().checked_sub(amount).unwrap_or_default(),
self.column(),
)
}
fn left(&self, amount: usize) -> PixelPosition {
PixelPosition::new(
self.row(),
self.column().checked_sub(amount).unwrap_or_default(),
)
}
fn down(&self, amount: usize) -> PixelPosition {
PixelPosition::new(self.row().wrapping_add(amount), self.column())
}
fn right(&self, amount: usize) -> PixelPosition {
PixelPosition::new(self.row(), self.column().wrapping_add(amount))
}
fn direction(&self, dir: Direction, amount: usize) -> PixelPosition {
match dir {
Direction::Up => self.up(amount),
Direction::Right => self.right(amount),
Direction::Down => self.down(amount),
Direction::Left => self.left(amount),
Direction::UpRight => self.up(amount).right(amount),
Direction::DownRight => self.down(amount).right(amount),
Direction::DownLeft => self.down(amount).left(amount),
Direction::UpLeft => self.up(amount).left(amount),
}
}
}
pub trait PixelStrictPositionInterface<const H: usize, const W: usize> {
fn row(&self) -> usize;
fn column(&self) -> usize;
fn expand(&self) -> (usize, usize) {
(self.row(), self.column())
}
fn unbound(&self) -> PixelPosition {
PixelPosition::new(self.row(), self.column())
}
fn checked_up(&self, amount: usize) -> StrictPositionValidationResult<H, W> {
self.unbound().up(amount).bound()
}
fn checked_left(&self, amount: usize) -> StrictPositionValidationResult<H, W> {
self.unbound().left(amount).bound()
}
fn checked_down(&self, amount: usize) -> StrictPositionValidationResult<H, W> {
self.unbound().down(amount).bound()
}
fn checked_right(&self, amount: usize) -> StrictPositionValidationResult<H, W> {
self.unbound().right(amount).bound()
}
fn checked_direction(
&self,
dir: Direction,
amount: usize,
) -> StrictPositionValidationResult<H, W> {
self.unbound().direction(dir, amount).bound()
}
fn bounding_up(&self, amount: usize) -> PixelStrictPosition<H, W> {
self.checked_up(amount).unwrap_or_else(|e| e.adjust())
}
fn bounding_left(&self, amount: usize) -> PixelStrictPosition<H, W> {
self.checked_left(amount).unwrap_or_else(|e| e.adjust())
}
fn bounding_down(&self, amount: usize) -> PixelStrictPosition<H, W> {
self.checked_down(amount).unwrap_or_else(|e| e.adjust())
}
fn bounding_right(&self, amount: usize) -> PixelStrictPosition<H, W> {
self.checked_right(amount).unwrap_or_else(|e| e.adjust())
}
fn bounding_direction(&self, dir: Direction, amount: usize) -> PixelStrictPosition<H, W> {
self.checked_direction(dir, amount)
.unwrap_or_else(|e| e.adjust())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub struct PixelPosition {
row: usize,
column: usize,
}
impl Display for PixelPosition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {})", self.row, self.column)
}
}
impl PixelPosition {
pub const fn new(row: usize, column: usize) -> Self {
Self { row, column }
}
}
impl PixelPositionInterface for PixelPosition {
fn row(&self) -> usize {
self.row
}
fn column(&self) -> usize {
self.column
}
}
pub type StrictPositionValidationResult<const H: usize, const W: usize> =
Result<PixelStrictPosition<H, W>, PixelPositionOutOfBoundError<H, W>>;
#[derive(Debug, Error)]
pub enum PixelPositionOutOfBoundError<const H: usize, const W: usize> {
#[error("The provided row value {0:?} is equal or more that row bound ({H}).")]
InvalidRow(PixelPosition),
#[error("The provided column value {0:?} is equal or more that column bound ({W}).")]
InvalidColumn(PixelPosition),
#[error("Both provided row and column values are out of bound ({H}, {W}).")]
InvalidBoth(PixelPosition),
}
impl<const H: usize, const W: usize> PixelPositionOutOfBoundError<H, W> {
pub fn validate_position(row: usize, column: usize) -> StrictPositionValidationResult<H, W> {
use std::cmp::Ordering::*;
use PixelPositionOutOfBoundError::*;
let raw_position = PixelPosition::new(row, column);
match (row.cmp(&H), column.cmp(&W)) {
(Equal | Greater, Less) => Err(InvalidRow(raw_position)),
(Less, Equal | Greater) => Err(InvalidColumn(raw_position)),
(Equal | Greater, Equal | Greater) => Err(InvalidBoth(raw_position)),
_ => Ok(PixelStrictPosition { raw: raw_position }),
}
}
pub fn adjust(&self) -> PixelStrictPosition<H, W> {
use PixelPositionOutOfBoundError::*;
match self {
InvalidRow(position) => PixelStrictPosition::new(H - 1, position.column).unwrap(),
InvalidColumn(position) => PixelStrictPosition::new(position.row, W - 1).unwrap(),
InvalidBoth(_) => PixelStrictPosition::new(H - 1, W - 1).unwrap(),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct PixelStrictPosition<const H: usize, const W: usize> {
raw: PixelPosition,
}
impl<const H: usize, const W: usize> Iterator for PixelStrictPosition<H, W> {
type Item = PixelStrictPosition<H, W>;
fn next(&mut self) -> Option<Self::Item> {
let next = self.bounding_right(1);
if next.column() == self.column() {
let next = self.bounding_down(1);
if next.row() == self.row() {
None
} else {
*self = PixelStrictPosition {
raw: PixelPosition::new(next.row(), 0),
};
Some(self.clone())
}
} else {
*self = next.clone();
Some(next)
}
}
}
impl<const H: usize, const W: usize> PixelStrictPosition<H, W> {
pub fn new(row: usize, column: usize) -> Result<Self, PixelPositionOutOfBoundError<H, W>> {
PixelPositionOutOfBoundError::validate_position(row, column)
}
}
impl<const H: usize, const W: usize> PixelStrictPositionInterface<H, W>
for PixelStrictPosition<H, W>
{
fn row(&self) -> usize {
self.raw.row
}
fn column(&self) -> usize {
self.raw.column
}
}
impl<const H: usize, const W: usize> PixelStrictPositionInterface<H, W>
for &PixelStrictPosition<H, W>
{
fn row(&self) -> usize {
self.raw.row
}
fn column(&self) -> usize {
self.raw.column
}
}
impl<const H: usize, const W: usize> PixelStrictPositionInterface<H, W>
for &mut PixelStrictPosition<H, W>
{
fn row(&self) -> usize {
self.raw.row
}
fn column(&self) -> usize {
self.raw.column
}
}
pub trait IntoPixelStrictPosition<const H: usize, const W: usize> {
fn into_pixel_strict_position(self) -> PixelStrictPosition<H, W>;
}
impl<const H: usize, const W: usize, T> IntoPixelStrictPosition<H, W> for T
where
T: PixelStrictPositionInterface<H, W>,
{
fn into_pixel_strict_position(self) -> PixelStrictPosition<H, W> {
PixelStrictPosition::new(self.row(), self.column()).unwrap()
}
}
impl<const H: usize, const W: usize> IntoPixelStrictPosition<H, W> for (usize, usize) {
fn into_pixel_strict_position(self) -> PixelStrictPosition<H, W> {
PixelStrictPosition::new(self.0, self.1).unwrap_or_else(|e| e.adjust())
}
}
impl<const H: usize, const W: usize> IntoPixelStrictPosition<H, W> for [usize; 2] {
fn into_pixel_strict_position(self) -> PixelStrictPosition<H, W> {
PixelStrictPosition::new(self[0], self[1]).unwrap_or_else(|e| e.adjust())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum StrictPositions {
TopLeft,
TopRight,
BottomRight,
BottomLeft,
Center,
TopCenter,
RightCenter,
BottomCenter,
LeftCenter,
}
impl<const H: usize, const W: usize> PixelStrictPositionInterface<H, W> for StrictPositions {
fn row(&self) -> usize {
use StrictPositions::*;
match self {
TopLeft => 0,
TopRight => 0,
BottomRight => H - 1,
BottomLeft => H - 1,
Center => H / 2,
TopCenter => 0,
RightCenter => H / 2,
BottomCenter => H - 1,
LeftCenter => H / 2,
}
}
fn column(&self) -> usize {
use StrictPositions::*;
match self {
TopLeft => 0,
TopRight => W - 1,
BottomRight => W - 1,
BottomLeft => 0,
Center => W / 2,
TopCenter => W / 2,
RightCenter => W - 1,
BottomCenter => W / 2,
LeftCenter => 0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Direction {
Up,
UpRight,
Right,
DownRight,
Down,
DownLeft,
Left,
UpLeft,
}
impl Iterator for Direction {
type Item = Direction;
fn next(&mut self) -> Option<Self::Item> {
use Direction::*;
match self {
Up => UpRight,
UpRight => Right,
Right => DownRight,
DownRight => Down,
Down => DownLeft,
DownLeft => Left,
Left => UpLeft,
UpLeft => Up,
}
.into()
}
}
pub struct SingleCycle {
initial_dir: Direction,
current: Option<Direction>,
}
impl SingleCycle {
pub fn new(initial_dir: Direction) -> Self {
Self {
initial_dir,
current: None,
}
}
}
impl Iterator for SingleCycle {
type Item = Direction;
fn next(&mut self) -> Option<Self::Item> {
if let Some(mut current) = self.current {
let next = current.next().unwrap();
if next == self.initial_dir {
return None;
}
self.current = Some(next);
self.current
} else {
self.current = Some(self.initial_dir);
Some(self.initial_dir)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_name() {
let pos = PixelStrictPosition::<5, 5>::new(0, 0).unwrap();
assert_eq!(
PixelStrictPosition {
raw: PixelPosition::new(1, 0)
},
pos.bounding_down(1)
);
assert_eq!(
PixelStrictPosition {
raw: PixelPosition::new(2, 0)
},
pos.bounding_down(2)
);
assert_eq!(
PixelStrictPosition {
raw: PixelPosition::new(3, 0)
},
pos.bounding_down(3)
);
assert_eq!(
PixelStrictPosition {
raw: PixelPosition::new(4, 0)
},
pos.bounding_down(4)
);
assert_eq!(
PixelStrictPosition {
raw: PixelPosition::new(4, 0)
},
pos.bounding_down(5)
);
assert_eq!(
PixelStrictPosition {
raw: PixelPosition::new(0, 4)
},
pos.bounding_right(5)
);
}
#[test]
fn test_iter() {
let mut pos = PixelStrictPosition::<2, 2>::new(0, 0).unwrap();
assert_eq!(
Some(PixelStrictPosition {
raw: PixelPosition::new(0, 1)
}),
pos.next()
);
assert_eq!(
Some(PixelStrictPosition {
raw: PixelPosition::new(1, 0)
}),
pos.next()
);
assert_eq!(
Some(PixelStrictPosition {
raw: PixelPosition::new(1, 1)
}),
pos.next()
);
assert_eq!(None, pos.next());
}
#[test]
fn test_direction_single_cycle() {
use Direction::*;
let mut single = SingleCycle::new(Down);
assert_eq!(Some(Down), single.next());
assert_eq!(Some(DownLeft), single.next());
assert_eq!(Some(Left), single.next());
assert_eq!(Some(UpLeft), single.next());
assert_eq!(Some(Up), single.next());
assert_eq!(Some(UpRight), single.next());
assert_eq!(Some(Right), single.next());
assert_eq!(Some(DownRight), single.next());
assert_eq!(None, single.next());
}
}