From 5719e278a2ae4a8404dcbd10c22cf5d536cd205d Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Wed, 14 Feb 2024 17:49:05 +0000 Subject: [PATCH] Add utils for hexagonal grild (#50) Reviewed-on: https://git.tipragot.fr/corentin/border-wars/pulls/50 Reviewed-by: Tipragot Co-authored-by: CoCoSol007 Co-committed-by: CoCoSol007 --- Cargo.lock | 74 ++++++++++++++++++++ crates/border-wars/Cargo.toml | 2 + crates/border-wars/src/hex.rs | 126 ++++++++++++++++++++++++++++++++++ crates/border-wars/src/lib.rs | 1 + 4 files changed, 203 insertions(+) create mode 100644 crates/border-wars/src/hex.rs diff --git a/Cargo.lock b/Cargo.lock index 3f17b55..58bf08f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1316,6 +1316,8 @@ version = "0.1.0" dependencies = [ "bevy", "bevy_egui", + "num", + "partial-min-max", ] [[package]] @@ -2993,6 +2995,40 @@ dependencies = [ "winapi", ] +[[package]] +name = "num" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23c6602fda94a57c990fe0df199a035d83576b496aa29f4e634a8ac6004e68a6" +dependencies = [ + "num-traits", +] + [[package]] name = "num-derive" version = "0.3.3" @@ -3004,6 +3040,38 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d869c01cc0c455284163fd0092f1f93835385ccab5a98a0dcc497b2f8bf055a9" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-traits" version = "0.2.18" @@ -3261,6 +3329,12 @@ dependencies = [ "windows-targets 0.48.5", ] +[[package]] +name = "partial-min-max" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6448add382c60bbbc64f9dab41309a12ec530c05191601042f911356ac09758c" + [[package]] name = "paste" version = "1.0.14" diff --git a/crates/border-wars/Cargo.toml b/crates/border-wars/Cargo.toml index 19f39d9..5f2e0a0 100644 --- a/crates/border-wars/Cargo.toml +++ b/crates/border-wars/Cargo.toml @@ -13,3 +13,5 @@ workspace = true [dependencies] bevy = "0.12.1" bevy_egui = "0.24.0" +num = "0.4.1" +partial-min-max = "0.4.0" diff --git a/crates/border-wars/src/hex.rs b/crates/border-wars/src/hex.rs new file mode 100644 index 0000000..83e3776 --- /dev/null +++ b/crates/border-wars/src/hex.rs @@ -0,0 +1,126 @@ +//! All functions related to calculations in a hexagonal grid. + +use std::collections::HashSet; +use std::hash::Hash; +use std::ops::{Add, AddAssign, Sub, SubAssign}; + +use num::{FromPrimitive, Signed}; +use partial_min_max::{max, min}; + +/// Represents a number that can be used in a hexagonal grid. +pub trait HexNumber: Signed + PartialEq + Copy + PartialOrd + FromPrimitive {} + +impl HexNumber for T {} + +/// Represents a position in a hexagonal grid. +/// We use the axial coordinate system explained in this +/// [documentation](https://www.redblobgames.com/grids/hexagons/#coordinates). +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct HexPosition { + /// Q coordinate. + pub q: T, + + /// R coordinate. + pub r: T, +} + +impl HexPosition { + /// Returns the distance between two [HexPosition]s. + /// + /// # How it works + /// + /// In the hexagonal grid, using the + /// [cube coordinate system](https://www.redblobgames.com/grids/hexagons/#coordinates), + /// it's akin to a cube in 3D space. + /// The Manhattan distance between two positions is equal to half of + /// the sum of abs(dx) + abs(dy) + abs(dz). + /// However, in hexagonal grids, z is defined as -q - r. + /// + /// # Example + /// + /// ```no_run + /// use border_wars::hex::HexPosition; + /// + /// let a = HexPosition { q: 0, r: 0 }; + /// let b = HexPosition { q: 1, r: 1 }; + /// + /// assert_eq!(a.distance_to(&b), 2); + /// ``` + pub fn distance_to(&self, other: &Self) -> T { + // Calculate the difference between the q and r coordinates. + let dq = (self.q - other.q).abs(); + let dr = (self.r - other.r).abs(); + let ds = dq + dr; + + // Manhattan distance = (abs(dq) + abs(dr) + abs(ds)) / 2 + (dq + dr + ds) / (T::one() + T::one()) + } +} + +impl HexPosition { + /// Returns all positions within a given `range` from the current + /// `HexPosition`. + /// + /// This function iterates over the possible q and r values within the + /// specified range. + /// Note that the original position is also returned. + /// + /// For more details, refer to: https://www.redblobgames.com/grids/hexagons/#range + /// + /// # Example + /// + /// ``` + /// use border_wars::hex::HexPosition; + /// + /// let position = HexPosition { q: 0, r: 0 }; + /// + /// let positions = position.range(1); + /// + /// assert_eq!(positions.len(), 7); + /// ``` + pub fn range(&self, range: T) -> HashSet { + let mut result_positions = HashSet::new(); + for q in num::range_inclusive(-range, range) { + for r in num::range_inclusive(max(-range, -q - range), min(range, -q + range)) { + result_positions.insert(Self { q, r }); + } + } + result_positions + } +} + +impl Add for HexPosition { + type Output = Self; + + fn add(self, other: Self) -> Self::Output { + Self { + q: self.q + other.q, + r: self.r + other.r, + } + } +} + +impl AddAssign for HexPosition { + fn add_assign(&mut self, other: Self) { + self.q += other.q; + self.r += other.r; + } +} + +impl Sub for HexPosition { + type Output = Self; + + fn sub(self, other: Self) -> Self { + Self { + q: self.q - other.q, + r: self.r - other.r, + } + } +} + +impl SubAssign for HexPosition { + fn sub_assign(&mut self, other: Self) { + self.q -= other.q; + self.r -= other.r; + } +} diff --git a/crates/border-wars/src/lib.rs b/crates/border-wars/src/lib.rs index c85940e..e475a9e 100644 --- a/crates/border-wars/src/lib.rs +++ b/crates/border-wars/src/lib.rs @@ -2,6 +2,7 @@ use bevy::prelude::*; +pub mod hex; pub mod scenes; /// The current scene of the game.