bestagon 0.9.0

An engine for discrete stuff in hexagonal grids
Documentation
use crate::hex::*;
#[cfg(feature = "bevy")]
use bevy_ecs::prelude::Component;
#[cfg(feature = "bevy")]
use bevy_reflect::Reflect;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::hash::Hash;

/// A struct can be considered and Edge if it implements a number of other
/// traits and this traits provides default implementations for some behaviour.
pub trait Edge: Into<EdgeAx> + From<EdgeAx> + Hash + Eq + Copy {
  /// Returns the edge that borders the two given hexes.
  fn between<T>(a: &T, b: &T) -> Self
  where
    T: Hex,
  {
    if a.dist(b) > 1 {
      panic!("Cannot have edge between non-neighbouring hexes.");
    } else {
      let (a_ax, b_ax): (HexAx, HexAx) = ((*a).into(), (*b).into());
      EdgeAx::new(a_ax.q + b_ax.q, a_ax.r + b_ax.r).into()
    }
  }
}

/// An edge represents the edge between two hex faces. To get the coordinates
/// for an edge, you would want to use the average of the coordinates of the two
/// hexes that touch it. Since we use integers, we cannot use the average so
/// instead just use the sum (in this case the average times 2).
#[derive(Eq, PartialEq, Debug, Copy, Clone, Hash, Default)]
#[cfg_attr(feature = "bevy", derive(Reflect, Component))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct EdgeAx {
  pub q: isize,
  pub r: isize,
}

impl EdgeAx {
  pub fn new(q: isize, r: isize) -> Self {
    EdgeAx { q, r }
  }
}

impl Edge for EdgeAx {}

#[derive(PartialEq, Debug, Copy, Clone, Default)]
#[cfg_attr(feature = "bevy", derive(Reflect, Component))]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FEdgeAx {
  pub q: f32,
  pub r: f32,
}

impl FEdgeAx {
  pub fn new(q: f32, r: f32) -> Self {
    FEdgeAx { q, r }
  }
}

#[cfg(test)]
mod test {
  use std::collections::HashSet;

  use super::*;
  use crate::{
    hex_range::*,
    neighbours::{Edges, Hexes},
  };

  #[test]
  fn edges_are_reversible_cases() {
    for c in HexRange::sphere(&HexCb::new(0, 0, 0), 5).compute() {
      check_edge_reversibility(c);
    }
  }

  fn check_edge_reversibility(center: HexAx) {
    for n in center.hexes() {
      let e = EdgeAx::between(&center, &n);
      let hexes: HashSet<HexAx> = e.hexes().collect();
      assert_eq!(hexes.len(), 2);
      assert!(hexes.contains(&center));
      assert!(hexes.contains(&n));
    }
  }

  #[test]
  fn face_has_6_edges() {
    assert_eq!(HexAx::new(0, 0).edges().count(), 6);
  }

  #[test]
  fn sphere_r_1_has_30_edges() {
    assert_eq!(
      HexRange::sphere(&HexAx::new(0, 0), 1)
        .compute::<HexAx>()
        .iter()
        .flat_map(|hf| hf.edges().into_iter())
        .collect::<HashSet<EdgeAx>>()
        .len(),
      30
    );
  }

  #[test]
  fn sphere_r_2_has_72_edges() {
    assert_eq!(
      HexRange::sphere(&HexCb::new(0, 0, 0), 2)
        .compute::<HexAx>()
        .iter()
        .flat_map(|hf| hf.edges().into_iter())
        .collect::<HashSet<EdgeAx>>()
        .len(),
      72
    );
  }

  #[test]
  fn edge_neighbours_touch_4_hexes() {
    let x = HexAx::new(1, 2);
    for e in x.edges() {
      let hs: HashSet<HexAx> = e
        .edges()
        .flat_map(|en| en.hexes().collect::<Vec<HexAx>>())
        .collect();
      assert_eq!(hs.len(), 4);
    }
  }
}