sokoban-elements 0.2.1

Parsing and writing Sokoban, Hexoban, Trioban and Octoban file formats.
Documentation
use std::fmt::{Debug, Display};
use std::iter::{IntoIterator, Iterator};

use itertools::{self, Itertools};

/// Direction of movement.
///
/// Not all directions are legal (or even supported) by all [`Tessellation`](crate::Tessellation)s.
///
/// For example, in [`Sokoban`](crate::Tessellation::Sokoban), it is completely illegal to make a
/// move in north-west direction.
///
/// On the other hand, in [`Octoban`](crate::Tessellation::Octoban) it is allowed to try making a
/// north-west move, but success will depend on concrete board position. If that concrete position
/// happens to be square, then that move is not allowed.
///
/// Note though that [`Direction`] doesn't try to encode or resolve what is or isn't allowed, it
/// just enumerates all possible directions.
#[repr(u8)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Direction {
    Up        = 0,
    NorthEast = 1,
    Right     = 2,
    SouthEast = 3,
    Down      = 4,
    SouthWest = 5,
    Left      = 6,
    NorthWest = 7,
}

impl Direction {
    /// Number of all possible [`Direction`]s.
    pub const COUNT: usize = 8;

    /// All possible [`Direction`]s in clockwise order.
    pub const ALL: [Direction; Direction::COUNT] = [
        Direction::Up,
        Direction::NorthEast,
        Direction::Right,
        Direction::SouthEast,
        Direction::Down,
        Direction::SouthWest,
        Direction::Left,
        Direction::NorthWest,
    ];

    #[expect(clippy::as_conversions)]
    pub fn opposite(&self) -> Direction { OPPOSITES[usize::from(*self as u8)] }
}

impl Display for Direction {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "Direction::{self:?}")
    }
}

const OPPOSITES: [Direction; Direction::COUNT] = [
    Direction::Down,
    Direction::SouthWest,
    Direction::Left,
    Direction::NorthWest,
    Direction::Up,
    Direction::NorthEast,
    Direction::Right,
    Direction::SouthEast,
];

const EMPTY: u8 = 0;

const DIRECTIONS_FLAGS: [u8; Direction::COUNT] = [1, 2, 4, 8, 16, 32, 64, 128];

const FLAGS_DIRECTIONS: [Direction; Direction::COUNT] = [
    Direction::Up,
    Direction::NorthEast,
    Direction::Right,
    Direction::SouthEast,
    Direction::Down,
    Direction::SouthWest,
    Direction::Left,
    Direction::NorthWest,
];

/// Immutable `Set` of [`Direction`] with small memory footprint.
///
/// Can be used as key representing some aggregated quality of game board position. For example, in
/// puzzle:
///
/// ```txt
/// #####
/// #@  #
/// #$  #
/// #.  #
/// #####
/// ```
///
/// we can see that position `(0, 0)` is a wall that has neighbor wall on the right and below. Or,
/// in terms of [`DirectionsSet`]:
///
/// ```rust
/// use sokoban_elements::*;
///
/// let neighbor_walls: DirectionsSet = [Direction::Left, Direction::Up].into();
///
/// assert_eq!(neighbor_walls, [Direction::Left, Direction::Up]);
///
/// assert!(neighbor_walls.contains(Direction::Left));
/// assert!(neighbor_walls.contains(Direction::Up));
///
/// assert!(!neighbor_walls.contains(Direction::Down));
/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DirectionsSet(u8);

impl DirectionsSet {
    pub fn is_empty(&self) -> bool { self.0 == EMPTY }

    #[expect(clippy::as_conversions)]
    pub fn contains(&self, direction: Direction) -> bool {
        self.0 & DIRECTIONS_FLAGS[usize::from(direction as u8)] != 0
    }

    pub fn iter(&self) -> DirectionsSetIter { DirectionsSetIter::new(*self) }
}

