bestagon 0.5.0

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

use crate::{division::*, hex::*, neighbours::*, node::Node, node::NodeAx};

/// 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> + Neighbour + 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()
    }
  }

  /// Returns the hexes of which this edge is the border.
  fn hexes<T>(&self) -> HashSet<T>
  where
    T: Hex,
  {
    let self_ax: EdgeAx = (*self).into();
    let mut result = HashSet::new();
    result.insert(HexAx::new(self_ax.q.my_div_floor(2), self_ax.r.my_div_ceil(2)).into());
    result.insert(HexAx::new(self_ax.q.my_div_ceil(2), self_ax.r.my_div_floor(2)).into());
    result
  }

  /// Returns the nodes at the ends of this edge.
  fn nodes<T>(&self) -> HashSet<T>
  where
    T: Node,
  {
    let self_ax: EdgeAx = (*self).into();

    let mut result = HashSet::new();

    if self_ax.q % 2 == 0 {
      // X doesn't change when passing edge: at top/bottom (in flat orientation)
      result.insert(NodeAx::new((self_ax.q * 3 - 2) / 2, (self_ax.r * 3 + 1) / 2).into());
      result.insert(NodeAx::new((self_ax.q * 3 + 2) / 2, (self_ax.r * 3 - 1) / 2).into());
    } else if self_ax.r % 2 == 0 {
      // Y doesn't change when passing edge: at bottom-left/top-right
      // (in flat orientation)
      result.insert(NodeAx::new((self_ax.q * 3 + 1) / 2, (self_ax.r * 3 - 2) / 2).into());
      result.insert(NodeAx::new((self_ax.q * 3 - 1) / 2, (self_ax.r * 3 + 2) / 2).into());
    } else {
      assert_eq!((-self_ax.q - self_ax.r) % 2, 0);
      // Z doesn't change when passing edge: at top-left/bottom-right
      // (in flat orientation)
      result.insert(NodeAx::new((self_ax.q * 3 - 1) / 2, (self_ax.r * 3 - 1) / 2).into());
      result.insert(NodeAx::new((self_ax.q * 3 + 1) / 2, (self_ax.r * 3 + 1) / 2).into());
    }

    result
  }
}

/// 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)]
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 {}

impl Neighbour for EdgeAx {
  fn get_neighbour(&self, n: u8) -> Option<Self> {
    if self.q % 2 == 0 {
      // X doesn't change when passing edge: at top/bottom (in flat orientation)
      match n {
        0 => Some(EdgeAx::new(self.q - 1, self.r + 1)), // top left
        1 => Some(EdgeAx::new(self.q - 1, self.r)),     // bottom left
        2 => Some(EdgeAx::new(self.q + 1, self.r - 1)), // bottom right
        3 => Some(EdgeAx::new(self.q + 1, self.r)),     // top right
        _ => None,
      }
    } else if self.r % 2 == 0 {
      // Y doesn't change when passing edge: at bottom-left/top-right
      // (in flat orientation)
      match n {
        0 => Some(EdgeAx::new(self.q, self.r - 1)),     // bottom
        1 => Some(EdgeAx::new(self.q + 1, self.r - 1)), // right
        2 => Some(EdgeAx::new(self.q, self.r + 1)),     // top
        3 => Some(EdgeAx::new(self.q - 1, self.r + 1)), // left
        _ => None,
      }
    } else {
      assert_eq!((-self.q - self.r) % 2, 0);
      // Z doesn't change when passing edge: at top-left/bottom-right
      // (in flat orientation)
      match n {
        0 => Some(EdgeAx::new(self.q - 1, self.r)), // left
        1 => Some(EdgeAx::new(self.q, self.r - 1)), // bottom
        2 => Some(EdgeAx::new(self.q + 1, self.r)), // right
        3 => Some(EdgeAx::new(self.q, self.r + 1)), // top
        _ => None,
      }
    }
  }
}

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::Neighbours};

  #[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.neighbours() {
      let e = EdgeAx::between(&center, &n);
      let hexes: HashSet<HexAx> = e.hexes();
      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::<EdgeAx>().len(), 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::<EdgeAx>() {
      let hs: HashSet<HexAx> = e.neighbours().flat_map(|en| en.hexes()).collect();
      assert_eq!(hs.len(), 4);
    }
  }
}