bestagon 0.6.0

An engine for discrete stuff in hexagonal grids
Documentation
use std::{collections::HashSet, hash::Hash};

use crate::{
  edge::{Edge, EdgeAx},
  hex::{Hex, HexAx},
};

#[cfg(feature = "bevy")]
use bevy_ecs::prelude::Component;
#[cfg(feature = "bevy")]
use bevy_reflect::Reflect;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// A node is shared by three edges, and by three hexes. It is the point that
/// connects them.
pub trait Node: Into<NodeAx> + From<NodeAx> + Hash + Eq + Copy {
  fn hexes<T>(&self) -> HashSet<T>
  where
    T: Hex,
  {
    let self_ax: NodeAx = (*self).into();
    let mut result = HashSet::new();
    match self_ax.q.rem_euclid(3) {
      1 => {
        // Right side of edge (flat orientation)
        assert_eq!(self_ax.r.rem_euclid(3), 1);
        result.insert(HexAx::new((self_ax.q - 1) / 3, (self_ax.r + 2) / 3).into());
        result.insert(HexAx::new((self_ax.q - 1) / 3, (self_ax.r - 1) / 3).into());
        result.insert(HexAx::new((self_ax.q + 2) / 3, (self_ax.r - 1) / 3).into());
      }
      2 => {
        // Left side of edge (flat orientation)
        assert_eq!(self_ax.r.rem_euclid(3), 2);
        result.insert(HexAx::new((self_ax.q - 2) / 3, (self_ax.r + 1) / 3).into());
        result.insert(HexAx::new((self_ax.q + 1) / 3, (self_ax.r + 1) / 3).into());
        result.insert(HexAx::new((self_ax.q + 1) / 3, (self_ax.r - 2) / 3).into());
      }
      _ => panic!(
        "Tried to evaluate hexes of an invalid node coordinate: {:?}",
        self_ax
      ),
    }
    result
  }

  fn edges<T>(&self) -> HashSet<T>
  where
    T: Edge,
  {
    let self_ax: NodeAx = (*self).into();
    let mut result = HashSet::new();
    match self_ax.q.rem_euclid(3) {
      1 => {
        // Right side of edge (flat orientation)
        assert_eq!(self_ax.r.rem_euclid(3), 1);
        // left
        result.insert(EdgeAx::new((self_ax.q * 2 - 2) / 3, (self_ax.r * 2 + 1) / 3).into());
        // top-right
        result.insert(EdgeAx::new((self_ax.q * 2 + 1) / 3, (self_ax.r * 2 + 1) / 3).into());
        // bottom-right
        result.insert(EdgeAx::new((self_ax.q * 2 + 1) / 3, (self_ax.r * 2 - 2) / 3).into());
      }
      2 => {
        // Left side of edge (flat orientation)
        assert_eq!(self_ax.r.rem_euclid(3), 2);
        // top-left
        result.insert(EdgeAx::new((self_ax.q * 2 - 1) / 3, (self_ax.r * 2 + 2) / 3).into());
        // bottom-left
        result.insert(EdgeAx::new((self_ax.q * 2 - 1) / 3, (self_ax.r * 2 - 1) / 3).into());
        // right
        result.insert(EdgeAx::new((self_ax.q * 2 + 2) / 3, (self_ax.r * 2 - 1) / 3).into());
      }
      _ => panic!(
        "Tried to evaluate hexes of an invalid node coordinate: {:?}",
        self_ax
      ),
    }
    result
  }
}

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

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

impl Node for NodeAx {}

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

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

impl From<&NodeAx> for FNodeAx {
  fn from(value: &NodeAx) -> Self {
    FNodeAx::new(value.q as f32, value.r as f32)
  }
}

#[cfg(test)]
mod test {
  use super::*;
  use crate::{edge::*, hex::*, hex_range::*, neighbours::*};
  use std::collections::HashSet;

  #[test]
  fn neighbouring_edges_share_node_cases() {
    for hex in HexRange::sphere(&HexAx::new(0, 0), 2).compute::<HexAx>() {
      for e in hex.edges() {
        verify_neighbouring_edge_share_node(&e);
      }
    }
  }

  fn verify_neighbouring_edge_share_node(e: &EdgeAx) {
    let my_nodes = e.nodes().into_iter().collect::<HashSet<NodeAx>>();
    for n in e.neighbours() {
      assert_eq!(
        my_nodes
          .intersection(&n.nodes())
          .collect::<HashSet<&NodeAx>>()
          .len(),
        1
      );
    }
  }

  #[test]
  fn hex_has_6_nodes_which_each_have_3_hexes_including_the_aforementioned_hex() {
    for hex in HexRange::sphere(&HexAx::new(0, 0), 3).compute::<HexAx>() {
      let nodes = hex.nodes::<NodeAx>();
      assert_eq!(nodes.len(), 6);
      for node in nodes {
        let hexes: HashSet<HexAx> = node.hexes();
        assert_eq!(hexes.len(), 3);
        assert!(hexes.contains(&hex));
      }
    }
  }

  #[test]
  fn hex_nodes_each_touch_2_edges_that_touch_the_aforementioned_hex() {
    for hex in HexRange::sphere(&HexAx::new(0, 0), 3).compute::<HexAx>() {
      let hex_edges: HashSet<EdgeAx> = hex.edges();
      let nodes = hex.nodes::<NodeAx>();
      for node in nodes {
        let edges: HashSet<EdgeAx> = node.edges();
        assert_eq!(edges.len(), 3);
        assert_eq!(
          edges
            .intersection(&hex_edges)
            .collect::<HashSet<&EdgeAx>>()
            .len(),
          2
        );
      }
    }
  }
}