monorepo/coordinates/src/hex.rs

164 lines
4.9 KiB
Rust
Raw Normal View History

2023-01-14 22:19:36 +00:00
/// Ĉ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);
}
}
}
}