/// Ĉ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 AxialAddr { q: i32, r: i32, } impl AxialAddr { /// Create a new axial coordinate address. pub fn new(q: i32, r: i32) -> Self { Self { q, r } } pub fn origin() -> AxialAddr { AxialAddr { 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 { vec![ AxialAddr::new(self.q + 1, self.r), AxialAddr::new(self.q, self.r + 1), AxialAddr::new(self.q - 1, self.r + 1), AxialAddr::new(self.q - 1, self.r), AxialAddr::new(self.q, self.r - 1), AxialAddr::new(self.q + 1, self.r - 1), ] .into_iter() } /// Test whether a coordinate is adjacent to this one pub fn is_adjacent(&self, dest: &AxialAddr) -> bool { dest.adjacencies() .collect::>() .contains(&self) } /// Measure the distance to a destination pub fn distance(&self, dest: &AxialAddr) -> usize { (((self.q() - dest.q()).abs() + (self.r() - dest.r()).abs() + (self.s() - dest.s()).abs()) / 2) as usize } /// Get an iteration of all of the coordinates within the specified distance of this one. pub fn addresses(&self, distance: usize) -> impl Iterator { let item = self.clone(); let mut results: HashSet = HashSet::new(); let mut positions: Vec = Vec::new(); positions.push(item); while positions.len() > 0 { let elem = positions.remove(0); for adj in elem.adjacencies() { if self.distance(&adj) <= distance && !results.contains(&adj) { positions.push(adj.clone()); } } results.insert(elem); } results.into_iter() } } #[cfg(test)] mod tests { use super::*; use proptest::prelude::*; use std::collections::HashSet; #[test] fn distance_0_has_the_source() { let addr = AxialAddr::origin(); let lst: Vec = addr.addresses(0).collect(); assert_eq!(lst.len(), 1); assert!(lst.contains(&AxialAddr::origin())); } #[test] fn distance_1_has_seven_addresses() { let hexaddr = AxialAddr::origin(); let lst: Vec = hexaddr.addresses(1).collect(); assert_eq!(lst.len(), 7); } #[test] fn distance_2_has_19_addresses() { let hexaddr = AxialAddr::origin(); let lst: Vec = hexaddr.addresses(2).collect(); assert_eq!(lst.len(), 19); } fn address() -> (std::ops::Range, std::ops::Range) { (-65536_i32..65535, -65536_i32..65535) } proptest! { #[test] fn produces_adjacent_coordinates((x, y) in address(), idx in 0_usize..6) { let coord1 = AxialAddr::new(x, y); let lst1: HashSet = coord1.adjacencies().collect(); assert_eq!(lst1.len(), 6); let lst1 = lst1.into_iter().collect::>(); let coord2 = &lst1[idx]; let lst2: Vec = coord2.adjacencies().collect(); assert!(lst2.contains(&coord1)); } #[test] fn tests_adjacencies((q, r) in address(), idx in 0_usize..6) { let coord1 = AxialAddr::new(q, r); let lst1: Vec = 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 = AxialAddr::new(q1, r1); let hexaddr_2 = AxialAddr::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 = AxialAddr::new(q, r); let en_distancaj_hexaddr: Vec = hexaddr.addresses(distance).collect(); let expected_cnt = ((0..distance+1).map(|v| v * 6).fold(1, |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); } } } }