diff --git a/Makefile b/Makefile index 58c3415..89d33bf 100644 --- a/Makefile +++ b/Makefile @@ -5,6 +5,12 @@ changeset-dev: changeset-test: cd changeset && make test +coordinates-dev: + cd coordinates && make dev + +coordinates-test: + cd coordinates && make test + emseries-dev: cd emseries && make dev diff --git a/coordinates/Cargo.lock b/coordinates/Cargo.lock new file mode 100644 index 0000000..4aa5567 --- /dev/null +++ b/coordinates/Cargo.lock @@ -0,0 +1,325 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "coordinates" +version = "0.1.0" +dependencies = [ + "proptest", + "thiserror", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro2" +version = "1.0.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0d9cc07f18492d879586c92b485def06bc850da3118075cd45d50e9c95b0e5" +dependencies = [ + "bit-set", + "bitflags", + "byteorder", + "lazy_static", + "num-traits", + "quick-error 2.0.1", + "rand", + "rand_chacha", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-error" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3" + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_xorshift" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d25bf25ec5ae4a3f1b92f929810509a2f53d7dca2f50b794ff57e3face536c8f" +dependencies = [ + "rand_core", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error 1.2.3", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/coordinates/Cargo.toml b/coordinates/Cargo.toml new file mode 100644 index 0000000..a5f3e17 --- /dev/null +++ b/coordinates/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "coordinates" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proptest = "1.0" +thiserror = "1.0" diff --git a/coordinates/Makefile b/coordinates/Makefile new file mode 100644 index 0000000..1bd40ea --- /dev/null +++ b/coordinates/Makefile @@ -0,0 +1,9 @@ + +dev: + cargo watch -x build + +test: + cargo watch -x test + +test-once: + cargo test diff --git a/coordinates/src/hex.rs b/coordinates/src/hex.rs new file mode 100644 index 0000000..3283c21 --- /dev/null +++ b/coordinates/src/hex.rs @@ -0,0 +1,163 @@ +/// Ĉ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 { + 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::>().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 { + let eko = self.clone(); + let mut rezulto: HashSet = HashSet::new(); + let mut vico: Vec = 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 = addr.within(0).collect(); + assert_eq!(lst.len(), 0); + } + + #[test] + fn distance_1_has_six_addresses() { + let hexaddr = HexAddr::origin(); + let lst: Vec = hexaddr.within(1).collect(); + assert_eq!(lst.len(), 6); + } + + #[test] + fn distance_2_has_18_addresses() { + let hexaddr = HexAddr::origin(); + let lst: Vec = hexaddr.within(2).collect(); + assert_eq!(lst.len(), 18); + } + + 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 = HexAddr::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 = HexAddr::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 = 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.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); + } + } + } +} diff --git a/coordinates/src/lib.rs b/coordinates/src/lib.rs new file mode 100644 index 0000000..6e34e58 --- /dev/null +++ b/coordinates/src/lib.rs @@ -0,0 +1,22 @@ +/* +Copyright 2022, Savanni D'Gerinel + +This file is part of Koordinatoj. + +Lumeto is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. + +Lumeto is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with Lumeto. If not, see . +*/ +use thiserror; + +#[derive(Debug, thiserror::Error)] +pub enum Error { + #[error("La enhavita koordinato ne estas valida en ĉi-tiu sistemo")] + InvalidCoordinate, +} + +mod hex; + +pub use hex::HexAddr; diff --git a/flake.lock b/flake.lock index 869d819..fcc38c1 100644 --- a/flake.lock +++ b/flake.lock @@ -17,11 +17,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1672335022, - "narHash": "sha256-417aTRDZpob/UNsA4A0ilBUBQ0H8u+Wx9+OJAAygufQ=", + "lastModified": 1672580127, + "narHash": "sha256-3lW3xZslREhJogoOkjeZtlBtvFMyxHku7I/9IVehhT8=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "88eaf4a29b5b87a5f83fab413caba24cb13633f0", + "rev": "0874168639713f547c05947c76124f78441ea46c", "type": "github" }, "original": { @@ -52,7 +52,7 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "narHash": "sha256-ruR2xo30Vn7kY2hAgg2Z2xrCvNePxck6mgR5a8u+zow=", + "narHash": "sha256-VCSmIYJy/ZzTvEGjdfITmTYfybXBgZpMjyjDndbou+8=", "type": "tarball", "url": "https://github.com/oxalica/rust-overlay/archive/master.tar.gz" },