use crate::{
coord::CoordIJK, error, CellIndex, Edge, Vertex, NUM_HEX_VERTS,
NUM_PENT_VERTS,
};
use std::{fmt, num::NonZeroU8};
const MAX: u8 = 6;
const TO_VERTEX_HEXAGON: [Vertex; NUM_HEX_VERTS as usize] = [
Vertex::new_unchecked(3),
Vertex::new_unchecked(1),
Vertex::new_unchecked(2),
Vertex::new_unchecked(5),
Vertex::new_unchecked(4),
Vertex::new_unchecked(0),
];
const TO_VERTEX_PENTAGON: [Vertex; NUM_PENT_VERTS as usize] = [
Vertex::new_unchecked(1),
Vertex::new_unchecked(2),
Vertex::new_unchecked(4),
Vertex::new_unchecked(3),
Vertex::new_unchecked(0),
];
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
#[repr(u8)]
#[allow(clippy::exhaustive_enums)] #[cfg_attr(
feature = "serde",
derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr)
)]
pub enum Direction {
Center = 0,
K = 1,
J = 2,
JK = 3,
I = 4,
IK = 5,
IJ = 6,
}
impl Direction {
pub fn iter() -> impl Iterator<Item = Self> {
(0..=MAX).map(Self::new_unchecked)
}
pub(crate) fn coordinate(self) -> CoordIJK {
let value = u8::from(self);
CoordIJK::new(
i32::from((value >> 2) & 1),
i32::from((value >> 1) & 1),
i32::from(value & 1),
)
}
pub(crate) fn axe(self) -> Option<NonZeroU8> {
NonZeroU8::new(self.into())
}
#[allow(unsafe_code)]
pub(crate) const fn new_unchecked(value: u8) -> Self {
assert!(value <= MAX, "direction out of range");
unsafe { std::mem::transmute::<u8, Self>(value) }
}
#[rustfmt::skip]
pub(crate) const fn rotate60<const CCW: bool>(self, count: usize) -> Self {
use Direction::{Center, I, IJ, IK, J, JK, K};
const CCW_SEQUENCE: [Direction; 6] = [K, IK, I, IJ, J, JK];
const CW_SEQUENCE: [Direction; 6] = [K, JK, J, IJ, I, IK];
if count == 0 {
return self;
}
let offset = match self {
Center => return self,
K => 0,
J => if CCW { 4 } else { 2 },
JK => if CCW { 5 } else { 1 },
I => if CCW { 2 } else { 4 },
IK => if CCW { 1 } else { 5 },
IJ => 3,
};
let index = (count + offset) % 6;
if CCW { CCW_SEQUENCE[index] } else { CW_SEQUENCE[index] }
}
#[rustfmt::skip]
pub(crate) const fn rotate60_once<const CCW: bool>(self) -> Self {
use Direction::{Center, I, IJ, IK, J, JK, K};
match self {
Center => Center,
K => if CCW { IK } else { JK },
J => if CCW { JK } else { IJ },
JK => if CCW { K } else { J },
I => if CCW { IJ } else { IK },
IK => if CCW { I } else { K },
IJ => if CCW { J } else { I },
}
}
pub(crate) fn vertex(self, origin: CellIndex) -> Vertex {
let is_pentagon = origin.is_pentagon();
assert!(self != Self::Center && !(is_pentagon && self == Self::K));
let rotations = origin.vertex_rotations();
Vertex::new_unchecked(if is_pentagon {
let index = usize::from(self) - 2;
(u8::from(TO_VERTEX_PENTAGON[index]) + NUM_PENT_VERTS - rotations)
% NUM_PENT_VERTS
} else {
let index = usize::from(self) - 1;
(u8::from(TO_VERTEX_HEXAGON[index]) + NUM_HEX_VERTS - rotations)
% NUM_HEX_VERTS
})
}
}
impl TryFrom<u8> for Direction {
type Error = error::InvalidDirection;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Center),
1 => Ok(Self::K),
2 => Ok(Self::J),
3 => Ok(Self::JK),
4 => Ok(Self::I),
5 => Ok(Self::IK),
6 => Ok(Self::IJ),
_ => Err(Self::Error::new(value, "out of range")),
}
}
}
impl From<Direction> for u8 {
fn from(value: Direction) -> Self {
value as Self
}
}
impl From<Direction> for u64 {
fn from(value: Direction) -> Self {
u8::from(value).into()
}
}
impl From<Direction> for usize {
fn from(value: Direction) -> Self {
u8::from(value).into()
}
}
impl From<Edge> for Direction {
fn from(value: Edge) -> Self {
Self::new_unchecked(value.into())
}
}
impl fmt::Display for Direction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", u8::from(*self))
}
}