use crate::{Hex, hex::ExactSizeHexIterator};
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct Parallelogram {
pub min: Hex,
pub max: Hex,
}
impl Default for Parallelogram {
fn default() -> Self {
Self {
min: Hex::splat(-10),
max: Hex::splat(10),
}
}
}
impl Parallelogram {
#[inline]
#[must_use]
pub const fn new(min: Hex, max: Hex) -> Self {
Self { min, max }
}
#[must_use]
#[inline]
pub fn coords(self) -> impl ExactSizeIterator<Item = Hex> {
parallelogram(self.min, self.max)
}
}
#[must_use]
#[expect(clippy::cast_sign_loss)]
pub fn parallelogram(min: Hex, max: Hex) -> impl ExactSizeIterator<Item = Hex> {
let dist = (max.x.saturating_sub(min.x) + 1) * (max.y.saturating_sub(min.y) + 1);
ExactSizeHexIterator {
iter: (min.x()..=max.x())
.flat_map(move |x| (min.y()..=max.y()).map(move |y| Hex::new(x, y))),
count: dist as usize,
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct Triangle {
pub size: u32,
}
impl Default for Triangle {
fn default() -> Self {
Self { size: 10 }
}
}
impl Triangle {
#[inline]
#[must_use]
pub const fn new(size: u32) -> Self {
Self { size }
}
#[must_use]
#[inline]
pub fn coords(self) -> impl ExactSizeIterator<Item = Hex> {
triangle(self.size)
}
}
#[expect(clippy::cast_possible_wrap)]
#[must_use]
pub fn triangle(size: u32) -> impl ExactSizeIterator<Item = Hex> {
ExactSizeHexIterator {
iter: (0..=size)
.flat_map(move |x| (0..=(size - x)).map(move |y| Hex::new(x as i32, y as i32))),
count: Hex::wedge_count(size) as usize,
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct Hexagon {
pub center: Hex,
pub radius: u32,
}
impl Default for Hexagon {
fn default() -> Self {
Self {
center: Hex::ZERO,
radius: 10,
}
}
}
impl Hexagon {
#[inline]
#[must_use]
pub const fn new(center: Hex, radius: u32) -> Self {
Self { center, radius }
}
#[inline]
#[must_use]
pub fn coords(self) -> impl ExactSizeIterator<Item = Hex> {
hexagon(self.center, self.radius)
}
}
#[must_use]
pub fn hexagon(center: Hex, radius: u32) -> impl ExactSizeIterator<Item = Hex> {
center.range(radius)
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct Rombus {
pub origin: Hex,
pub rows: u32,
pub columns: u32,
}
impl Default for Rombus {
fn default() -> Self {
Self {
origin: Hex::ZERO,
rows: 10,
columns: 10,
}
}
}
impl Rombus {
#[must_use]
pub fn coords(self) -> impl ExactSizeIterator<Item = Hex> {
rombus(self.origin, self.rows, self.columns)
}
}
#[must_use]
#[expect(clippy::cast_possible_wrap)]
pub fn rombus(point: Hex, rows: u32, columns: u32) -> impl ExactSizeIterator<Item = Hex> {
ExactSizeHexIterator {
iter: (0..rows).flat_map(move |y| {
(0..columns).map(move |x| point.const_add(Hex::new(x as i32, y as i32)))
}),
count: (rows * columns) as usize,
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct PointyRectangle {
pub left: i32,
pub right: i32,
pub top: i32,
pub bottom: i32,
}
impl Default for PointyRectangle {
fn default() -> Self {
Self {
left: -10,
right: 10,
top: -10,
bottom: 10,
}
}
}
impl PointyRectangle {
#[must_use]
#[inline]
pub fn coords(self) -> impl ExactSizeIterator<Item = Hex> {
pointy_rectangle([self.left, self.right, self.top, self.bottom])
}
}
#[must_use]
#[expect(clippy::cast_sign_loss)]
pub fn pointy_rectangle(
[left, right, top, bottom]: [i32; 4],
) -> impl ExactSizeIterator<Item = Hex> {
let count = (right.saturating_sub(left) + 1) * (bottom.saturating_sub(top) + 1);
ExactSizeHexIterator {
iter: (top..=bottom).flat_map(move |y| {
let y_offset = y >> 1;
((left - y_offset)..=(right - y_offset)).map(move |x| Hex::new(x, y))
}),
count: count as usize,
}
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "facet", derive(facet::Facet))]
#[cfg_attr(feature = "bevy_reflect", derive(bevy_reflect::Reflect))]
pub struct FlatRectangle {
pub left: i32,
pub right: i32,
pub top: i32,
pub bottom: i32,
}
impl Default for FlatRectangle {
fn default() -> Self {
Self {
left: -10,
right: 10,
top: -10,
bottom: 10,
}
}
}
impl FlatRectangle {
#[must_use]
pub fn coords(self) -> impl ExactSizeIterator<Item = Hex> {
flat_rectangle([self.left, self.right, self.top, self.bottom])
}
}
#[must_use]
#[expect(clippy::cast_sign_loss)]
pub fn flat_rectangle([left, right, top, bottom]: [i32; 4]) -> impl ExactSizeIterator<Item = Hex> {
let count = (right.saturating_sub(left) + 1) * (bottom.saturating_sub(top) + 1);
ExactSizeHexIterator {
iter: (left..=right).flat_map(move |x| {
let x_offset = x >> 1;
((top - x_offset)..=(bottom - x_offset)).map(move |y| Hex::new(x, y))
}),
count: count as usize,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hexagon_test() {
let point = Hex::new(3, -987);
for range in 0..=30 {
let iter = hexagon(point, range);
assert_eq!(iter.len(), iter.count());
}
}
#[test]
fn triangle_test() {
for range in 0..=30 {
let iter = triangle(range);
assert_eq!(iter.len(), iter.count());
}
}
#[test]
fn parallelogram_test() {
for min in 0..=30 {
for max in 0..=30 {
let iter = parallelogram(Hex::splat(min), Hex::splat(min + max));
assert_eq!(iter.len(), iter.count());
}
}
}
#[test]
fn rombus_test() {
for columns in 0..=30 {
for rows in 0..=30 {
for p in Hex::ZERO.range(10) {
let iter = rombus(p, rows, columns);
assert_eq!(iter.len(), iter.count());
}
}
}
}
#[test]
fn pointy_rectangle_test() {
for left in -20..=20 {
for right in 0..=20 {
for top in -20..=20 {
for bottom in 0..=20 {
let iter = pointy_rectangle([left, left + right, top, top + bottom]);
assert_eq!(iter.len(), iter.count());
}
}
}
}
}
#[test]
fn flat_rectangle_test() {
for left in -20..=20 {
for right in 0..=20 {
for top in -20..=20 {
for bottom in 0..=20 {
let iter = flat_rectangle([left, left + right, top, top + bottom]);
assert_eq!(iter.len(), iter.count());
}
}
}
}
}
}