164 lines
4.9 KiB
Rust
164 lines
4.9 KiB
Rust
|
/// Ĉi-tiu modulo enhavas la elementojn por kub-koordinato.
|
||
|
///
|
||
|
/// This code is based on https://www.redblobgames.com/grids/hexagons/
|
||
|
use crate::Error;
|
||
|
use std::collections::HashSet;
|
||
|
|
||
|
/// An address within the hex coordinate system
|
||
|
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||
|
pub struct HexAddr {
|
||
|
q: i32,
|
||
|
r: i32,
|
||
|
}
|
||
|
|
||
|
impl HexAddr {
|
||
|
/// Create a new axial coordinate address.
|
||
|
pub fn new(q: i32, r: i32) -> Self {
|
||
|
Self { q, r }
|
||
|
}
|
||
|
|
||
|
pub fn origin() -> HexAddr {
|
||
|
HexAddr { q: 0, r: 0 }
|
||
|
}
|
||
|
|
||
|
pub fn q(&self) -> i32 {
|
||
|
self.q
|
||
|
}
|
||
|
|
||
|
pub fn r(&self) -> i32 {
|
||
|
self.r
|
||
|
}
|
||
|
|
||
|
pub fn s(&self) -> i32 {
|
||
|
-self.q - self.r
|
||
|
}
|
||
|
|
||
|
/// Get a list of coordinates adjacent to this one.
|
||
|
pub fn adjacencies(&self) -> impl Iterator<Item = HexAddr> {
|
||
|
vec![
|
||
|
HexAddr::new(self.q + 1, self.r),
|
||
|
HexAddr::new(self.q, self.r + 1),
|
||
|
HexAddr::new(self.q - 1, self.r + 1),
|
||
|
HexAddr::new(self.q - 1, self.r),
|
||
|
HexAddr::new(self.q, self.r - 1),
|
||
|
HexAddr::new(self.q + 1, self.r - 1),
|
||
|
]
|
||
|
.into_iter()
|
||
|
}
|
||
|
|
||
|
/// Test whether a coordinate is adjacent to this one
|
||
|
pub fn is_adjacent(&self, dest: &HexAddr) -> bool {
|
||
|
dest.adjacencies().collect::<Vec<HexAddr>>().contains(&self)
|
||
|
}
|
||
|
|
||
|
/// Measure the distance to a destination
|
||
|
pub fn distance(&self, dest: &HexAddr) -> usize {
|
||
|
(((self.q() - dest.q()).abs() + (self.r() - dest.r()).abs() + (self.s() - dest.s()).abs())
|
||
|
/ 2) as usize
|
||
|
}
|
||
|
|
||
|
/// Get an iteration to all of the coordinates within the specified distance of this one.
|
||
|
pub fn within(&self, distance: usize) -> impl Iterator<Item = HexAddr> {
|
||
|
let eko = self.clone();
|
||
|
let mut rezulto: HashSet<HexAddr> = HashSet::new();
|
||
|
let mut vico: Vec<HexAddr> = Vec::new();
|
||
|
|
||
|
vico.push(eko);
|
||
|
|
||
|
while vico.len() > 0 {
|
||
|
let elem = vico.remove(0);
|
||
|
for adj in elem.adjacencies() {
|
||
|
if self.distance(&adj) <= distance && !rezulto.contains(&adj) {
|
||
|
vico.push(adj.clone());
|
||
|
}
|
||
|
}
|
||
|
rezulto.insert(elem);
|
||
|
}
|
||
|
|
||
|
rezulto.remove(self);
|
||
|
rezulto.into_iter()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[cfg(test)]
|
||
|
mod tests {
|
||
|
use super::*;
|
||
|
use proptest::prelude::*;
|
||
|
use std::collections::HashSet;
|
||
|
|
||
|
#[test]
|
||
|
fn distance_0_is_empty() {
|
||
|
let addr = HexAddr::origin();
|
||
|
let lst: Vec<HexAddr> = addr.within(0).collect();
|
||
|
assert_eq!(lst.len(), 0);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn distance_1_has_six_addresses() {
|
||
|
let hexaddr = HexAddr::origin();
|
||
|
let lst: Vec<HexAddr> = hexaddr.within(1).collect();
|
||
|
assert_eq!(lst.len(), 6);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn distance_2_has_18_addresses() {
|
||
|
let hexaddr = HexAddr::origin();
|
||
|
let lst: Vec<HexAddr> = hexaddr.within(2).collect();
|
||
|
assert_eq!(lst.len(), 18);
|
||
|
}
|
||
|
|
||
|
fn address() -> (std::ops::Range<i32>, std::ops::Range<i32>) {
|
||
|
(-65536_i32..65535, -65536_i32..65535)
|
||
|
}
|
||
|
|
||
|
proptest! {
|
||
|
#[test]
|
||
|
fn produces_adjacent_coordinates((x, y) in address(), idx in 0_usize..6) {
|
||
|
let coord1 = HexAddr::new(x, y);
|
||
|
let lst1: HashSet<HexAddr> = coord1.adjacencies().collect();
|
||
|
assert_eq!(lst1.len(), 6);
|
||
|
|
||
|
let lst1 = lst1.into_iter().collect::<Vec<_>>();
|
||
|
let coord2 = &lst1[idx];
|
||
|
let lst2: Vec<HexAddr> = coord2.adjacencies().collect();
|
||
|
assert!(lst2.contains(&coord1));
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn tests_adjacencies((q, r) in address(), idx in 0_usize..6) {
|
||
|
let coord1 = HexAddr::new(q, r);
|
||
|
let lst1: Vec<HexAddr> = coord1.adjacencies().collect();
|
||
|
assert_eq!(lst1.len(), 6);
|
||
|
|
||
|
let coord2 = &lst1[idx];
|
||
|
assert!(coord2.is_adjacent(&coord1));
|
||
|
assert!(coord1.is_adjacent(&coord2));
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn measures_distance((q1, r1) in address(),
|
||
|
(q2, r2) in address()) {
|
||
|
let hexaddr_1 = HexAddr::new(q1, r1);
|
||
|
let hexaddr_2 = HexAddr::new(q2, r2);
|
||
|
|
||
|
let s1 = -q1 - r1;
|
||
|
let s2 = -q2 - r2;
|
||
|
let expected = (((q1 - q2).abs() + (r1 - r2).abs() + (s1 - s2).abs()) / 2) as usize;
|
||
|
assert_eq!(hexaddr_1.distance(&hexaddr_2), expected);
|
||
|
assert_eq!(hexaddr_2.distance(&hexaddr_1), expected);
|
||
|
}
|
||
|
|
||
|
#[test]
|
||
|
fn calculates_distance((q, r) in address(), distance in 0_usize..6) {
|
||
|
let hexaddr = HexAddr::new(q, r);
|
||
|
let en_distancaj_hexaddr: Vec<HexAddr> = hexaddr.within(distance).collect();
|
||
|
|
||
|
let expected_cnt = ((0..distance+1).map(|v| v * 6).fold(0, |acc, val| acc + val)) as usize;
|
||
|
assert_eq!(en_distancaj_hexaddr.len(), expected_cnt);
|
||
|
for c in en_distancaj_hexaddr {
|
||
|
assert!(c.distance(&hexaddr) <= distance as usize);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|