#![cfg_attr(feature = "nightly", feature(step_trait))]
#![cfg_attr(feature = "nightly", feature(coroutines))]
#![cfg_attr(feature = "nightly", feature(iter_from_coroutine))]
#![cfg_attr(feature = "nightly", feature(impl_trait_in_assoc_type))]
#![cfg_attr(feature = "nightly", feature(doc_cfg))]
use derive_more::with_trait::Sub;
use derive_more::{Add, Display, From, Into, Mul, Neg};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use serde::{Deserialize, Serialize};
use std::ops;
#[derive(
Debug,
Copy,
Clone,
PartialOrd,
Ord,
PartialEq,
Eq,
Add,
Sub,
Mul,
Display,
From,
Into,
Hash,
Serialize,
Deserialize,
)]
pub struct TileIndex(pub u64);
cfg_if::cfg_if! {if #[cfg(feature = "nightly")] {
#[cfg(any(feature = "nightly", doc))]
#[doc(cfg(feature = "nightly"))]
impl std::iter::Step for TileIndex {
fn steps_between(start: &Self, end: &Self) -> (usize, Option<usize>) {
return if end < start {
(0, None)
} else {
match usize::try_from((*end - *start).value() as usize) {
Result::Ok(value) => (value, Some(value)),
Result::Err(_) => (usize::MAX, None),
}
};
}
fn forward_checked(start: Self, count: usize) -> Option<Self> {
Some(
u64::try_from((start + u64::try_from(count).ok()?).value())
.ok()?
.into(),
)
}
fn backward_checked(start: Self, count: usize) -> Option<Self> {
(start - u64::try_from(count).ok()?).try_into().ok()
}
}
}}
#[derive(Debug, Copy, Clone, PartialEq, Add, Sub, Mul, Display, From, Serialize, Deserialize)]
pub struct RingIndex(u64);
impl TileIndex {
pub fn value(&self) -> u64 {
self.0
}
pub fn is_origin(&self) -> bool {
self.0 == 0
}
}
impl RingIndex {
pub const ORIGIN_RING: RingIndex = RingIndex(1);
pub fn value(&self) -> u64 {
let val = self.0;
assert!(val > 0, "{val} is not a valid RingIndex value.");
val
}
}
impl ops::Add<u64> for TileIndex {
type Output = TileIndex;
fn add(self, rhs: u64) -> TileIndex {
TileIndex(self.value() + rhs)
}
}
impl ops::Sub<u64> for TileIndex {
type Output = TileIndex;
fn sub(self, rhs: u64) -> TileIndex {
assert!(rhs <= self.value());
TileIndex(self.value() - rhs)
}
}
impl ops::Rem<u64> for TileIndex {
type Output = TileIndex;
fn rem(self, rhs: u64) -> TileIndex {
TileIndex(self.value() % rhs)
}
}
impl ops::Add<TileIndex> for u64 {
type Output = TileIndex;
fn add(self, rhs: TileIndex) -> TileIndex {
TileIndex(self + rhs.value())
}
}
impl ops::Add<u64> for RingIndex {
type Output = RingIndex;
fn add(self, rhs: u64) -> RingIndex {
RingIndex(self.value() + rhs)
}
}
impl ops::Add<RingIndex> for u64 {
type Output = RingIndex;
fn add(self, rhs: RingIndex) -> RingIndex {
RingIndex(self + rhs.value())
}
}
impl ops::Sub<u64> for RingIndex {
type Output = RingIndex;
fn sub(self, rhs: u64) -> RingIndex {
assert!(rhs <= self.value());
RingIndex(self.value() - rhs)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Add, Display, From, Into, Serialize, Deserialize)]
pub struct Ring {
n: RingIndex,
}
#[derive(Debug, Copy, Clone, PartialEq, Display, From, Into, Serialize, Deserialize)]
#[display("HGSTile: {h}")]
pub struct HGSTile {
h: TileIndex,
ring: Ring,
}
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Display,
From,
Into,
Eq,
Neg,
Add,
Mul,
Sub,
Serialize,
Deserialize,
)]
#[display("CCTile: ({q}, {r},{s})")]
pub struct CCTile {
q: i64,
r: i64,
s: i64,
}
#[derive(Debug, Copy, Clone, PartialEq, Add, Display, From, Into, Serialize, Deserialize)]
pub struct RingEdge(u8);
#[derive(Debug, Copy, Clone, PartialEq, Add, Display, TryFromPrimitive, IntoPrimitive, Eq)]
#[repr(u8)]
pub enum RingCornerIndex {
RIGHT = 0,
TOPRIGHT = 1,
TOPLEFT = 2,
LEFT = 3,
BOTTOMLEFT = 4,
BOTTOMRIGHT = 5,
}
impl RingEdge {
pub fn start(&self) -> RingCornerIndex {
TryInto::<RingCornerIndex>::try_into(self.0).unwrap()
}
pub fn end(&self) -> RingCornerIndex {
TryInto::<RingCornerIndex>::try_into((self.0 + 1) % 6).unwrap()
}
pub fn direction(&self) -> RingCornerIndex {
self.end().next()
}
pub fn from_corners<'a>(a: &'a RingCornerIndex, b: &'a RingCornerIndex) -> Self {
assert_ne!(a, b);
let a_val: u8 = (*a).into();
let b_val: u8 = (*b).into();
let edge_start: RingCornerIndex =
RingCornerIndex::try_from_primitive(u8::min(a_val, b_val)).unwrap();
if ((*b == RingCornerIndex::BOTTOMRIGHT) && (*a == RingCornerIndex::RIGHT))
|| ((*a == RingCornerIndex::BOTTOMRIGHT) && (*b == RingCornerIndex::RIGHT))
{
return Self(5);
} else {
let res: RingEdge = u8::from(edge_start).into();
debug_assert_eq!(edge_start, res.start());
res
}
}
pub fn from_primitive(p: u8) -> Self {
Self(p % 6)
}
}
impl RingCornerIndex {
pub fn next(&self) -> Self {
let v = *self as u8;
let v2 = match v {
0..=4 => v + 1,
5 => 0,
6_u8..=u8::MAX => panic!("Invalid corner index used as start."),
};
TryInto::<RingCornerIndex>::try_into(v2).unwrap()
}
pub fn all() -> impl std::iter::Iterator<Item = RingCornerIndex> {
Self::all_from(Self::RIGHT)
}
pub fn all_from(start: RingCornerIndex) -> impl std::iter::Iterator<Item = RingCornerIndex> {
let mut ctr = start;
let mut done = false;
std::iter::from_fn(move || {
ctr = ctr.next();
if done {
return None;
}
if ctr == start {
done = true;
};
return Some(ctr);
})
}
}
impl HGSTile {
pub fn new(tile_index: TileIndex) -> Self {
return Self {
h: tile_index,
ring: Ring::new(ring_index_for_tile_index(tile_index)),
};
}
pub fn make(tile_index: u64) -> Self {
HGSTile::new(TileIndex(tile_index))
}
pub fn is_origin_tile(&self) -> bool {
self.h == TileIndex(0)
}
pub fn increment_spiral(&self) -> Self {
Self::new(self.h + TileIndex(1))
}
pub fn spiral_steps(&self, steps: i64) -> Self {
assert!(steps <= self.h.value() as i64);
Self::new(TileIndex((self.h.value() as i64 + steps) as u64))
}
pub fn ring_steps(&self, steps: i64) -> Self {
let ring_size = self.ring.size();
let ring_min = self.ring_min();
let current_offset_in_ring = self.h.value() - ring_min.h.value();
let new_offset_in_ring = (current_offset_in_ring
+ (steps.rem_euclid(ring_size as i64) as u64))
.rem_euclid(ring_size);
Self::new(ring_min.h + new_offset_in_ring)
}
pub fn decrement_spiral(&self) -> Self {
assert!(
self.h.value() > 0,
"Can not decrement from the origin-tile."
);
Self::new(self.h - TileIndex(1))
}
pub fn ring_max(&self) -> Self {
HGSTile::new(self.ring.max())
}
pub fn ring_min(&self) -> Self {
HGSTile::new(self.ring.min())
}
pub fn ring_edge(&self) -> RingEdge {
self.ring.edge(self.h)
}
pub fn cc(&self) -> CCTile {
self.into()
}
pub fn spiral_index(&self) -> TileIndex {
self.h
}
pub fn ring(&self) -> Ring {
self.ring
}
}
impl Sub<i32> for TileIndex {
type Output = TileIndex;
fn sub(self, rhs: i32) -> Self::Output {
TileIndex((self.value() as i64 - rhs as i64) as u64)
}
}
impl Ring {
pub fn new(ring_index: RingIndex) -> Self {
Ring { n: ring_index }
}
pub fn from_tile_index(tile_index: &TileIndex) -> Self {
Ring::new(ring_index_for_tile_index(*tile_index))
}
pub fn next_ring(&self) -> Ring {
Ring { n: self.n + 1 }
}
pub fn prev_ring(&self) -> Ring {
assert!(self.n.value() > 0, "Cannot decrement the innermost circle.");
Ring { n: self.n - 1 }
}
pub fn neighbors_in_ring(&self, tile: &HGSTile) -> Vec<HGSTile> {
let base = ring_min(self.n);
let h_offset = tile.h - base;
let size = ring_size(self.n);
let lesser_neighbor: HGSTile = HGSTile::new(base + ((h_offset - 1) % size));
let greater_neighbor: HGSTile = HGSTile::new(base + ((h_offset + 1) % size));
return vec![lesser_neighbor, greater_neighbor];
}
pub fn full_edge_size(&self) -> u64 {
return self.n.value();
}
pub fn size(&self) -> u64 {
return ring_size(self.n);
}
pub fn min(&self) -> TileIndex {
return ring_min(self.n);
}
pub fn max(&self) -> TileIndex {
return ring_max(self.n);
}
pub fn edge(&self, h: TileIndex) -> RingEdge {
assert!(!h.is_origin());
let b = self.min();
let ring = Ring::from_tile_index(&h);
let side_size = ring.full_edge_size() - 1;
assert!(h >= b);
let offset: u64 = (h - b).value();
let ring_size = ring.size();
assert!(
offset < ring_size,
"offset={offset:?}, ringsize={ring_size:?}, h={h:?}, ring={ring:?}"
);
return RingEdge::from_primitive((offset / side_size).try_into().unwrap());
}
pub fn edge_size(&self) -> u64 {
self.full_edge_size() - 1
}
pub fn corner(&self, c: RingCornerIndex) -> TileIndex {
let val: u8 = c.into();
if val == 0 {
return self.max();
}
return self.min() + self.edge_size() * (val as u64) - 1;
}
pub fn random_tile_in_ring<RNG: rand::Rng>(&self, rng: &mut RNG) -> TileIndex {
TileIndex(rng.random_range(self.min().value()..=self.max().value()))
}
pub fn radius(&self) -> u64 {
(self.n - RingIndex::ORIGIN_RING).value()
}
pub fn ring_index(&self) -> RingIndex {
self.n
}
}
fn ring_size(n: RingIndex) -> u64 {
if n == RingIndex::ORIGIN_RING {
return 1;
}
return (n.value() - 1) * 6;
}
fn ring_max(n: RingIndex) -> TileIndex {
if n.value() == 0 {
return TileIndex(0);
}
return TileIndex(3 * (n.value() - 1) * n.value());
}
pub fn ring_index_for_tile_index(h: TileIndex) -> RingIndex {
return RingIndex((1. / 6. * (3. + f64::sqrt((12 * h.value() + 9) as f64))).ceil() as u64);
}
fn ring_min(n: RingIndex) -> TileIndex {
if n == RingIndex::ORIGIN_RING {
return TileIndex(0);
}
return ring_max(n - 1) + 1;
}
impl Sub<i64> for TileIndex {
type Output = TileIndex;
fn sub(self, rhs: i64) -> Self::Output {
TileIndex(((self.value() as i64) - rhs) as u64)
}
}
impl CCTile {
pub fn new(tile_index: TileIndex) -> Self {
HGSTile::new(tile_index).into()
}
pub fn make(tile_index: u64) -> Self {
HGSTile::make(tile_index).into()
}
pub fn origin() -> CCTile {
CCTile::from_qrs(0, 0, 0)
}
pub fn from_qrs(q: i64, r: i64, s: i64) -> CCTile {
assert_eq!(
q + r + s,
0,
"q + r + s does not sum up to zero for the tile ({q},{r},{s})!"
);
CCTile { q, r, s }
}
pub fn from_qr(q: i64, r: i64) -> CCTile {
CCTile::from_qrs(q, r, 0 - q - r)
}
pub fn from_qrs_tuple(t: (i64, i64, i64)) -> CCTile {
CCTile::from_qrs(t.0, t.1, t.2)
}
pub fn into_qrs_tuple(&self) -> (i64, i64, i64) {
(self.q, self.r, self.s)
}
pub fn unit(direction: &RingCornerIndex) -> CCTile {
CCTile::from_qrs_tuple(match direction {
RingCornerIndex::RIGHT => (1, 0, -1),
RingCornerIndex::TOPRIGHT => (1, -1, 0),
RingCornerIndex::TOPLEFT => (0, -1, 1),
RingCornerIndex::LEFT => (-1, 0, 1),
RingCornerIndex::BOTTOMLEFT => (-1, 1, 0),
RingCornerIndex::BOTTOMRIGHT => (0, 1, -1),
})
}
pub fn units() -> impl Iterator<Item = CCTile> {
RingCornerIndex::all().map(|rci| CCTile::unit(&rci))
}
pub fn corner_to_direction(corner: &Self) -> RingCornerIndex {
assert!(corner.is_corner());
assert!(!corner.is_origin_tile());
if corner.r == 0 {
if corner.q > 0 {
return RingCornerIndex::RIGHT;
} else {
return RingCornerIndex::LEFT;
}
}
if corner.q == 0 {
if corner.r < 0 {
return RingCornerIndex::TOPLEFT;
} else {
return RingCornerIndex::BOTTOMRIGHT;
}
}
if corner.s == 0 {
if corner.q > 0 {
return RingCornerIndex::TOPRIGHT;
} else {
return RingCornerIndex::BOTTOMLEFT;
}
}
panic!("This can't happen. A non-origin corner tile has no axis set to 0.")
}
pub fn is_corner(&self) -> bool {
self.q == 0 || self.r == 0 || self.s == 0
}
pub fn norm_euclidean(&self) -> f64 {
let li = Self::origin().euclidean_distance_to(self);
let redblob = f64::sqrt(self.norm_euclidean_sq() as f64);
debug_assert_eq!(li, redblob as f64);
redblob
}
pub fn norm_euclidean_sq(&self) -> i64 {
self.q * self.q + self.r * self.r + self.q * self.r
}
pub fn norm_steps(&self) -> u64 {
self.max_coord()
}
fn max_coord(&self) -> u64 {
let result = [self.q, self.r, self.s]
.iter()
.map(|coord| coord.abs() as u64)
.max()
.unwrap();
assert!(result > 0 || *self == Self::from_qrs(0, 0, 0));
result
}
pub fn grid_distance_to(&self, other: &CCTile) -> u64 {
(*self - *other).norm_steps()
}
pub fn euclidean_distance_sq(&self, other: &CCTile) -> i64 {
let dq = self.q - other.q;
let dr = self.r - other.r;
let redblob = dq * dq + dr * dr + dq * dr;
redblob
}
pub fn euclidean_distance_to(&self, other: &CCTile) -> f64 {
f64::sqrt(self.euclidean_distance_sq(other) as f64)
}
pub fn is_origin_tile(&self) -> bool {
self.q == 0 && self.r == 0 && self.s == 0
}
pub fn wedge_around_ringcorner(&self) -> Vec<RingCornerIndex> {
if self.is_origin_tile() {
return vec![];
}
let q_r = (self.q - self.r).abs();
let r_s = (self.r - self.s).abs();
let s_q = (self.s - self.q).abs();
let rci_qr = if self.q - self.r > 0 {
RingCornerIndex::TOPRIGHT
} else {
RingCornerIndex::BOTTOMLEFT
};
let rci_rs = if self.r - self.s > 0 {
RingCornerIndex::BOTTOMRIGHT
} else {
RingCornerIndex::TOPLEFT
};
let rci_sq = if self.s - self.q > 0 {
RingCornerIndex::LEFT
} else {
RingCornerIndex::RIGHT
};
let corner_indices = [rci_sq, rci_rs, rci_qr];
let axes = [s_q, r_s, q_r];
let mut sorted_indices = [0, 1, 2];
sorted_indices.sort_by_key(|el| axes[*el]);
let [_min_index, middle_index, max_index] = sorted_indices;
let max: i64 = axes[sorted_indices[2]];
let middle: i64 = axes[sorted_indices[1]];
let min: i64 = axes[sorted_indices[0]];
assert!(min <= max);
if max > middle {
return vec![corner_indices[max_index]];
}
assert_eq!(middle, max);
let edge =
RingEdge::from_corners(&corner_indices[middle_index], &corner_indices[max_index]);
return vec![edge.start(), edge.end()];
}
pub fn closest_corner_hgs(&self) -> HGSTile {
assert!(!self.is_origin_tile());
let w = self.wedge_around_ringcorner()[0];
let r = Ring::from(*self);
HGSTile::new(r.corner(w))
}
pub fn previous_corner_cc(&self) -> CCTile {
assert!(!self.is_origin_tile());
let closest_corner = self.closest_corner_cc();
if &closest_corner == self {
return closest_corner;
}
let pre_closest_corner = closest_corner.rot60cw();
if self.are_colinear_with(&pre_closest_corner, &closest_corner) {
return pre_closest_corner;
}
return closest_corner;
}
pub fn is_colinear_with(&self, other: &CCTile) -> bool {
self.q == other.q || self.r == other.r || self.s == other.s
}
pub fn are_colinear_with(&self, other: &CCTile, third: &CCTile) -> bool {
if other.q == third.q {
if self.q == other.q {
return true;
}
}
if other.r == third.r {
if self.r == other.r {
return true;
}
}
if other.s == third.s {
if self.s == other.s {
return true;
}
}
return false;
}
pub fn closest_corner_cc(&self) -> CCTile {
assert!(!self.is_origin_tile());
let w = self.wedge_around_ringcorner()[0];
let r = Ring::from(*self);
let direction = CCTile::unit(&w);
direction * ((r.n.value() as i64) - 1)
}
pub fn rot60ccw(&self) -> CCTile {
CCTile::from_qrs(-self.s, -self.q, -self.r)
}
pub fn rot60cw(&self) -> CCTile {
CCTile::from_qrs(-self.r, -self.s, -self.q)
}
pub fn reflect_along_constant_axis(&self, q_: bool, r_: bool, s_: bool) -> CCTile {
let (mut q, mut r, mut s) = (self.q, self.r, self.s);
if q_ {
(r, s) = (s, r);
}
if r_ {
(q, s) = (s, q);
}
if s_ {
(q, r) = (r, q);
}
CCTile::from_qrs(q, r, s)
}
pub fn reflect_orthogonally_across_constant_axis(
&self,
q_: bool,
r_: bool,
s_: bool,
) -> CCTile {
self.reflect_along_constant_axis(q_, r_, s_)
.reflect_diagonally()
}
pub fn reflect_diagonally(&self) -> CCTile {
CCTile::from_qrs(-self.q, -self.r, -self.s)
}
pub fn reflect_vertically(&self) -> CCTile {
self.reflect_orthogonally_across_constant_axis(false, true, false)
}
pub fn reflect_horizontally(&self) -> CCTile {
self.reflect_along_constant_axis(false, true, false)
}
pub fn reflect_with_reference_point(
&self,
reference_point: &CCTile,
reflection_fn: fn(&CCTile) -> CCTile,
) -> CCTile {
let shifted = *self - *reference_point;
let reflected = reflection_fn(&shifted);
let unshifted = reflected + *reference_point;
unshifted
}
pub fn spiral_steps(&self, steps: i64) -> Self {
let ht: HGSTile = (*self).into();
let ht2 = ht.spiral_steps(steps);
ht2.into()
}
pub fn to_pixel(&self, origin: (f64, f64), unit_step: f64) -> (f64, f64) {
let outer_radius = unit_step / f64::sqrt(3.);
let _redblob_size = outer_radius;
let x = unit_step * ((self.q as f64) + (self.r as f64) / 2.);
let y = f64::sqrt(3.) / 2. * unit_step * (-self.r as f64);
return (origin.0 + x, origin.1 + y);
}
pub fn hgs(&self) -> HGSTile {
self.into()
}
pub fn spiral_index(&self) -> TileIndex {
self.hgs().h
}
pub fn from_pixel(pixel: (f64, f64)) -> Self {
Self::from_pixel_(pixel, (0., 0.), 1.)
}
pub fn from_pixel_(pixel: (f64, f64), origin: (f64, f64), unit_step: f64) -> Self {
let x = pixel.0 - origin.0;
let y = pixel.1 - origin.1;
let r = -2. / f64::sqrt(3.) * y / unit_step;
let q = x / unit_step - (r / 2.);
Self::round_to_nearest_tile(q, r)
}
pub fn round_to_nearest_tile(frac_q: f64, frac_r: f64) -> Self {
let frac_s: f64 = 0. - frac_q - frac_r;
let mut q = f64::round(frac_q);
let mut r = f64::round(frac_r);
let mut s = f64::round(frac_s);
let q_diff = f64::abs(q - frac_q);
let r_diff = f64::abs(r - frac_r);
let s_diff = f64::abs(s - frac_s);
if q_diff > r_diff && q_diff > s_diff {
q = -r - s
} else if r_diff > s_diff {
r = -q - s
} else {
s = -q - r
};
Self::from_qrs(q as i64, r as i64, s as i64)
}
pub fn movement_range(&self, steps: u64) -> MovementRange {
let n: i64 = steps as i64;
MovementRange {
q_min: self.q - n,
q_max: self.q + n,
r_min: self.r - n,
r_max: self.r + n,
s_min: self.s - n,
s_max: self.s + n,
}
}
pub fn pixel_step_vertical(unit_step: f64) -> (f64, f64) {
(unit_step, unit_step * f64::sqrt(3.) / 2.)
}
}
#[derive(Copy, Clone, Debug, Serialize, Deserialize)]
pub struct MovementRange {
q_min: i64,
q_max: i64,
r_min: i64,
r_max: i64,
s_min: i64,
s_max: i64,
}
impl MovementRange {
pub fn around(tile: &CCTile, steps: i64) -> Self {
let n: i64 = i64::abs(steps);
MovementRange {
q_min: tile.q - n,
q_max: tile.q + n,
r_min: tile.r - n,
r_max: tile.r + n,
s_min: tile.s - n,
s_max: tile.s + n,
}
}
pub fn intersect(&self, other: &MovementRange) -> MovementRange {
MovementRange {
q_min: i64::max(self.q_min, other.q_min),
q_max: i64::min(self.q_max, other.q_max),
r_min: i64::max(self.r_min, other.r_min),
r_max: i64::min(self.r_max, other.r_max),
s_min: i64::max(self.s_min, other.s_min),
s_max: i64::min(self.s_max, other.s_max),
}
}
pub fn contains(&self, tile: &CCTile) -> bool {
tile.q <= self.q_max
&& tile.r <= self.r_max
&& tile.s <= self.s_max
&& self.q_min <= tile.q
&& self.r_min <= tile.r
&& self.s_min <= tile.s
}
cfg_if::cfg_if! {if #[cfg(feature = "nightly")] {
#[cfg(any(feature = "nightly", doc))]
#[doc(cfg(feature = "nightly"))]
pub fn count_tiles(&self) -> usize {
let mut count = 0;
for _ in *self {
count += 1;
}
return count;
}
}
}
}
cfg_if::cfg_if! {if #[cfg(feature = "nightly")] {
#[cfg(any(feature = "nightly", doc))]
#[doc(cfg(feature = "nightly"))]
impl IntoIterator for MovementRange {
type Item = CCTile;
type IntoIter = impl Iterator<Item = Self::Item>;
fn into_iter(self) -> Self::IntoIter {
let it = std::iter::from_coroutine(
#[coroutine]
move || {
for q in (self.q_min)..=self.q_max {
let r_min = i64::max(self.r_min, -q - self.s_max);
let r_max = i64::min(self.r_max, -q - self.s_min);
for r in r_min..=r_max {
#[cfg(feature = "nightly")]
yield CCTile::from_qr(q, r);
}
}
},
);
it
}
}
}}
impl From<HGSTile> for CCTile {
fn from(item: HGSTile) -> Self {
(&item).into()
}
}
impl From<&HGSTile> for CCTile {
fn from(item: &HGSTile) -> Self {
if item.is_origin_tile() {
return CCTile::origin();
}
let edge_section = item.ring_edge();
let corner_index = edge_section.start();
let cc_axis_unit = CCTile::unit(&corner_index);
let cc_edge_start = cc_axis_unit * (item.ring.radius() as i64);
let cc_edge_unit = CCTile::unit(&edge_section.direction());
let corner_h = item.ring.corner(corner_index);
if corner_h > item.h {
let fake_corner_h = corner_h - item.ring.size();
let steps_from_corner = item.h - fake_corner_h;
return cc_edge_start + cc_edge_unit * (steps_from_corner.value() as i64);
}
let steps_from_corner = item.h - corner_h;
return cc_edge_start + cc_edge_unit * (steps_from_corner.value() as i64);
}
}
impl From<&CCTile> for Ring {
fn from(item: &CCTile) -> Self {
Ring::new(RingIndex(item.norm_steps() + 1))
}
}
impl From<CCTile> for Ring {
fn from(item: CCTile) -> Self {
(&item).into()
}
}
impl From<&CCTile> for HGSTile {
fn from(item: &CCTile) -> Self {
let ring = Ring::from(item);
let ring_index = ring.n;
if ring.n == RingIndex::ORIGIN_RING {
return HGSTile::new(TileIndex(0));
}
assert!(
!item.is_origin_tile(),
"This can not happen, the ring index of {item:?} is not 1. It is {ring_index:?}"
);
let wedges = item.wedge_around_ringcorner();
let closest_corner_hgs = HGSTile::new(ring.corner(wedges[0]));
if wedges.len() == 2 {
assert_eq!(ring.full_edge_size() % 2, 1);
let corner0_hgs = closest_corner_hgs;
let corner1_hgs = HGSTile::new(ring.corner(wedges[1]));
if corner1_hgs.h < corner0_hgs.h {
return HGSTile::new(corner0_hgs.ring_min().h + ring.edge_size() / 2 - 1);
} else {
return HGSTile::new(corner0_hgs.h + ring.edge_size() / 2);
}
}
assert_eq!(wedges.len(), 1);
let previous_corner = &item.previous_corner_cc();
let offset_along_edge_hgs = item.grid_distance_to(previous_corner);
let rci = CCTile::corner_to_direction(previous_corner);
let previous_corner_hgs = HGSTile::new(ring.corner(rci));
return previous_corner_hgs.ring_steps(offset_along_edge_hgs as i64);
}
}
impl From<CCTile> for HGSTile {
fn from(item: CCTile) -> Self {
(&item).into()
}
}
#[cfg(test)]
mod test {
use super::*;
use approx_eq::assert_approx_eq;
#[test]
fn test_multiplication_with_unit() {
let u = CCTile::unit(&RingCornerIndex::RIGHT);
let two_u_mul = u * 2;
let two_u_add = u + u;
assert_eq!(two_u_mul, two_u_add);
println!("Testing Tests works.");
}
#[test]
fn test_hexgridspiral_cc_conversion() {
let origin = CCTile::from_qr(0, 0);
let tile0: HGSTile = origin.into();
let h0 = tile0.h;
assert_eq!(h0, TileIndex(0));
let o_cc: CCTile = CCTile::from(HGSTile::make(9));
let nine = CCTile::from_qr(1, -2);
assert_eq!(nine, o_cc);
}
#[test]
fn test_hexgridspiral_cc_conversion_issue1() {
let hgs_tile = HGSTile::make(37);
assert_eq!(hgs_tile.h, TileIndex(37), "TileIndex was wrong A1");
let cc_37_from_hgs: CCTile = CCTile::from(HGSTile::make(37));
let cc_37_from_qr = CCTile::from_qr(4, -1);
assert_eq!(cc_37_from_hgs, cc_37_from_qr);
let cc_37_from_qrs = CCTile::from_qrs(4, -1, -3);
assert_eq!(cc_37_from_hgs, cc_37_from_qrs);
let hgs_from_cc_from_hgs: HGSTile = cc_37_from_hgs.into();
let h_37_from_cc_from_hgs = hgs_from_cc_from_hgs.h;
assert_eq!(
h_37_from_cc_from_hgs,
TileIndex(37),
"TileIndex was wrong (cc from hgs)"
);
let hgs_37_from_qr: HGSTile = cc_37_from_qr.into();
let h_37_from_qr = hgs_37_from_qr.h;
assert_eq!(h_37_from_qr, TileIndex(37), "TileIndex was wrong (qr)");
let hgs_37: HGSTile = cc_37_from_qrs.into();
let h_37 = hgs_37.h;
assert_eq!(h_37, TileIndex(37), "TileIndex was wrong (qrs)");
}
#[test]
fn test_cc_hexgridspiral_conversion() {
let origin = HGSTile::make(0);
let tile0: CCTile = origin.into();
assert_eq!(tile0, CCTile::origin());
let nine = CCTile::from_qr(1, -2);
let nine_to_hgs = nine.hgs();
assert_eq!(nine_to_hgs, HGSTile::make(9));
let seven2 = CCTile::from_qr(2, -1);
assert_eq!(seven2.hgs(), HGSTile::make(7));
let seven = CCTile::from_qrs(2, -1, -1);
assert_eq!(seven.hgs(), HGSTile::make(7));
let eight = CCTile::from_qrs(2, -2, 0);
assert_eq!(eight.hgs(), HGSTile::make(8));
let tile_a = CCTile::from_qr(1, -3);
assert_eq!(tile_a.hgs(), HGSTile::make(23));
}
#[test]
fn test_hexcount_steps_from_zero() {
let start = HGSTile::new(TileIndex(0));
let mut x1 = start.increment_spiral();
for _i in 1..6 {
x1 = x1.increment_spiral();
}
let x2 = start.ring.next_ring().max();
assert!(x1.h == x2, "Six spiral-steps should equal one ring-step in the first non-origin ring, but we got {x1} and {x2}.");
assert!(x2.value() == 6);
}
#[test]
fn test_hexcount_steps_from_one() {
let start = HGSTile::new(TileIndex(1));
let mut x1 = start.increment_spiral();
for _i in 1..6 {
x1 = x1.increment_spiral();
}
let x2 = start.ring.next_ring().min();
assert_eq!(x1.h, x2);
}
#[test]
fn test_hexcount_steps_from_seven() {
let start = HGSTile::new(TileIndex(7));
let mut x1 = start;
for _i in 0..start.ring.size() {
x1 = x1.increment_spiral();
}
let x2 = start.ring.next_ring().min();
assert_eq!(x1.h, x2);
}
#[test]
fn test_ring_max_min() {
let min1 = ring_min(RingIndex::ORIGIN_RING.into());
let max1 = ring_max(RingIndex::ORIGIN_RING.into());
assert!(min1.value() == 0);
assert!(max1.value() == 0);
let min2 = ring_min(2.into());
let max2 = ring_max(2.into());
assert!(min2.value() == 1);
assert!(max2.value() == 6, "Should be 6 but was {max1}");
let min3 = ring_min(3.into());
let max3 = ring_max(3.into());
assert_eq!(min3.value(), 7);
assert_eq!(max3.value(), 18);
}
#[test]
fn test_ring_size() {
for n in 1..10 {
let ring = Ring::new(n.into());
assert_eq!(ring.size(), ring.max().value() - ring.min().value() + 1);
}
}
#[test]
fn test_ring_index_for_min() {
let a = ring_index_for_tile_index(TileIndex(0));
let b = ring_index_for_tile_index(TileIndex(1));
let c = ring_index_for_tile_index(TileIndex(7));
assert_eq!(a, RingIndex(1));
assert_eq!(b, RingIndex(2));
assert_eq!(c, RingIndex(3));
}
#[test]
fn test_ring_index_for_max() {
let a = ring_index_for_tile_index(TileIndex(0));
assert_eq!(a, RingIndex(1));
let b = ring_index_for_tile_index(TileIndex(6));
assert_eq!(b, RingIndex(2));
let d = ring_index_for_tile_index(TileIndex(18));
assert_eq!(d, RingIndex(3));
let c = ring_index_for_tile_index(TileIndex(36));
assert_eq!(c, RingIndex(4));
}
#[test]
fn test_ring_index_for_any() {
for h in 1..=6 {
let b = ring_index_for_tile_index(TileIndex(h));
assert_eq!(b, RingIndex(2));
}
for h in 19..=35 {
let d = ring_index_for_tile_index(TileIndex(h));
assert_eq!(d, RingIndex(4));
}
}
#[test]
fn test_ring_max() {
let a = ring_max(RingIndex(1));
let b = ring_max(RingIndex(2));
let c = ring_max(RingIndex(3));
assert_eq!(a, TileIndex(0));
assert_eq!(b, TileIndex(6));
assert_eq!(c, TileIndex(18));
}
#[test]
fn test_wedge_around_ringcorner1() {
let topright = CCTile::from_qr(1, -1);
let rci_vec = topright.wedge_around_ringcorner();
assert_eq!(rci_vec.len(), 1);
let mut rci = rci_vec[0];
assert_eq!(rci, RingCornerIndex::TOPRIGHT);
rci = rci.next();
let topleft = topright + CCTile::unit(&RingCornerIndex::LEFT);
let rci2_vec = topleft.wedge_around_ringcorner();
assert_eq!(rci2_vec.len(), 1);
assert_eq!(rci2_vec[0], rci);
}
#[test]
fn test_wedge_around_ringcorner2() {
let tile = CCTile::from_qrs(1, 3, -4);
let rci_vec = tile.wedge_around_ringcorner();
assert_eq!(rci_vec.len(), 1);
let rci = rci_vec[0];
assert_eq!(rci, RingCornerIndex::BOTTOMRIGHT);
}
#[test]
fn test_wedge_around_ringcorner3() {
let tile = CCTile::from_qrs(-4, 2, 2);
let rci_vec = tile.wedge_around_ringcorner();
assert_eq!(rci_vec.len(), 2);
assert_eq!(rci_vec[0], RingCornerIndex::LEFT);
assert_eq!(rci_vec[1], RingCornerIndex::BOTTOMLEFT);
}
cfg_if::cfg_if! {if #[cfg(feature = "nightly")] {
#[test]
fn test_ring_index_for_tile_index_small() {
for ring_index in 1..3 {
let ring = Ring::new(ring_index.into());
assert!(ring.n.value() > 0);
for tile_index in ring.min()..=ring.max() {
let ring_index_returned = ring_index_for_tile_index(tile_index);
assert_eq!(ring_index, ring_index_returned.value());
}
}
}
}}
#[test]
fn test_ring_index_construction_speed() {
for ring_index in 100000..120000 {
let ring = Ring::new(ring_index.into());
let ring_index_returned = ring_index_for_tile_index(ring.min());
assert_eq!(ring_index, ring_index_returned.value());
}
for ring_index in 100000..120000 {
let ring = Ring::new(ring_index.into());
let ring_index_returned = ring_index_for_tile_index(ring.max());
assert_eq!(ring_index, ring_index_returned.value());
}
for ring_index in 100..120 {
let ring = Ring::new(ring_index.into());
let mid = ring.min() + (ring.size() / 2);
let ring_index_returned = ring_index_for_tile_index(mid);
assert_eq!(ring_index, ring_index_returned.value());
}
}
#[test]
fn test_ring_index_for_tile_index_big() {
let mut rng = <rand_chacha::ChaCha20Rng as rand::SeedableRng>::seed_from_u64(40);
for ring_index in 100..120 {
let ring = Ring::new(ring_index.into());
let tile_index = ring.random_tile_in_ring(&mut rng);
let ring_index_returned = ring_index_for_tile_index(tile_index);
assert_eq!(ring_index, ring_index_returned.value());
}
}
#[test]
fn test_norm_steps() {
let u1 = CCTile::unit(&RingCornerIndex::RIGHT);
assert_eq!(u1.norm_steps(), 1);
let u2 = u1 + u1;
assert_eq!(u2.norm_steps(), 2);
let t1 = CCTile::from_qrs(1, -2, 1);
let t2 = CCTile::from_qrs(2, -2, 0);
let t3 = CCTile::from_qrs(0, 2, -2);
let n1 = t1.norm_steps();
assert_eq!(n1, 2);
assert_eq!(t2.norm_steps(), n1);
assert_eq!(t3.norm_steps(), n1);
assert_eq!(CCTile::from_qr(0, 0).norm_steps(), 0);
for ring_step in 1..4 {
for direction in CCTile::units() {
let tile_a = direction * ring_step;
let tile_b = direction * -ring_step;
assert_eq!(tile_a.norm_steps(), tile_b.norm_steps());
assert_eq!(tile_a.norm_steps(), ring_step.try_into().unwrap());
}
}
let tile35 = HGSTile::make(35).cc();
assert_eq!(tile35.norm_steps(), 3);
}
#[test]
fn test_ring_edge_is_ordered_ccw() {
let ring_edge = RingEdge::from_corners(&RingCornerIndex::RIGHT, &RingCornerIndex::TOPRIGHT);
assert_eq!(
vec![ring_edge.start(), ring_edge.end()],
vec![RingCornerIndex::RIGHT, RingCornerIndex::TOPRIGHT]
);
let ring_edge =
RingEdge::from_corners(&RingCornerIndex::LEFT, &RingCornerIndex::BOTTOMLEFT);
assert_eq!(
vec![ring_edge.start(), ring_edge.end()],
vec![RingCornerIndex::LEFT, RingCornerIndex::BOTTOMLEFT]
);
let ring_edge2 =
RingEdge::from_corners(&RingCornerIndex::BOTTOMRIGHT, &RingCornerIndex::BOTTOMLEFT);
assert_eq!(
vec![ring_edge2.start(), ring_edge2.end()],
vec![RingCornerIndex::BOTTOMLEFT, RingCornerIndex::BOTTOMRIGHT]
);
for rci_a in RingCornerIndex::all() {
let rci_b = rci_a.next();
let ring_edge = RingEdge::from_corners(&rci_a, &rci_b);
assert_eq!(ring_edge.start(), rci_a);
assert_eq!(ring_edge.end(), rci_b);
let ring_edge2 = RingEdge::from_corners(&rci_b, &rci_a);
assert_eq!(ring_edge2.start(), rci_a);
assert_eq!(ring_edge2.end(), rci_b);
}
}
#[test]
fn test_ring_edge() {
let r0 = RingEdge(0);
assert_eq!(r0.start(), RingCornerIndex::RIGHT);
assert_eq!(r0.end(), RingCornerIndex::TOPRIGHT);
let r1 = RingEdge::from_primitive(1);
assert_eq!(
vec![r1.start(), r1.end()],
vec![RingCornerIndex::TOPRIGHT, RingCornerIndex::TOPLEFT]
);
let r2 = RingEdge::from_primitive(2);
assert_eq!(
vec![r2.start(), r2.end()],
vec![RingCornerIndex::TOPLEFT, RingCornerIndex::LEFT]
);
let r3 = RingEdge::from_primitive(3);
assert_eq!(
vec![r3.start(), r3.end()],
vec![RingCornerIndex::LEFT, RingCornerIndex::BOTTOMLEFT]
);
let r4 = RingEdge::from_primitive(4);
assert_eq!(
vec![r4.start(), r4.end()],
vec![RingCornerIndex::BOTTOMLEFT, RingCornerIndex::BOTTOMRIGHT]
);
let r5 = RingEdge::from_primitive(5);
assert_eq!(
vec![r5.start(), r5.end()],
vec![RingCornerIndex::BOTTOMRIGHT, RingCornerIndex::RIGHT]
);
let r6 = RingEdge::from_primitive(6);
assert_eq!(
vec![r6.start(), r6.end()],
vec![RingCornerIndex::RIGHT, RingCornerIndex::TOPRIGHT]
);
}
#[test]
fn test_ring_edge_from_corners() {
let r0 = RingEdge::from_primitive(0);
assert_eq!(
vec![r0.start(), r0.end()],
vec![RingCornerIndex::RIGHT, RingCornerIndex::TOPRIGHT]
);
let ring_corner0: RingCornerIndex = RingCornerIndex::try_from_primitive(0).unwrap();
let ring_corner1: RingCornerIndex = RingCornerIndex::try_from_primitive(1).unwrap();
let r0c = RingEdge::from_corners(&ring_corner0, &ring_corner1);
assert_eq!(
vec![r0c.start(), r0c.end()],
vec![RingCornerIndex::RIGHT, RingCornerIndex::TOPRIGHT]
);
let r0c2 = RingEdge::from_corners(&ring_corner1, &ring_corner0);
assert_eq!(
vec![r0c2.start(), r0c2.end()],
vec![RingCornerIndex::RIGHT, RingCornerIndex::TOPRIGHT]
);
}
#[test]
fn test_ring_corner_index_all() {
let a = RingCornerIndex::all().count();
assert_eq!(a, 6);
}
#[test]
fn test_rot60_cc() {
let mut i = 0;
for r in RingCornerIndex::all_from(RingCornerIndex::BOTTOMLEFT) {
i += 1;
let t = CCTile::unit(&r) * i;
let q = CCTile::unit(&r.next()) * i;
assert_eq!(t.rot60ccw(), q);
assert_eq!(q.rot60cw(), t);
}
for h in (1..100).step_by(7) {
let not_corner: CCTile = HGSTile::new(TileIndex(h)).into();
let a = not_corner.rot60cw().rot60cw().rot60cw();
let b = not_corner.rot60ccw().rot60ccw().rot60ccw();
assert_eq!(a, b);
}
}
#[test]
fn test_euclidean_distance() {
let tile0 = HGSTile::make(0);
let tile1 = CCTile::unit(&RingCornerIndex::RIGHT);
assert_eq!(
tile1.euclidean_distance_to(&Into::<CCTile>::into(tile0)),
1.
);
let tile2 = CCTile::unit(&RingCornerIndex::TOPRIGHT);
assert_eq!(tile1.euclidean_distance_to(&tile1), 0.);
assert_eq!(tile1.euclidean_distance_to(&tile2), 1.);
assert_eq!(tile2.euclidean_distance_to(&tile1), 1.);
let tile7: CCTile = HGSTile::make(7).into();
let tileo: CCTile = CCTile::origin();
assert_eq!(tile7.euclidean_distance_to(&tile1), 1.);
assert_eq!(tile7.euclidean_distance_sq(&tileo), 3);
let tile8 = CCTile::make(8);
assert_eq!(tile8.euclidean_distance_to(&tileo), 2.);
assert_eq!(tile8.euclidean_distance_to(&tile7), 1.);
let tile_minus_8 = tile8 * (-1);
let norm8 = tile8.norm_euclidean();
let norm8_minus = tile_minus_8.norm_euclidean();
assert_eq!(norm8, norm8_minus);
let norm_distance = tile8.euclidean_distance_to(&tile_minus_8);
assert_eq!(norm_distance, 2. * norm8);
}
#[test]
fn test_conversion_to_pixel() {
{
let tile1_cc = CCTile::make(1);
assert_eq!(tile1_cc, CCTile::from_qr(1, -1));
let tile1_px = tile1_cc.to_pixel((0., 0.), 1.);
assert_eq!(tile1_px, (0.5, f64::sqrt(3.) / 2.));
}
let tile1_cc = CCTile::unit(&RingCornerIndex::RIGHT);
let tile1_px = tile1_cc.to_pixel((0., 0.), 1.);
assert_eq!(tile1_px, (1., 0.));
let tile_right = tile1_cc * 5;
assert_eq!(tile_right.to_pixel((1., 2.), 1.), (6., 2.));
assert_eq!(tile1_cc.to_pixel((0., 0.), 10000.), (10000., 0.));
let tile2 = CCTile::unit(&RingCornerIndex::TOPLEFT);
assert_eq!(tile2, CCTile::from_qr(0, -1));
let tile2_px = tile2.to_pixel((0., 0.), 1.);
let inner_radius = 0.5;
let outer_radius = 2. / f64::sqrt(3.) * inner_radius;
let y_should = 1.5 * outer_radius;
assert_approx_eq!(tile2_px.0, -0.5);
assert_approx_eq!(tile2_px.1, y_should);
assert!(f64::abs(tile2_px.1 - y_should) < f64::EPSILON * 5.);
let tile35 = HGSTile::make(35).cc();
let pixel35 = tile35.to_pixel((2., 3.), 4.);
let pxstep = CCTile::pixel_step_vertical(4.);
let should_be_pixel_35 = (2. + 2.5 * pxstep.0, 3. - pxstep.1);
assert_eq!(pixel35, should_be_pixel_35);
}
#[test]
fn test_conversion_from_pixel_and_back2() {
let origin = (0., 0.);
let unit_step = 1.;
{
let h = TileIndex(7);
let tile = CCTile::new(h);
let expected_pixel_x = unit_step * 1.5;
let expected_pixel_y = unit_step * f64::sqrt(3.) / 2.;
let pixel = tile.to_pixel(origin, unit_step);
assert_approx_eq!(pixel.0, expected_pixel_x);
assert_approx_eq!(pixel.1, expected_pixel_y);
let tile2 = CCTile::from_pixel(pixel);
assert_eq!(tile, tile2);
}
}
#[test]
fn test_conversion_from_pixel() {
let origin = (0., 0.);
let unit_step = 1.;
{
let tile = CCTile::make(0);
assert_eq!(tile.to_pixel(origin, unit_step), (0., 0.));
assert_eq!(CCTile::from_pixel((0., 0.)), tile);
}
{
let tile1 = CCTile::make(1);
assert_eq!(CCTile::from_pixel((0.5, f64::sqrt(3.) / 2.)), tile1);
}
{
let tile = CCTile::make(1);
assert_eq!(tile, CCTile::from_qr(1, -1));
let pixel = tile.to_pixel(origin, unit_step);
assert_approx_eq!(pixel.0, 0.5);
assert_approx_eq!(pixel.1, f64::sqrt(3.) / 2. * unit_step);
}
}
#[test]
fn test_conversion_from_pixel_and_back() {
let origin = (0., 0.);
let unit_step = 1.;
for h in [0, 1, 2, 4, 5, 6, 8, 10, 27, 100] {
let tile = CCTile::make(h);
let pixel = tile.to_pixel(origin, unit_step);
let tile2 = CCTile::from_pixel(pixel);
assert_eq!(tile, tile2);
}
}
#[cfg(feature = "nightly")]
#[test]
fn test_movement_range() {
for h in [0, 1, 2, 4, 5, 6, 7, 10, 27, 100] {
let o = CCTile::make(h);
for max_steps in 0..10 {
let m = o.movement_range(max_steps);
for tile in m {
assert!(tile.grid_distance_to(&o) <= max_steps);
}
let collected: Vec<CCTile> = m.into_iter().collect();
let num_tiles = collected.len() as u64;
let s = 1 + 6 * ((0..=max_steps).sum::<u64>());
assert_eq!(s, num_tiles);
}
}
}
#[test]
#[cfg(feature = "nightly")]
fn test_movement_range_intersection() {
let tile_a = CCTile::origin() + CCTile::unit(&RingCornerIndex::LEFT);
let tile_b = CCTile::origin() + CCTile::unit(&RingCornerIndex::RIGHT) * 2;
let range_a = tile_a.movement_range(2);
assert!(range_a.contains(&tile_a));
assert_eq!(range_a.contains(&CCTile::origin()), true);
assert_eq!(range_a.count_tiles(), 19);
let range_b = tile_b.movement_range(1);
assert!(range_b.contains(&tile_b));
assert_eq!(range_b.count_tiles(), 7);
assert_eq!(range_b.contains(&CCTile::origin()), false);
let range_ab = range_a.intersect(&range_b);
assert_eq!(range_ab.contains(&tile_a), false);
assert_eq!(range_ab.contains(&tile_b), false);
assert_eq!(range_ab.contains(&CCTile::origin()), false);
let mut num_elems = 0;
for _elem in range_ab {
num_elems += 1;
}
assert_eq!(num_elems, 1);
}
#[test]
fn test_spiral_steps() {
let ht: HGSTile = CCTile::unit(&RingCornerIndex::RIGHT).into();
assert_eq!(ht.h.value(), 6);
let ht_minus1 = ht.decrement_spiral();
assert_eq!(ht_minus1.h, ht.h - 1);
let ht2 = ht.spiral_steps(3);
assert_eq!(ht2.h.value(), 9);
}
#[test]
fn readme_hgs1() {
let tile = CCTile::from_qrs(2, -1, -1);
assert_eq!(tile.spiral_index(), TileIndex(7));
let tile2 = tile.spiral_steps(2);
assert_eq!(tile2.spiral_index(), TileIndex(9));
assert_eq!(tile2, CCTile::from_qrs(1, -2, 1));
}
#[test]
fn test_reflect() {
let tile = CCTile::from_qrs(4, -3, -1);
let tile_r = tile.reflect_along_constant_axis(false, true, false);
assert_eq!(tile_r, (-1, -3, 4).into());
let tile_q = tile.reflect_along_constant_axis(true, false, false);
assert_eq!(tile_q, (4, -1, -3).into());
let tile_s = tile.reflect_along_constant_axis(false, false, true);
assert_eq!(tile_s, (-3, 4, -1).into());
let tile_reflected = tile.reflect_diagonally();
assert_eq!(tile_reflected, (-4, 3, 1).into());
let tile_ortho_r = tile.reflect_orthogonally_across_constant_axis(false, true, false);
assert_eq!(tile_ortho_r, (1, 3, -4).into());
let tile_ortho_s = tile.reflect_orthogonally_across_constant_axis(false, false, true);
assert_eq!(tile_ortho_s, (3, -4, 1).into());
let tile_ortho_q = tile.reflect_orthogonally_across_constant_axis(true, false, false);
assert_eq!(tile_ortho_q, (-4, 1, 3).into());
}
#[test]
fn test_grid_distance_to() {
let tile_left = CCTile::unit(&RingCornerIndex::LEFT);
let tile_right = CCTile::unit(&RingCornerIndex::RIGHT);
assert_eq!(tile_left.grid_distance_to(&tile_right), 2);
let tile1 = CCTile::from_qrs(1, 2, -3);
assert_eq!(tile1.grid_distance_to(&tile_right), 2);
assert_eq!(tile_right.grid_distance_to(&tile1), 2);
assert_eq!(tile1.grid_distance_to(&tile1), 0);
assert_eq!(tile1.grid_distance_to(&tile_left), 4);
let tile2 = CCTile::from_qrs(-3, 2, 1);
assert_eq!(tile2.grid_distance_to(&tile1), 4);
}
#[test]
fn test_circular_steps_general() {
for idx in 0..=10 {
let hgs_tile = HGSTile::make(idx);
let ring_size = hgs_tile.ring.size() as i64;
let walk_one_circle = hgs_tile.ring_steps(ring_size);
assert_eq!(walk_one_circle, hgs_tile);
let walk_two_circles = hgs_tile.ring_steps(2 * ring_size);
assert_eq!(walk_two_circles, hgs_tile);
let walk_backwards_one_circle = hgs_tile.ring_steps(-1 * ring_size);
assert_eq!(walk_backwards_one_circle, hgs_tile);
let single_step_forward = hgs_tile.ring_steps(1);
let single_step_backward_again = single_step_forward.ring_steps(-1);
assert_eq!(single_step_backward_again, hgs_tile);
}
}
#[test]
fn test_circular_steps_specifics() {
let hgs0 = HGSTile::make(0);
let hgs0_plus_1 = hgs0.ring_steps(1);
assert_eq!(
hgs0_plus_1, hgs0,
"The origin ring should only consist of one tile."
);
let hgs1 = HGSTile::make(1);
let hgs1_plus_1 = hgs1.ring_steps(1);
assert_eq!(hgs1_plus_1.h.value(), 1 + 1);
let hgs2 = HGSTile::make(2);
let hgs2_plus_1 = hgs2.ring_steps(1);
assert_eq!(hgs2_plus_1.h.value(), 2 + 1);
let hgs3 = HGSTile::make(3);
let hgs3_plus_1 = hgs3.ring_steps(1);
assert_eq!(hgs3_plus_1.h.value(), 3 + 1);
let hgs4 = HGSTile::make(4);
let hgs4_plus_1 = hgs4.ring_steps(1);
assert_eq!(hgs4_plus_1.h.value(), 4 + 1);
let hgs5 = HGSTile::make(5);
let hgs5_plus_1 = hgs5.ring_steps(1);
assert_eq!(hgs5_plus_1.h.value(), 5 + 1);
let hgs6 = HGSTile::make(6);
let hgs6_plus_1 = hgs6.ring_steps(1);
assert_eq!(hgs6_plus_1.h.value(), 1);
let hgs7 = HGSTile::make(7);
let hgs7_plus_1 = hgs7.ring_steps(1);
assert_eq!(hgs7_plus_1.h, TileIndex(8));
let hgs7_minus_1 = hgs7.ring_steps(-1);
assert_eq!(hgs7_minus_1.h, TileIndex(18));
let hgs37 = HGSTile::make(37);
let hgs37_plus_1 = hgs37.ring_steps(1);
assert_eq!(hgs37_plus_1.h, TileIndex(38));
let hgs37_minus_1 = hgs37.ring_steps(-1);
assert_eq!(hgs37_minus_1.h, TileIndex(60));
let hgs38 = HGSTile::make(38);
let hgs38_plus_1 = hgs38.ring_steps(1);
assert_eq!(hgs38_plus_1.h, TileIndex(39));
let hgs38_minus_1 = hgs38.ring_steps(-1);
assert_eq!(hgs38_minus_1.h, TileIndex(37));
let hgs13 = HGSTile::make(13);
let hgs13_plus_1 = hgs13.ring_steps(1);
assert_eq!(hgs13_plus_1.h, TileIndex(14));
let hgs13_minus_1 = hgs13.ring_steps(-1);
assert_eq!(hgs13_minus_1.h, TileIndex(12));
let hgs13_plus_2 = hgs13.ring_steps(2);
assert_eq!(hgs13_plus_2.h, TileIndex(15));
}
}