use core::iter::FusedIterator;
use crate::{Hex, int::SignedInt};
pub struct HexRing<T: SignedInt> {
current: Hex<T>,
steps: T,
direction: u8,
radius: T,
done: bool,
}
impl<T: SignedInt> HexRing<T> {
pub(crate) fn new(center: Hex<T>, radius: T) -> Self {
if radius == T::ZERO {
return Self {
current: center,
steps: T::ZERO,
direction: 0,
radius,
done: false,
};
}
let start = center + Hex::<T>::SW * radius;
Self {
current: start,
steps: T::ZERO,
direction: 0,
radius,
done: false,
}
}
}
impl<T: SignedInt> Iterator for HexRing<T> {
type Item = Hex<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
if self.radius == T::ZERO {
self.done = true;
return Some(self.current);
}
if self.direction >= 6 {
return None;
}
let item = self.current;
self.current += Hex::<T>::DIRECTIONS[self.direction as usize];
self.steps += T::ONE;
if self.steps == self.radius {
self.steps = T::ZERO;
self.direction += 1;
}
Some(item)
}
}
impl<T: SignedInt> FusedIterator for HexRing<T> {}
pub struct HexRange<T: SignedInt> {
center: Hex<T>,
radius: T,
dq: T,
dr: T,
dr_max: T,
done: bool,
}
impl<T: SignedInt> HexRange<T> {
pub(crate) fn new(center: Hex<T>, radius: T) -> Self {
let dq = -radius;
let dr_min = if -radius > -dq - radius {
-radius
} else {
-dq - radius
};
let dr_max = if radius < -dq + radius {
radius
} else {
-dq + radius
};
Self {
center,
radius,
dq,
dr: dr_min,
dr_max,
done: false,
}
}
}
impl<T: SignedInt> Iterator for HexRange<T> {
type Item = Hex<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.done {
return None;
}
if self.dq > self.radius {
return None;
}
let item = self.center + Hex::new(self.dq, self.dr);
if self.dr < self.dr_max {
self.dr += T::ONE;
} else {
self.dq += T::ONE;
if self.dq > self.radius {
self.done = true;
return Some(item);
}
let a = -self.radius;
let b = -self.dq - self.radius;
self.dr = if a > b { a } else { b };
let c = self.radius;
let d = -self.dq + self.radius;
self.dr_max = if c < d { c } else { d };
}
Some(item)
}
}
impl<T: SignedInt> FusedIterator for HexRange<T> {}
pub struct HexLine<T: SignedInt> {
start: Hex<T>,
end: Hex<T>,
distance: T,
step: T,
}
impl<T: SignedInt> HexLine<T> {
pub(crate) fn new(start: Hex<T>, end: Hex<T>) -> Self {
let distance = start.distance(end);
Self {
start,
end,
distance,
step: T::ZERO,
}
}
fn lerp_axis(a: T, b: T, step: T, dist: T) -> T {
let num = a * (dist - step) + b * step;
let two = T::ONE + T::ONE;
let numer = two * num + dist;
let denom = two * dist;
if numer >= T::ZERO {
numer / denom
} else {
(numer - denom + T::ONE) / denom
}
}
}
impl<T: SignedInt> Iterator for HexLine<T> {
type Item = Hex<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.step > self.distance {
return None;
}
let item = if self.distance == T::ZERO {
self.start
} else {
let q = Self::lerp_axis(self.start.q, self.end.q, self.step, self.distance);
let r = Self::lerp_axis(self.start.r, self.end.r, self.step, self.distance);
Hex::new(q, r)
};
self.step += T::ONE;
Some(item)
}
}
impl<T: SignedInt> FusedIterator for HexLine<T> {}
#[cfg(test)]
mod tests {
use crate::{Hex, hex};
#[test]
fn ring_radius_0_is_center() {
let center = hex!(1, 2);
let v: alloc::vec::Vec<_> = center.ring(0).collect();
assert_eq!(v, [center]);
}
#[test]
fn ring_counts() {
let c = Hex::ORIGIN;
assert_eq!(c.ring(0).count(), 1);
assert_eq!(c.ring(1).count(), 6);
assert_eq!(c.ring(2).count(), 12);
assert_eq!(c.ring(3).count(), 18);
}
#[test]
fn ring_all_at_correct_distance() {
let center = hex!(2, -1);
for r in 0_i32..=4 {
for h in center.ring(r) {
assert_eq!(
center.distance(h),
r,
"ring({r}) hex {h} has wrong distance"
);
}
}
}
#[test]
fn ring_is_fused() {
let mut it = Hex::ORIGIN.ring(1);
for _ in 0..6 {
it.next();
}
assert!(it.next().is_none());
assert!(it.next().is_none());
}
#[test]
fn range_counts() {
let c = Hex::ORIGIN;
assert_eq!(c.range(0).count(), 1);
assert_eq!(c.range(1).count(), 7);
assert_eq!(c.range(2).count(), 19);
assert_eq!(c.range(3).count(), 37);
}
#[test]
fn range_all_within_radius() {
let center = hex!(-1, 2);
for r in 0_i32..=3 {
for h in center.range(r) {
assert!(center.distance(h) <= r, "range({r}) hex {h} is too far");
}
}
}
#[test]
fn line_endpoints() {
let a = hex!(0, 0);
let b = hex!(3, -1);
let line: alloc::vec::Vec<_> = a.line_to(b).collect();
assert_eq!(*line.first().unwrap(), a);
assert_eq!(*line.last().unwrap(), b);
}
#[test]
fn line_count_equals_distance_plus_one() {
let a = hex!(0, 0);
let b = hex!(4, -2);
#[allow(clippy::cast_sign_loss)]
let expected = a.distance(b) as usize + 1;
assert_eq!(a.line_to(b).count(), expected);
}
#[test]
fn line_zero_distance() {
let h = hex!(1, 1);
let line: alloc::vec::Vec<_> = h.line_to(h).collect();
assert_eq!(line, [h]);
}
#[test]
fn line_consecutive_steps_are_neighbors() {
let a = hex!(0, 0);
let b = hex!(4, -2);
let line: alloc::vec::Vec<_> = a.line_to(b).collect();
for w in line.windows(2) {
assert_eq!(
w[0].distance(w[1]),
1,
"consecutive line hexes {:?} and {:?} are not neighbors",
w[0],
w[1]
);
}
}
extern crate alloc;
}