use core::{
fmt,
ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign},
};
use crate::{Direction, HexLine, HexRange, HexRing, int::SignedInt, offset::OffsetScheme};
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(C)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Hex<T: SignedInt = i32> {
pub q: T,
pub r: T,
}
impl<T: SignedInt> Hex<T> {
pub const ORIGIN: Self = Self {
q: T::ZERO,
r: T::ZERO,
};
pub const E: Self = Self {
q: T::ONE,
r: T::ZERO,
};
pub const NE: Self = Self {
q: T::ONE,
r: T::NEG_ONE,
};
pub const NW: Self = Self {
q: T::ZERO,
r: T::NEG_ONE,
};
pub const W: Self = Self {
q: T::NEG_ONE,
r: T::ZERO,
};
pub const SW: Self = Self {
q: T::NEG_ONE,
r: T::ONE,
};
pub const SE: Self = Self {
q: T::ZERO,
r: T::ONE,
};
pub const DIRECTIONS: [Self; 6] = [Self::E, Self::NE, Self::NW, Self::W, Self::SW, Self::SE];
}
impl<T: SignedInt> Hex<T> {
#[must_use]
pub const fn new(q: T, r: T) -> Self {
Self { q, r }
}
#[must_use]
pub fn s(self) -> T {
-self.q - self.r
}
#[must_use]
pub fn neighbor(self, dir: Direction) -> Self {
self + Self::DIRECTIONS[dir as usize]
}
#[must_use]
pub fn neighbors(self) -> [Self; 6] {
Self::DIRECTIONS.map(|d| self + d)
}
#[must_use]
pub fn distance(self, other: Self) -> T {
let dq = (self.q - other.q).abs();
let dr = (self.r - other.r).abs();
let ds = (self.s() - other.s()).abs();
if dq >= dr && dq >= ds {
dq
} else if dr >= ds {
dr
} else {
ds
}
}
#[must_use]
pub fn length(self) -> T {
self.distance(Self::ORIGIN)
}
#[must_use]
pub fn ring(self, radius: T) -> HexRing<T> {
HexRing::new(self, radius)
}
#[must_use]
pub fn range(self, radius: T) -> HexRange<T> {
HexRange::new(self, radius)
}
#[must_use]
pub fn line_to(self, other: Self) -> HexLine<T> {
HexLine::new(self, other)
}
#[must_use]
pub fn to_offset<S: OffsetScheme<T>>(self) -> crate::offset::OffsetHex<T, S> {
S::from_axial(self)
}
#[must_use]
pub fn rotate_cw(self) -> Self {
Self {
q: -self.r,
r: -self.s(),
}
}
#[must_use]
pub fn rotate_ccw(self) -> Self {
Self {
q: -self.s(),
r: -self.q,
}
}
#[must_use]
pub fn reflect_q(self) -> Self {
Self {
q: self.q,
r: self.s(),
}
}
#[must_use]
pub fn reflect_r(self) -> Self {
Self {
q: self.s(),
r: self.r,
}
}
#[must_use]
pub const fn reflect_s(self) -> Self {
Self {
q: self.r,
r: self.q,
}
}
}
impl<T: SignedInt> Add for Hex<T> {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
q: self.q + rhs.q,
r: self.r + rhs.r,
}
}
}
impl<T: SignedInt> AddAssign for Hex<T> {
fn add_assign(&mut self, rhs: Self) {
self.q += rhs.q;
self.r += rhs.r;
}
}
impl<T: SignedInt> Sub for Hex<T> {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
q: self.q - rhs.q,
r: self.r - rhs.r,
}
}
}
impl<T: SignedInt> SubAssign for Hex<T> {
fn sub_assign(&mut self, rhs: Self) {
self.q -= rhs.q;
self.r -= rhs.r;
}
}
impl<T: SignedInt> Neg for Hex<T> {
type Output = Self;
fn neg(self) -> Self {
Self {
q: -self.q,
r: -self.r,
}
}
}
impl<T: SignedInt> Mul<T> for Hex<T> {
type Output = Self;
fn mul(self, rhs: T) -> Self {
Self {
q: self.q * rhs,
r: self.r * rhs,
}
}
}
impl<T: SignedInt> MulAssign<T> for Hex<T> {
fn mul_assign(&mut self, rhs: T) {
self.q *= rhs;
self.r *= rhs;
}
}
impl<T: SignedInt> PartialOrd for Hex<T> {
fn partial_cmp(&self, other: &Self) -> Option<core::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<T: SignedInt> Ord for Hex<T> {
fn cmp(&self, other: &Self) -> core::cmp::Ordering {
self.r.cmp(&other.r).then(self.q.cmp(&other.q))
}
}
impl<T: SignedInt> fmt::Display for Hex<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.q, self.r)
}
}
impl<T: SignedInt> From<(T, T)> for Hex<T> {
fn from((q, r): (T, T)) -> Self {
Self::new(q, r)
}
}
impl<T: SignedInt> From<[T; 2]> for Hex<T> {
fn from([q, r]: [T; 2]) -> Self {
Self::new(q, r)
}
}
impl<T: SignedInt> From<Hex<T>> for (T, T) {
fn from(h: Hex<T>) -> Self {
(h.q, h.r)
}
}
impl<T: SignedInt> From<Hex<T>> for [T; 2] {
fn from(h: Hex<T>) -> Self {
[h.q, h.r]
}
}
#[cfg(feature = "ixy")]
impl<T> From<Hex<T>> for ixy::Pos<T>
where
T: SignedInt + ixy::int::Int,
{
fn from(h: Hex<T>) -> Self {
let o = h.to_offset::<crate::offset::OddR>();
#[allow(clippy::use_self)]
ixy::Pos::new(o.col, o.row)
}
}
#[cfg(feature = "ixy")]
impl<T> From<ixy::Pos<T>> for Hex<T>
where
T: SignedInt + ixy::int::Int,
{
fn from(p: ixy::Pos<T>) -> Self {
crate::offset::OffsetHex::<T, crate::offset::OddR>::new(p.x, p.y).to_hex()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hex;
#[test]
fn s_axis_constraint() {
let h = hex!(3, -1);
assert_eq!(h.q + h.r + h.s(), 0);
}
#[test]
fn distance_symmetry() {
let a = hex!(2, -1);
let b = hex!(-1, 3);
assert_eq!(a.distance(b), b.distance(a));
}
#[test]
fn distance_known_values() {
assert_eq!(hex!(0, 0).distance(hex!(3, -1)), 3);
assert_eq!(hex!(-2, 3).distance(hex!(1, -1)), 4);
}
#[test]
fn length_matches_distance_from_origin() {
let h = hex!(2, -1);
assert_eq!(h.length(), h.distance(hex!(0, 0)));
}
#[test]
fn neighbor_all_directions() {
let center = hex!(0, 0);
assert_eq!(center.neighbor(Direction::E), hex!(1, 0));
assert_eq!(center.neighbor(Direction::NE), hex!(1, -1));
assert_eq!(center.neighbor(Direction::NW), hex!(0, -1));
assert_eq!(center.neighbor(Direction::W), hex!(-1, 0));
assert_eq!(center.neighbor(Direction::SW), hex!(-1, 1));
assert_eq!(center.neighbor(Direction::SE), hex!(0, 1));
}
#[test]
fn rotate_cw_six_times_is_identity() {
let h = hex!(3, -1);
let mut cur = h;
for _ in 0..6 {
cur = cur.rotate_cw();
}
assert_eq!(cur, h);
}
#[test]
fn rotate_ccw_six_times_is_identity() {
let h = hex!(2, 3);
let mut cur = h;
for _ in 0..6 {
cur = cur.rotate_ccw();
}
assert_eq!(cur, h);
}
#[test]
fn reflect_involutions() {
let h = hex!(1, 2);
assert_eq!(h.reflect_q().reflect_q(), h);
assert_eq!(h.reflect_r().reflect_r(), h);
assert_eq!(h.reflect_s().reflect_s(), h);
}
#[test]
fn arithmetic_ops() {
let a = hex!(1, 2);
let b = hex!(3, -1);
assert_eq!(a + b, hex!(4, 1));
assert_eq!(a - b, hex!(-2, 3));
assert_eq!(-a, hex!(-1, -2));
assert_eq!(a * 3, hex!(3, 6));
}
#[test]
fn ordering_row_major() {
let a = hex!(5, 0);
let b = hex!(0, 1);
assert!(a < b);
}
#[test]
fn display() {
assert_eq!(alloc::format!("{}", hex!(1, -2)), "(1, -2)");
}
#[test]
fn conversions() {
let h: Hex = (1, 2).into();
assert_eq!(h, hex!(1, 2));
let h: Hex = [3, 4].into();
assert_eq!(h, hex!(3, 4));
let t: (i32, i32) = hex!(5, 6).into();
assert_eq!(t, (5, 6));
let a: [i32; 2] = hex!(7, 8).into();
assert_eq!(a, [7, 8]);
}
#[test]
fn i8_boundaries() {
let h = Hex::<i8>::new(i8::MAX, i8::MIN);
assert_eq!(h.q, i8::MAX);
assert_eq!(h.r, i8::MIN);
}
extern crate alloc;
}