impl Display for DirectionsSet {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{{{}}}", self.iter().map(|d| format!("{d}")).join(", "))
    }
}

impl Default for DirectionsSet {
    fn default() -> Self { Self(EMPTY) }
}

/// Iterator over [`Direction`] in [`DirectionsSet`] in clockwise order.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DirectionsSetIter {
    src:     u8,
    current: usize,
}

impl DirectionsSetIter {
    fn new(src: DirectionsSet) -> DirectionsSetIter {
        DirectionsSetIter {
            src:     src.0,
            current: 0,
        }
    }
}

impl Iterator for DirectionsSetIter {
    type Item = Direction;

    fn next(&mut self) -> Option<Direction> {
        while self.current < Direction::COUNT {
            let idx = self.current;
            self.current += 1;
            if self.src & DIRECTIONS_FLAGS[idx] != 0 {
                return Some(FLAGS_DIRECTIONS[idx]);
            }
        }
        None
    }
}

impl IntoIterator for &DirectionsSet {
    type Item = Direction;
    type IntoIter = DirectionsSetIter;

    fn into_iter(self) -> Self::IntoIter { DirectionsSetIter::new(*self) }
}

impl IntoIterator for DirectionsSet {
    type Item = Direction;
    type IntoIter = DirectionsSetIter;

    fn into_iter(self) -> Self::IntoIter { DirectionsSetIter::new(self) }
}

impl<const N: usize> From<&[Direction; N]> for DirectionsSet {
    fn from(data: &[Direction; N]) -> Self { Self::from_iter(data) }
}

impl<const N: usize> From<&mut [Direction; N]> for DirectionsSet {
    fn from(data: &mut [Direction; N]) -> Self { Self::from_iter(data) }
}

impl<const N: usize> From<[Direction; N]> for DirectionsSet {
    fn from(data: [Direction; N]) -> Self { Self::from_iter(data) }
}

impl From<&[Direction]> for DirectionsSet {
    fn from(data: &[Direction]) -> Self { Self::from_iter(data) }
}

impl From<&mut [Direction]> for DirectionsSet {
    fn from(data: &mut [Direction]) -> Self { Self::from_iter(data) }
}

impl FromIterator<Direction> for DirectionsSet {
    #[expect(clippy::as_conversions)]
    fn from_iter<I: IntoIterator<Item = Direction>>(iter: I) -> Self {
        let mut retv = EMPTY;
        for d in iter {
            retv |= DIRECTIONS_FLAGS[usize::from(d as u8)];
        }
        Self(retv)
    }
}

impl<'a> FromIterator<&'a Direction> for DirectionsSet {
    fn from_iter<I: IntoIterator<Item = &'a Direction>>(iter: I) -> Self {
        Self::from_iter(iter.into_iter().copied())
    }
}

impl<'a> FromIterator<&'a mut Direction> for DirectionsSet {
    fn from_iter<I: IntoIterator<Item = &'a mut Direction>>(iter: I) -> Self {
        Self::from_iter(iter.into_iter().map(|d| *d))
    }
}

impl<const N: usize> PartialEq<[Direction; N]> for DirectionsSet {
    fn eq(&self, other: &[Direction; N]) -> bool { self.0 == DirectionsSet::from(other).0 }
}

impl<const N: usize> PartialEq<&[Direction; N]> for DirectionsSet {
    fn eq(&self, other: &&[Direction; N]) -> bool { self.0 == DirectionsSet::from(*other).0 }
}

impl<const N: usize> PartialEq<&mut [Direction; N]> for DirectionsSet {
    fn eq(&self, other: &&mut [Direction; N]) -> bool {
        self.0 == DirectionsSet::from_iter(other.iter()).0
    }
}

impl PartialEq<[Direction]> for DirectionsSet {
    fn eq(&self, other: &[Direction]) -> bool { self.0 == DirectionsSet::from(other).0 }
}

