From d782bcccdb62525583ac15e6676cf37eae74f7a6 Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Fri, 16 Feb 2024 00:58:10 +0100 Subject: [PATCH 01/10] save --- Cargo.lock | 1 + crates/border-wars/Cargo.toml | 1 + crates/border-wars/src/map/hex.rs | 375 ++++++++++++++++--------- crates/border-wars/src/map/hex_save.rs | 155 ++++++++++ 4 files changed, 400 insertions(+), 132 deletions(-) create mode 100644 crates/border-wars/src/map/hex_save.rs diff --git a/Cargo.lock b/Cargo.lock index 58bf08f..bb750c8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1318,6 +1318,7 @@ dependencies = [ "bevy_egui", "num", "partial-min-max", + "paste", ] [[package]] diff --git a/crates/border-wars/Cargo.toml b/crates/border-wars/Cargo.toml index 5f2e0a0..70e1543 100644 --- a/crates/border-wars/Cargo.toml +++ b/crates/border-wars/Cargo.toml @@ -15,3 +15,4 @@ bevy = "0.12.1" bevy_egui = "0.24.0" num = "0.4.1" partial-min-max = "0.4.0" +paste = "1.0.14" diff --git a/crates/border-wars/src/map/hex.rs b/crates/border-wars/src/map/hex.rs index 74db219..8cb56c8 100644 --- a/crates/border-wars/src/map/hex.rs +++ b/crates/border-wars/src/map/hex.rs @@ -1,155 +1,266 @@ //! 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::cast::AsPrimitive; -use num::{FromPrimitive, Signed}; -use partial_min_max::{max, min}; +use std::ops::{ + Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, +}; -/// Represents a number that can be used in a hexagonal grid. -pub trait HexNumber: Signed + PartialEq + Copy + PartialOrd + FromPrimitive {} +use num::{cast::AsPrimitive, FromPrimitive}; +use paste::paste; -impl HexNumber for T {} +/// Represents a number that can be used in calculations for hexagonal grids. +pub trait Number: + Copy + + PartialEq + + PartialOrd + + Add + + Sub + + Mul + + Div + + Rem + + Neg + + AddAssign + + SubAssign + + MulAssign + + DivAssign + + RemAssign + + std::fmt::Debug +{ + /// The number -2. + const MINUS_TWO: Self; + + /// The number -1. + const MINUS_ONE: Self; + + /// The number 0. + const ZERO: Self; + + /// The number 1. + const ONE: Self; + + /// The number 2. + const TWO: Self; + + /// Returns the maximum of `self` and `other`. + fn max(self, other: Self) -> Self { + if self > other { self } else { other } + } + + /// Returns the minimum of `self` and `other`. + fn min(self, other: Self) -> Self { + if self < other { self } else { other } + } + + /// Returns the absolute value of `self`. + fn abs(self) -> Self { + if self < Self::ZERO { -self } else { self } + } +} + +macro_rules! number_impl { + ($($t:ty,)*) => {paste!{$( + impl Number for $t { + const MINUS_ONE: Self = - [< 1 $t >]; + const MINUS_TWO: Self = - [< 2 $t >]; + const ZERO: Self = [< 0 $t >]; + const ONE: Self = [< 1 $t >]; + const TWO: Self = [< 2 $t >]; + } + )*}}; +} + +number_impl! { + i8, i16, i32, i64, i128, isize, + f32, f64, +} /// 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, +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct HexPosition(pub T, pub T); - /// R coordinate. - pub r: T, +/// All possible directions in a hexagonal grid. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum HexDirection { + /// The direction right. + Right, + + /// The direction up-right. + UpRight, + + /// The direction up-left. + UpLeft, + + /// The direction left. + Left, + + /// The direction down-left. + DownLeft, + + /// The direction down-right. + DownRight, } -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::map::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()) - } - - /// Converts the current [HexPosition] into a pixel coordinate. - /// Input: The size of the hexagon in pixels (witdh, height). - /// - /// If you want to learn more about pixel coordinates conversion, - /// you can check the - /// [documentation](https://www.redblobgames.com/grids/hexagons/#hex-to-pixel). - /// - /// # Example - /// - /// ```no_run - /// use border_wars::map::hex::HexPosition; - /// - /// let position = HexPosition { q: 1, r: 0 }; - /// assert_eq!( - /// position.to_pixel_coordinates((1.0, 1.0)), - /// (3f32.sqrt(), 0.0) - /// ); - /// ``` - pub fn to_pixel_coordinates(&self, size: (f32, f32)) -> (f32, f32) { - ( - size.0 - * 3f32 - .sqrt() - .mul_add(self.q.as_(), 3f32.sqrt() / 2.0 * (self.r.as_())), - size.1 * (3.0 / 2.0 * self.r.as_()), - ) +impl HexDirection { + /// Returns the vector of the direction. + pub fn to_vector(self) -> HexPosition { + match self { + HexDirection::Right => HexPosition(T::ONE, T::ZERO), + HexDirection::UpRight => HexPosition(T::ONE, T::MINUS_ONE), + HexDirection::UpLeft => HexPosition(T::ZERO, T::MINUS_ONE), + HexDirection::Left => HexPosition(T::MINUS_ONE, T::ZERO), + HexDirection::DownLeft => HexPosition(T::MINUS_ONE, T::ONE), + HexDirection::DownRight => HexPosition(T::ZERO, 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::map::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 }); +/// A hexagonal ring iterator. +pub struct HexRing { + current: HexPosition, + direction: HexDirection, + radius: usize, + index: usize, +} + +impl Iterator for HexRing { + type Item = HexPosition; + + fn next(&mut self) -> Option { + if self.index >= self.radius { + self.direction = match self.direction { + HexDirection::Right => HexDirection::UpRight, + HexDirection::UpRight => HexDirection::UpLeft, + HexDirection::UpLeft => HexDirection::Left, + HexDirection::Left => HexDirection::DownLeft, + HexDirection::DownLeft => HexDirection::DownRight, + HexDirection::DownRight => return None, + }; + self.index = 0; + } + let result = self.current; + self.current += self.direction.to_vector(); + self.index += 1; + Some(result) + } + + fn size_hint(&self) -> (usize, Option) { + let remaining = match self.direction { + HexDirection::Right => self.radius * 6, + HexDirection::UpRight => self.radius * 5, + HexDirection::UpLeft => self.radius * 4, + HexDirection::Left => self.radius * 3, + HexDirection::DownLeft => self.radius * 2, + HexDirection::DownRight => self.radius, + } - self.index; + (remaining, Some(remaining)) + } +} + +/// A hexagonal spiral iterator. +pub struct HexSpiral { + origin: HexPosition, + current: HexRing, + radius: usize, + index: usize, +} + +impl+ FromPrimitive> Iterator for HexSpiral { + type Item = HexPosition; + + fn next(&mut self) -> Option { + if self.index == 0 { + self.index += 1; + return Some(self.origin); + } + if self.index > self.radius { + return None; + } + let mut result = self.current.next(); + if result.is_none() && self.index < self.radius { + self.index += 1; + self.current = self.origin.ring(T::from_usize(self.index).unwrap()); + result = self.current.next(); + } + result + } + +} + + +impl+ FromPrimitive> HexPosition { + /// Creates a new hexagonal position. + pub fn new(x: T, y: T) -> Self { + Self(x, y) + } + + /// Returns the distance between two hexagonal positions. + pub fn distance(self, other: Self) -> T { + let HexPosition(x, y) = self - other; + x.abs() + y.abs() + (x + y).abs() / T::TWO + } + + /// Returns the hexagonal ring of the given radius. + pub fn ring(self, radius: T) -> HexRing { + let usize_radius = radius.as_(); + HexRing { + current: self + HexDirection::DownLeft.to_vector() * radius, + direction: HexDirection::Right, + radius: usize_radius, + index: 0, + } + } + + /// Returns the hexagonal spiral of the given radius. + pub fn spiral(self, radius: T) -> HexSpiral { + let usize_radius = radius.as_(); + HexSpiral { + origin: self, + current: self.ring(T::ONE), + radius: usize_radius, + index: 0, + } + } +} + +macro_rules! impl_ops { + ($(($t:ty, $n:ident),)*) => {paste!{$( + impl $t for HexPosition { + type Output = Self; + + fn $n(self, rhs: Self) -> Self { + Self(self.0.$n(rhs.0), self.1.$n(rhs.1)) } } - result_positions - } -} -impl Add for HexPosition { - type Output = Self; + impl $t for HexPosition { + type Output = Self; - fn add(self, other: Self) -> Self::Output { - Self { - q: self.q + other.q, - r: self.r + other.r, + fn $n(self, rhs: T) -> Self { + Self(self.0.$n(rhs), self.1.$n(rhs)) + } } - } -} -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 [< $t Assign >] for HexPosition { + fn [< $n _assign >](&mut self, rhs: Self) { + self.0.[< $n _assign >](rhs.0) ; + self.1.[< $n _assign >](rhs.1) ; + } } - } + + impl [< $t Assign >] for HexPosition { + fn [< $n _assign >](&mut self, rhs: T) { + self.0.[< $n _assign >](rhs); + self.1.[< $n _assign >](rhs); + } + } + )*}}; } -impl SubAssign for HexPosition { - fn sub_assign(&mut self, other: Self) { - self.q -= other.q; - self.r -= other.r; - } + +impl_ops! { + (Add, add), + (Sub, sub), + (Mul, mul), + (Div, div), + (Rem, rem), } diff --git a/crates/border-wars/src/map/hex_save.rs b/crates/border-wars/src/map/hex_save.rs new file mode 100644 index 0000000..74db219 --- /dev/null +++ b/crates/border-wars/src/map/hex_save.rs @@ -0,0 +1,155 @@ +//! 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::cast::AsPrimitive; +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::map::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()) + } + + /// Converts the current [HexPosition] into a pixel coordinate. + /// Input: The size of the hexagon in pixels (witdh, height). + /// + /// If you want to learn more about pixel coordinates conversion, + /// you can check the + /// [documentation](https://www.redblobgames.com/grids/hexagons/#hex-to-pixel). + /// + /// # Example + /// + /// ```no_run + /// use border_wars::map::hex::HexPosition; + /// + /// let position = HexPosition { q: 1, r: 0 }; + /// assert_eq!( + /// position.to_pixel_coordinates((1.0, 1.0)), + /// (3f32.sqrt(), 0.0) + /// ); + /// ``` + pub fn to_pixel_coordinates(&self, size: (f32, f32)) -> (f32, f32) { + ( + size.0 + * 3f32 + .sqrt() + .mul_add(self.q.as_(), 3f32.sqrt() / 2.0 * (self.r.as_())), + size.1 * (3.0 / 2.0 * self.r.as_()), + ) + } +} + +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::map::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; + } +} -- 2.43.4 From 008a8f73e6c67b1fecf78e7421c5c20ae4ee0e0a Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Fri, 16 Feb 2024 00:58:24 +0100 Subject: [PATCH 02/10] fix fmt --- crates/border-wars/src/map/hex.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/border-wars/src/map/hex.rs b/crates/border-wars/src/map/hex.rs index 8cb56c8..93616f2 100644 --- a/crates/border-wars/src/map/hex.rs +++ b/crates/border-wars/src/map/hex.rs @@ -1,11 +1,11 @@ //! All functions related to calculations in a hexagonal grid. - use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -use num::{cast::AsPrimitive, FromPrimitive}; +use num::cast::AsPrimitive; +use num::FromPrimitive; use paste::paste; /// Represents a number that can be used in calculations for hexagonal grids. @@ -164,7 +164,7 @@ pub struct HexSpiral { index: usize, } -impl+ FromPrimitive> Iterator for HexSpiral { +impl + FromPrimitive> Iterator for HexSpiral { type Item = HexPosition; fn next(&mut self) -> Option { @@ -183,11 +183,9 @@ impl+ FromPrimitive> Iterator for HexSpiral { } result } - } - -impl+ FromPrimitive> HexPosition { +impl + FromPrimitive> HexPosition { /// Creates a new hexagonal position. pub fn new(x: T, y: T) -> Self { Self(x, y) @@ -256,7 +254,6 @@ macro_rules! impl_ops { )*}}; } - impl_ops! { (Add, add), (Sub, sub), -- 2.43.4 From b7f8dfb064f85040768a17f5d892de0d239a76a9 Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Fri, 16 Feb 2024 01:06:40 +0100 Subject: [PATCH 03/10] save --- crates/border-wars/src/map/hex.rs | 32 +++++++++++++++---------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/crates/border-wars/src/map/hex.rs b/crates/border-wars/src/map/hex.rs index 93616f2..79a1b65 100644 --- a/crates/border-wars/src/map/hex.rs +++ b/crates/border-wars/src/map/hex.rs @@ -118,8 +118,8 @@ impl HexDirection { pub struct HexRing { current: HexPosition, direction: HexDirection, - radius: usize, - index: usize, + radius: T, + index: T, } impl Iterator for HexRing { @@ -160,16 +160,16 @@ impl Iterator for HexRing { pub struct HexSpiral { origin: HexPosition, current: HexRing, - radius: usize, - index: usize, + radius: T, + index: T, } -impl + FromPrimitive> Iterator for HexSpiral { +impl Iterator for HexSpiral { type Item = HexPosition; fn next(&mut self) -> Option { - if self.index == 0 { - self.index += 1; + if self.index == T::TWO { + self.index += T::ONE; return Some(self.origin); } if self.index > self.radius { @@ -177,15 +177,15 @@ impl + FromPrimitive> Iterator for HexSpiral { } let mut result = self.current.next(); if result.is_none() && self.index < self.radius { - self.index += 1; - self.current = self.origin.ring(T::from_usize(self.index).unwrap()); + self.index += T::ONE; + self.current = self.origin.ring(self.index); result = self.current.next(); } result } } -impl + FromPrimitive> HexPosition { +impl HexPosition { /// Creates a new hexagonal position. pub fn new(x: T, y: T) -> Self { Self(x, y) @@ -193,29 +193,27 @@ impl + FromPrimitive> HexPosition { /// Returns the distance between two hexagonal positions. pub fn distance(self, other: Self) -> T { - let HexPosition(x, y) = self - other; + let Self(x, y) = self - other; x.abs() + y.abs() + (x + y).abs() / T::TWO } /// Returns the hexagonal ring of the given radius. pub fn ring(self, radius: T) -> HexRing { - let usize_radius = radius.as_(); HexRing { current: self + HexDirection::DownLeft.to_vector() * radius, direction: HexDirection::Right, - radius: usize_radius, - index: 0, + radius, + index: T::ZERO, } } /// Returns the hexagonal spiral of the given radius. pub fn spiral(self, radius: T) -> HexSpiral { - let usize_radius = radius.as_(); HexSpiral { origin: self, current: self.ring(T::ONE), - radius: usize_radius, - index: 0, + radius, + index: T::ZERO, } } } -- 2.43.4 From 3e6dfd4eca26254d1c2c7c52d0ae28fe80d8ab02 Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Fri, 16 Feb 2024 01:29:35 +0100 Subject: [PATCH 04/10] asve --- crates/border-wars/src/map/hex.rs | 41 +++++++++++++++++++++---------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/crates/border-wars/src/map/hex.rs b/crates/border-wars/src/map/hex.rs index 79a1b65..4bb867c 100644 --- a/crates/border-wars/src/map/hex.rs +++ b/crates/border-wars/src/map/hex.rs @@ -55,6 +55,11 @@ pub trait Number: fn abs(self) -> Self { if self < Self::ZERO { -self } else { self } } + + /// Converts `self` to an `usize`. + fn to_usize(self) -> usize; + + fn from_usize(value: usize) -> Self; } macro_rules! number_impl { @@ -65,7 +70,17 @@ macro_rules! number_impl { const ZERO: Self = [< 0 $t >]; const ONE: Self = [< 1 $t >]; const TWO: Self = [< 2 $t >]; + + fn to_usize(self) -> usize { + self as usize + } + + fn from_usize(value: usize) -> Self { + value as $t + } + } + )*}}; } @@ -118,8 +133,8 @@ impl HexDirection { pub struct HexRing { current: HexPosition, direction: HexDirection, - radius: T, - index: T, + radius: usize, + index: usize, } impl Iterator for HexRing { @@ -160,16 +175,16 @@ impl Iterator for HexRing { pub struct HexSpiral { origin: HexPosition, current: HexRing, - radius: T, - index: T, + radius: usize, + index: usize, } impl Iterator for HexSpiral { type Item = HexPosition; fn next(&mut self) -> Option { - if self.index == T::TWO { - self.index += T::ONE; + if self.index == 0 { + self.index += 1; return Some(self.origin); } if self.index > self.radius { @@ -177,7 +192,7 @@ impl Iterator for HexSpiral { } let mut result = self.current.next(); if result.is_none() && self.index < self.radius { - self.index += T::ONE; + self.index += 1; self.current = self.origin.ring(self.index); result = self.current.next(); } @@ -198,22 +213,22 @@ impl HexPosition { } /// Returns the hexagonal ring of the given radius. - pub fn ring(self, radius: T) -> HexRing { + pub fn ring(self, radius: usize) -> HexRing { HexRing { - current: self + HexDirection::DownLeft.to_vector() * radius, + current: self + HexDirection::DownLeft.to_vector() * radius.from_usize(), direction: HexDirection::Right, radius, - index: T::ZERO, + index: 0, } } /// Returns the hexagonal spiral of the given radius. - pub fn spiral(self, radius: T) -> HexSpiral { + pub fn spiral(self, radius: usize) -> HexSpiral { HexSpiral { origin: self, - current: self.ring(T::ONE), + current: self.ring(1), radius, - index: T::ZERO, + index: 0, } } } -- 2.43.4 From 6fb4996b8cbe3373d27e84a9033f9a853cb557eb Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Fri, 16 Feb 2024 01:41:31 +0100 Subject: [PATCH 05/10] add doc --- crates/border-wars/src/map/hex.rs | 37 ++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/crates/border-wars/src/map/hex.rs b/crates/border-wars/src/map/hex.rs index 4bb867c..6f24a0e 100644 --- a/crates/border-wars/src/map/hex.rs +++ b/crates/border-wars/src/map/hex.rs @@ -4,8 +4,6 @@ use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, }; -use num::cast::AsPrimitive; -use num::FromPrimitive; use paste::paste; /// Represents a number that can be used in calculations for hexagonal grids. @@ -59,9 +57,11 @@ pub trait Number: /// Converts `self` to an `usize`. fn to_usize(self) -> usize; + /// Converts an `usize` to `Self`. fn from_usize(value: usize) -> Self; } +/// Implements the `Number` trait for the given types. macro_rules! number_impl { ($($t:ty,)*) => {paste!{$( impl Number for $t { @@ -117,23 +117,30 @@ pub enum HexDirection { impl HexDirection { /// Returns the vector of the direction. - pub fn to_vector(self) -> HexPosition { + pub const fn to_vector(self) -> HexPosition { match self { - HexDirection::Right => HexPosition(T::ONE, T::ZERO), - HexDirection::UpRight => HexPosition(T::ONE, T::MINUS_ONE), - HexDirection::UpLeft => HexPosition(T::ZERO, T::MINUS_ONE), - HexDirection::Left => HexPosition(T::MINUS_ONE, T::ZERO), - HexDirection::DownLeft => HexPosition(T::MINUS_ONE, T::ONE), - HexDirection::DownRight => HexPosition(T::ZERO, T::ONE), + Self::Right => HexPosition(T::ONE, T::ZERO), + Self::UpRight => HexPosition(T::ONE, T::MINUS_ONE), + Self::UpLeft => HexPosition(T::ZERO, T::MINUS_ONE), + Self::Left => HexPosition(T::MINUS_ONE, T::ZERO), + Self::DownLeft => HexPosition(T::MINUS_ONE, T::ONE), + Self::DownRight => HexPosition(T::ZERO, T::ONE), } } } /// A hexagonal ring iterator. pub struct HexRing { + /// The current position in the ring. current: HexPosition, + + /// The direction of the current position to the next in the ring. direction: HexDirection, + + /// The radius of the ring. radius: usize, + + /// The index of the current position in the ring. index: usize, } @@ -173,9 +180,16 @@ impl Iterator for HexRing { /// A hexagonal spiral iterator. pub struct HexSpiral { + /// The origin of the spiral. origin: HexPosition, + + /// The current ring of the spiral. current: HexRing, + + /// The radius of the spiral. radius: usize, + + /// The index of the current ring in the spiral. index: usize, } @@ -202,7 +216,7 @@ impl Iterator for HexSpiral { impl HexPosition { /// Creates a new hexagonal position. - pub fn new(x: T, y: T) -> Self { + pub const fn new(x: T, y: T) -> Self { Self(x, y) } @@ -215,7 +229,7 @@ impl HexPosition { /// Returns the hexagonal ring of the given radius. pub fn ring(self, radius: usize) -> HexRing { HexRing { - current: self + HexDirection::DownLeft.to_vector() * radius.from_usize(), + current: self + HexDirection::DownLeft.to_vector() * T::from_usize(radius), direction: HexDirection::Right, radius, index: 0, @@ -233,6 +247,7 @@ impl HexPosition { } } +/// Implementation of the arithmetic operators for hexagonal positions. macro_rules! impl_ops { ($(($t:ty, $n:ident),)*) => {paste!{$( impl $t for HexPosition { -- 2.43.4 From 7c7993821212015ef80466840acbf495041db629 Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Fri, 16 Feb 2024 01:52:18 +0100 Subject: [PATCH 06/10] WIP save --- crates/border-wars/src/map/hex.rs | 39 +++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/crates/border-wars/src/map/hex.rs b/crates/border-wars/src/map/hex.rs index 6f24a0e..55647f2 100644 --- a/crates/border-wars/src/map/hex.rs +++ b/crates/border-wars/src/map/hex.rs @@ -1,8 +1,8 @@ //! All functions related to calculations in a hexagonal grid. -use std::ops::{ +use std::{collections::HashSet, ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, -}; +}}; use paste::paste; @@ -59,6 +59,12 @@ pub trait Number: /// Converts an `usize` to `Self`. fn from_usize(value: usize) -> Self; + + /// Converts `self` to an `f32`. + fn to_f32(self) -> f32; + + /// Converts an `f32` to `Self`. + fn from_f32(value: f32) -> Self; } /// Implements the `Number` trait for the given types. @@ -79,6 +85,14 @@ macro_rules! number_impl { value as $t } + fn to_f32(self) -> f32 { + self as f32 + } + + fn from_f32(value: f32) -> Self { + value as $t + } + } )*}}; @@ -220,12 +234,33 @@ impl HexPosition { Self(x, y) } + /// Returns the pixel coordinates of the hexagonal position. + pub fn to_pixel_coordinates(&self, size: (f32, f32)) -> (f32, f32) { + ( + size.0 + * 3f32 + .sqrt() + .mul_add(T::to_f32(self.0), 3f32.sqrt() / 2.0 * T::to_f32(self.0)), + size.1 * (3.0 / 2.0 * T::to_f32(self.1)), + ) + } + /// Returns the distance between two hexagonal positions. pub fn distance(self, other: Self) -> T { let Self(x, y) = self - other; x.abs() + y.abs() + (x + y).abs() / T::TWO } + 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((-range, -q - range), min(range, -q + range)) { + result_positions.insert(Self { q, r }); + } + } + result_positions + } + /// Returns the hexagonal ring of the given radius. pub fn ring(self, radius: usize) -> HexRing { HexRing { -- 2.43.4 From 85e6093f37e532af3080968f5b33e2aac2de0e3d Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Fri, 16 Feb 2024 10:08:20 +0100 Subject: [PATCH 07/10] add doc --- crates/border-wars/src/map/hex.rs | 139 ++++++++++++++++++++++++++---- 1 file changed, 124 insertions(+), 15 deletions(-) diff --git a/crates/border-wars/src/map/hex.rs b/crates/border-wars/src/map/hex.rs index 55647f2..15d9ce4 100644 --- a/crates/border-wars/src/map/hex.rs +++ b/crates/border-wars/src/map/hex.rs @@ -1,8 +1,8 @@ //! All functions related to calculations in a hexagonal grid. -use std::{collections::HashSet, ops::{ +use std::ops::{ Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, RemAssign, Sub, SubAssign, -}}; +}; use paste::paste; @@ -65,6 +65,12 @@ pub trait Number: /// Converts an `f32` to `Self`. fn from_f32(value: f32) -> Self; + + /// Converts `self` to an `isize`. + fn to_isize(self) -> isize; + + /// Converts an `isize` to `Self`. + fn from_isize(value: isize) -> Self; } /// Implements the `Number` trait for the given types. @@ -93,6 +99,14 @@ macro_rules! number_impl { value as $t } + fn to_isize(self) -> isize { + self as isize + } + + fn from_isize(value: isize) -> Self { + value as $t + } + } )*}}; @@ -104,6 +118,8 @@ number_impl! { } /// 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(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct HexPosition(pub T, pub T); @@ -130,7 +146,16 @@ pub enum HexDirection { } impl HexDirection { - /// Returns the vector of the direction. + /// Returns the vector ([HexPosition]) of the direction. + /// + /// # Example + /// + /// ```no_run + /// use border_wars::map::hex::{HexDirection, HexPosition}; + /// + /// let direction = HexDirection::Right; + /// assert_eq!(direction.to_vector(), HexPosition(1, 0)); + /// ``` pub const fn to_vector(self) -> HexPosition { match self { Self::Right => HexPosition(T::ONE, T::ZERO), @@ -211,6 +236,7 @@ impl Iterator for HexSpiral { type Item = HexPosition; fn next(&mut self) -> Option { + // The origin of the spiral. if self.index == 0 { self.index += 1; return Some(self.origin); @@ -229,12 +255,24 @@ impl Iterator for HexSpiral { } impl HexPosition { - /// Creates a new hexagonal position. - pub const fn new(x: T, y: T) -> Self { - Self(x, y) - } - - /// Returns the pixel coordinates of the hexagonal position. + /// Converts the current [HexPosition] into a pixel coordinate. + /// Input: The size of the hexagon in pixels (witdh, height). + /// + /// If you want to learn more about pixel coordinates conversion, + /// you can check the + /// [documentation](https://www.redblobgames.com/grids/hexagons/#hex-to-pixel). + /// + /// # Example + /// + /// ```no_run + /// use border_wars::map::hex::HexPosition; + /// + /// let position = HexPosition(1, 0); + /// assert_eq!( + /// position.to_pixel_coordinates((1.0, 1.0)), + /// (3f32.sqrt(), 0.0) + /// ); + /// ``` pub fn to_pixel_coordinates(&self, size: (f32, f32)) -> (f32, f32) { ( size.0 @@ -245,23 +283,79 @@ impl HexPosition { ) } - /// Returns the distance between two hexagonal positions. + /// 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::map::hex::HexPosition; + /// + /// let a = HexPosition(0, 0); + /// let b = HexPosition(1, 1); + /// + /// assert_eq!(a.distance(b), 2); + /// ``` pub fn distance(self, other: Self) -> T { let Self(x, y) = self - other; x.abs() + y.abs() + (x + y).abs() / T::TWO } - 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((-range, -q - range), min(range, -q + range)) { - result_positions.insert(Self { q, r }); + /// 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 + /// + /// ```no_run + /// use border_wars::map::hex::HexPosition; + /// + /// let position = HexPosition(0, 0); + /// let range = 1; + /// + /// let positions = position.range(range); + /// + /// assert_eq!(positions.len(), 7); + /// ``` + pub fn range(&self, range: isize) -> Vec { + let mut result_positions = Vec::new(); + for q in -range..=range { + for r in Number::min(-range, -q - range)..=Number::min(range, -q + range) { + result_positions.push(Self(T::from_isize(q), T::from_isize(r))); } } result_positions } /// Returns the hexagonal ring of the given radius. + /// If you want to learn more about hexagonal grids, check the + /// [documentation](https://www.redblobgames.com/grids/hexagons/#rings) + /// + /// # Example + /// + /// ```no_run + /// use border_wars::map::hex::HexPosition; + /// + /// let position = HexPosition(0, 0); + /// let radius = 1; + /// + /// for ring_position in position.ring(radius) { + /// println!("{:?}", ring_position); + /// } + /// ``` pub fn ring(self, radius: usize) -> HexRing { HexRing { current: self + HexDirection::DownLeft.to_vector() * T::from_usize(radius), @@ -272,6 +366,21 @@ impl HexPosition { } /// Returns the hexagonal spiral of the given radius. + /// If you want to learn more about hexagonal grids, check the + /// [documentation](https://www.redblobgames.com/grids/hexagons/#rings-spiral) + /// + /// # Example + /// + /// ```no_run + /// use border_wars::map::hex::HexPosition; + /// + /// let position = HexPosition(0, 0); + /// let radius = 1; + /// + /// for spiral_position in position.spiral(radius) { + /// println!("{:?}", spiral_position); + /// } + /// ``` pub fn spiral(self, radius: usize) -> HexSpiral { HexSpiral { origin: self, -- 2.43.4 From 8d7010c0c8760779da6f3b65d43f8d63546394d2 Mon Sep 17 00:00:00 2001 From: Corentin Date: Fri, 16 Feb 2024 09:10:04 +0000 Subject: [PATCH 08/10] Delete crates/border-wars/src/map/hex_save.rs --- crates/border-wars/src/map/hex_save.rs | 155 ------------------------- 1 file changed, 155 deletions(-) delete mode 100644 crates/border-wars/src/map/hex_save.rs diff --git a/crates/border-wars/src/map/hex_save.rs b/crates/border-wars/src/map/hex_save.rs deleted file mode 100644 index 74db219..0000000 --- a/crates/border-wars/src/map/hex_save.rs +++ /dev/null @@ -1,155 +0,0 @@ -//! 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::cast::AsPrimitive; -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::map::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()) - } - - /// Converts the current [HexPosition] into a pixel coordinate. - /// Input: The size of the hexagon in pixels (witdh, height). - /// - /// If you want to learn more about pixel coordinates conversion, - /// you can check the - /// [documentation](https://www.redblobgames.com/grids/hexagons/#hex-to-pixel). - /// - /// # Example - /// - /// ```no_run - /// use border_wars::map::hex::HexPosition; - /// - /// let position = HexPosition { q: 1, r: 0 }; - /// assert_eq!( - /// position.to_pixel_coordinates((1.0, 1.0)), - /// (3f32.sqrt(), 0.0) - /// ); - /// ``` - pub fn to_pixel_coordinates(&self, size: (f32, f32)) -> (f32, f32) { - ( - size.0 - * 3f32 - .sqrt() - .mul_add(self.q.as_(), 3f32.sqrt() / 2.0 * (self.r.as_())), - size.1 * (3.0 / 2.0 * self.r.as_()), - ) - } -} - -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::map::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; - } -} -- 2.43.4 From 37bb4ac5645b82ab1401dc72c79eb8adf9041fcf Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Fri, 16 Feb 2024 22:01:11 +0100 Subject: [PATCH 09/10] remove range --- crates/border-wars/src/map/hex.rs | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/crates/border-wars/src/map/hex.rs b/crates/border-wars/src/map/hex.rs index 15d9ce4..ea2a8ed 100644 --- a/crates/border-wars/src/map/hex.rs +++ b/crates/border-wars/src/map/hex.rs @@ -309,37 +309,6 @@ impl HexPosition { x.abs() + y.abs() + (x + y).abs() / T::TWO } - /// 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 - /// - /// ```no_run - /// use border_wars::map::hex::HexPosition; - /// - /// let position = HexPosition(0, 0); - /// let range = 1; - /// - /// let positions = position.range(range); - /// - /// assert_eq!(positions.len(), 7); - /// ``` - pub fn range(&self, range: isize) -> Vec { - let mut result_positions = Vec::new(); - for q in -range..=range { - for r in Number::min(-range, -q - range)..=Number::min(range, -q + range) { - result_positions.push(Self(T::from_isize(q), T::from_isize(r))); - } - } - result_positions - } - /// Returns the hexagonal ring of the given radius. /// If you want to learn more about hexagonal grids, check the /// [documentation](https://www.redblobgames.com/grids/hexagons/#rings) -- 2.43.4 From db8f10951b33c7bfae2e25e48c5b91b5b2efddbb Mon Sep 17 00:00:00 2001 From: CoCoSol007 Date: Fri, 16 Feb 2024 22:10:57 +0100 Subject: [PATCH 10/10] save --- crates/border-wars/src/map/hex.rs | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/crates/border-wars/src/map/hex.rs b/crates/border-wars/src/map/hex.rs index ea2a8ed..5410eff 100644 --- a/crates/border-wars/src/map/hex.rs +++ b/crates/border-wars/src/map/hex.rs @@ -54,9 +54,6 @@ pub trait Number: if self < Self::ZERO { -self } else { self } } - /// Converts `self` to an `usize`. - fn to_usize(self) -> usize; - /// Converts an `usize` to `Self`. fn from_usize(value: usize) -> Self; @@ -65,12 +62,6 @@ pub trait Number: /// Converts an `f32` to `Self`. fn from_f32(value: f32) -> Self; - - /// Converts `self` to an `isize`. - fn to_isize(self) -> isize; - - /// Converts an `isize` to `Self`. - fn from_isize(value: isize) -> Self; } /// Implements the `Number` trait for the given types. @@ -83,9 +74,6 @@ macro_rules! number_impl { const ONE: Self = [< 1 $t >]; const TWO: Self = [< 2 $t >]; - fn to_usize(self) -> usize { - self as usize - } fn from_usize(value: usize) -> Self { value as $t @@ -98,17 +86,7 @@ macro_rules! number_impl { fn from_f32(value: f32) -> Self { value as $t } - - fn to_isize(self) -> isize { - self as isize - } - - fn from_isize(value: isize) -> Self { - value as $t - } - } - )*}}; } -- 2.43.4