use core::marker::PhantomData;
use crate::{Hex, int::SignedInt, internal::Sealed};
pub struct OddR;
pub struct EvenR;
pub struct OddQ;
pub struct EvenQ;
impl Sealed for OddR {}
impl Sealed for EvenR {}
impl Sealed for OddQ {}
impl Sealed for EvenQ {}
#[allow(private_bounds)]
pub trait OffsetScheme<T: SignedInt>: Sealed + Sized {
fn from_axial(hex: Hex<T>) -> OffsetHex<T, Self>;
fn to_axial(offset: OffsetHex<T, Self>) -> Hex<T>;
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct OffsetHex<T: SignedInt, S: OffsetScheme<T>> {
pub col: T,
pub row: T,
_scheme: PhantomData<S>,
}
impl<T: SignedInt, S: OffsetScheme<T>> OffsetHex<T, S> {
#[must_use]
pub const fn new(col: T, row: T) -> Self {
Self {
col,
row,
_scheme: PhantomData,
}
}
#[must_use]
pub fn to_hex(self) -> Hex<T> {
S::to_axial(self)
}
}
fn parity<T: SignedInt>(v: T) -> T {
let two = T::ONE + T::ONE;
v - (v / two) * two
}
fn r_offset_to_axial<T: SignedInt>(col: T, row: T, shift: T) -> Hex<T> {
let two = T::ONE + T::ONE;
let q = col - (row - shift * parity(row)) / two;
Hex::new(q, row)
}
fn axial_to_r_offset<T: SignedInt>(hex: Hex<T>, shift: T) -> (T, T) {
let two = T::ONE + T::ONE;
let col = hex.q + (hex.r - shift * parity(hex.r)) / two;
(col, hex.r)
}
fn q_offset_to_axial<T: SignedInt>(col: T, row: T, shift: T) -> Hex<T> {
let two = T::ONE + T::ONE;
let r = row - (col - shift * parity(col)) / two;
Hex::new(col, r)
}
fn axial_to_q_offset<T: SignedInt>(hex: Hex<T>, shift: T) -> (T, T) {
let two = T::ONE + T::ONE;
let row = hex.r + (hex.q - shift * parity(hex.q)) / two;
(hex.q, row)
}
impl<T: SignedInt> OffsetScheme<T> for OddR {
fn from_axial(hex: Hex<T>) -> OffsetHex<T, Self> {
let (col, row) = axial_to_r_offset(hex, T::ONE);
OffsetHex::new(col, row)
}
fn to_axial(o: OffsetHex<T, Self>) -> Hex<T> {
r_offset_to_axial(o.col, o.row, T::ONE)
}
}
impl<T: SignedInt> OffsetScheme<T> for EvenR {
fn from_axial(hex: Hex<T>) -> OffsetHex<T, Self> {
let (col, row) = axial_to_r_offset(hex, T::NEG_ONE);
OffsetHex::new(col, row)
}
fn to_axial(o: OffsetHex<T, Self>) -> Hex<T> {
r_offset_to_axial(o.col, o.row, T::NEG_ONE)
}
}
impl<T: SignedInt> OffsetScheme<T> for OddQ {
fn from_axial(hex: Hex<T>) -> OffsetHex<T, Self> {
let (col, row) = axial_to_q_offset(hex, T::ONE);
OffsetHex::new(col, row)
}
fn to_axial(o: OffsetHex<T, Self>) -> Hex<T> {
q_offset_to_axial(o.col, o.row, T::ONE)
}
}
impl<T: SignedInt> OffsetScheme<T> for EvenQ {
fn from_axial(hex: Hex<T>) -> OffsetHex<T, Self> {
let (col, row) = axial_to_q_offset(hex, T::NEG_ONE);
OffsetHex::new(col, row)
}
fn to_axial(o: OffsetHex<T, Self>) -> Hex<T> {
q_offset_to_axial(o.col, o.row, T::NEG_ONE)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hex;
fn roundtrip<S: OffsetScheme<i32>>(q: i32, r: i32) {
let h = hex!(q, r);
let o = h.to_offset::<S>();
assert_eq!(o.to_hex(), h, "round-trip failed for hex!({q}, {r})");
}
#[test]
fn odd_r_roundtrip() {
for q in -3..=3 {
for r in -3..=3 {
roundtrip::<OddR>(q, r);
}
}
}
#[test]
fn even_r_roundtrip() {
for q in -3..=3 {
for r in -3..=3 {
roundtrip::<EvenR>(q, r);
}
}
}
#[test]
fn odd_q_roundtrip() {
for q in -3..=3 {
for r in -3..=3 {
roundtrip::<OddQ>(q, r);
}
}
}
#[test]
fn even_q_roundtrip() {
for q in -3..=3 {
for r in -3..=3 {
roundtrip::<EvenQ>(q, r);
}
}
}
#[test]
fn odd_r_known_values() {
let o = hex!(1, 2).to_offset::<OddR>();
assert_eq!(o.col, 2);
assert_eq!(o.row, 2);
}
#[test]
fn negative_coords_roundtrip() {
for q in -5..=0 {
for r in -5..=0 {
roundtrip::<OddR>(q, r);
roundtrip::<EvenR>(q, r);
roundtrip::<OddQ>(q, r);
roundtrip::<EvenQ>(q, r);
}
}
}
#[test]
fn zero_row_and_col() {
roundtrip::<OddR>(0, 0);
roundtrip::<EvenR>(0, 0);
roundtrip::<OddQ>(0, 0);
roundtrip::<EvenQ>(0, 0);
roundtrip::<OddR>(3, 0);
roundtrip::<OddR>(0, 3);
}
}