impl PartialEq<&[Direction]> for DirectionsSet {
    fn eq(&self, other: &&[Direction]) -> bool { self.0 == DirectionsSet::from(*other).0 }
}

impl PartialEq<&mut [Direction]> for DirectionsSet {
    fn eq(&self, other: &&mut [Direction]) -> bool {
        self.0 == DirectionsSet::from_iter(other.iter()).0
    }
}

impl<const N: usize> PartialEq<DirectionsSet> for [Direction; N] {
    fn eq(&self, other: &DirectionsSet) -> bool { DirectionsSet::from(self).0 == other.0 }
}

impl<const N: usize> PartialEq<DirectionsSet> for &[Direction; N] {
    fn eq(&self, other: &DirectionsSet) -> bool {
        DirectionsSet::from_iter(self.iter()).0 == other.0
    }
}

impl<const N: usize> PartialEq<DirectionsSet> for &mut [Direction; N] {
    fn eq(&self, other: &DirectionsSet) -> bool {
        DirectionsSet::from_iter(self.iter()).0 == other.0
    }
}

impl PartialEq<DirectionsSet> for [Direction] {
    fn eq(&self, other: &DirectionsSet) -> bool { DirectionsSet::from(self).0 == other.0 }
}

impl PartialEq<DirectionsSet> for &[Direction] {
    fn eq(&self, other: &DirectionsSet) -> bool {
        DirectionsSet::from_iter(self.iter()).0 == other.0
    }
}

impl PartialEq<DirectionsSet> for &mut [Direction] {
    fn eq(&self, other: &DirectionsSet) -> bool {
        DirectionsSet::from_iter(self.iter()).0 == other.0
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn it_calculates_opposite_direction() {
        assert_eq!(Direction::Up.opposite(), Direction::Down);
        assert_eq!(Direction::Down.opposite(), Direction::Up);
        assert_eq!(Direction::Left.opposite(), Direction::Right);
        assert_eq!(Direction::Right.opposite(), Direction::Left);
        assert_eq!(Direction::NorthWest.opposite(), Direction::SouthEast);
        assert_eq!(Direction::SouthEast.opposite(), Direction::NorthWest);
        assert_eq!(Direction::NorthEast.opposite(), Direction::SouthWest);
        assert_eq!(Direction::SouthWest.opposite(), Direction::NorthEast);
    }

    #[test]
    fn it_is_empty_by_default() {
        let neighbors = DirectionsSet::default();
        assert!(neighbors.is_empty());
    }

    #[test]
    fn it_can_be_constructed_from_and_compared_to_slice() {
        let neighbors: DirectionsSet = [Direction::Up, Direction::Down].into();
        assert_eq!(neighbors, [Direction::Up, Direction::Down]);
    }

    #[test]
    fn test_order_in_source_slice_doesnt_matter() {
        let neighbors1: DirectionsSet = [Direction::Up, Direction::Down].into();
        let neighbors2: DirectionsSet = [Direction::Down, Direction::Up].into();
        assert_eq!(neighbors1, neighbors2);
    }

    #[test]
    fn it_can_be_iterated() {
        let neighbors: DirectionsSet = [Direction::Up, Direction::Down].into();
        let mut v: Vec<Direction> = Vec::new();
        for d in neighbors {
            v.push(d);
        }
        assert_eq!(v.len(), 2);
        assert_eq!(v[0], Direction::Up);
        assert_eq!(v[1], Direction::Down);
    }

    #[test]
    fn it_iterates_in_clockwise_direction() {
        let neighbors: DirectionsSet =
            [Direction::NorthWest, Direction::Down, Direction::Up].into();
        let mut v: Vec<Direction> = Vec::new();
        for d in neighbors {
            v.push(d);
        }
        assert_eq!(v.len(), 3);
        assert_eq!(v[0], Direction::Up);
        assert_eq!(v[1], Direction::Down);
        assert_eq!(v[2], Direction::NorthWest);
    }
}