|
@@ -0,0 +1,222 @@
|
|
|
+use std::collections::HashMap;
|
|
|
+
|
|
|
+
|
|
|
+/*
|
|
|
+ * The basic element is a single Triangle.
|
|
|
+ * It can be with a wide base (upright) or on its tip:
|
|
|
+ *
|
|
|
+ * /\ +--------+
|
|
|
+ * / \ \ /
|
|
|
+ * / \ \ /
|
|
|
+ * / \ \ /
|
|
|
+ * +--------+ \/
|
|
|
+ *
|
|
|
+ * We number its edges accordingly:
|
|
|
+ * 1
|
|
|
+ * /\ +--------+
|
|
|
+ * 3 / \ 2 \ /
|
|
|
+ * / \ \ /
|
|
|
+ * / \ 3 \ / 2
|
|
|
+ * +--------+ \/
|
|
|
+ * 1
|
|
|
+ *
|
|
|
+ */
|
|
|
+
|
|
|
+#[derive(Copy,Clone)]
|
|
|
+pub enum Edge {
|
|
|
+ BOTTOMTOP, // 1
|
|
|
+ RIGHT, // 2
|
|
|
+ LEFT // 3
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+/* Numbers 1..7 are used to identify a given part */
|
|
|
+pub type PartID = u8;
|
|
|
+
|
|
|
+/* Parts are described by a given upright triangle and a sequence of edges that are extended */
|
|
|
+pub type Part = Vec<Edge>;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+/* For example, the brown part (1) could be described by starting at the lower right triangle and
|
|
|
+ * traversing edges in the following order: 3, 3, 1, 1, 2, 1, 3
|
|
|
+ * ____
|
|
|
+ * \ /\
|
|
|
+ * \/__\
|
|
|
+ * /\ /\
|
|
|
+ * /__\/__\ <--
|
|
|
+ * \ /
|
|
|
+ * \/
|
|
|
+ *
|
|
|
+ *
|
|
|
+ *
|
|
|
+ */
|
|
|
+pub fn generate_parts() -> HashMap<PartID, Part> {
|
|
|
+ HashMap::from([
|
|
|
+ (1, vec![Edge::LEFT, Edge::LEFT, Edge::BOTTOMTOP, Edge::BOTTOMTOP, Edge::RIGHT, Edge::BOTTOMTOP, Edge::LEFT]),
|
|
|
+ (2, vec![Edge::RIGHT, Edge::BOTTOMTOP, Edge::RIGHT, Edge::RIGHT, Edge::BOTTOMTOP]),
|
|
|
+ (3, vec![Edge::RIGHT, Edge::BOTTOMTOP, Edge::LEFT, Edge::BOTTOMTOP, Edge::RIGHT]),
|
|
|
+ ])
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * A map has two sides (left and right) and is not neccesarily convex.
|
|
|
+ * We specify each side by a list of lines.
|
|
|
+ * Each line is represented by yet another list of skip entries:
|
|
|
+ * a two-tuple, where the first number designates the cells that are "skipped" (a.k.a inaccessible)
|
|
|
+ * and the second number which specifies the number of valid fields.
|
|
|
+ */
|
|
|
+pub type SkipEntryIO = (u8, u8);
|
|
|
+pub type MapSideIO = Vec<Vec<SkipEntryIO>>;
|
|
|
+pub type MapIO = (MapSideIO, MapSideIO);
|
|
|
+
|
|
|
+/*
|
|
|
+ * For in-memory representation however, we simply use a two-dimensional array of cells.
|
|
|
+ * Each cell can be a Barrier (meaning no part may be placed on the cell), Empty (meaning at the
|
|
|
+ * current time, it is not occupied by a part) or Occupied.
|
|
|
+ */
|
|
|
+pub enum Cell {
|
|
|
+ Barrier,
|
|
|
+ Empty,
|
|
|
+ Occupied(PartID)
|
|
|
+}
|
|
|
+
|
|
|
+impl Cell {
|
|
|
+ fn is_empty(&self) -> bool {
|
|
|
+ match self {
|
|
|
+ Cell::Empty => {
|
|
|
+ return true;
|
|
|
+ },
|
|
|
+ _ => {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * A triangular grid looks like this:
|
|
|
+ * __ __
|
|
|
+ * /\ /\ /\
|
|
|
+ * /__\/__\/__\
|
|
|
+ * \ /\ /\ /
|
|
|
+ * \/__\/__\/
|
|
|
+ * /\ /\ /\
|
|
|
+ * /__\/__\/__\
|
|
|
+ *
|
|
|
+ * We require by definition that the left upper-most triangle is upright.
|
|
|
+ * A map is then represented by a two-dimensional row-major (meaning the outer array represents the
|
|
|
+ * different rows) two-dimensional array with a maximum
|
|
|
+ * size of 16x16.
|
|
|
+ * Since there are two sides, we store both of them in a tuple.
|
|
|
+ */
|
|
|
+pub type MapSide = [[Cell; 16]; 16];
|
|
|
+pub type Map = (MapSide, MapSide);
|
|
|
+
|
|
|
+/**
|
|
|
+ * Helper function that tells us whether a given coordinate is an upright or upside-down triangle
|
|
|
+ */
|
|
|
+pub fn is_upright(x: u8, y: u8) -> bool {
|
|
|
+ if y % 2 == 0 {
|
|
|
+ /* On even rows, even columns have upright triangles */
|
|
|
+ return x % 2 == 0;
|
|
|
+ } else {
|
|
|
+ /* On odd rows, odd columns have upright triangles */
|
|
|
+ return x % 2 == 1;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Helper function that returns true if the coordinates are within map bounds
|
|
|
+ */
|
|
|
+pub fn in_bound(x: u8, y: u8) -> bool {
|
|
|
+ x < 16 && y < 16
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * Moves along an edge and returns a new coordinate pair.
|
|
|
+ * If the target would be out of bounds, it returns None.
|
|
|
+ */
|
|
|
+pub fn move_along_edge(x: u8, y: u8, edge: Edge) -> Option<(u8, u8)> {
|
|
|
+ match edge {
|
|
|
+ Edge::LEFT => {
|
|
|
+ if x == 0 {
|
|
|
+ return None;
|
|
|
+ }
|
|
|
+
|
|
|
+ return Some((x - 1, y));
|
|
|
+ },
|
|
|
+ Edge::RIGHT => {
|
|
|
+ if x == 15 {
|
|
|
+ return None;
|
|
|
+ }
|
|
|
+
|
|
|
+ return Some((x + 1, y));
|
|
|
+ },
|
|
|
+ Edge::BOTTOMTOP => {
|
|
|
+ if (is_upright(x, y) && y == 15)
|
|
|
+ || (!is_upright(x,y) && y == 0) {
|
|
|
+ return None;
|
|
|
+ }
|
|
|
+
|
|
|
+ if is_upright(x,y) {
|
|
|
+ return Some((x, y + 1));
|
|
|
+ } else {
|
|
|
+ return Some((x, y - 1));
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Parts can be inverted (flipped on the other side) and also rotated.
|
|
|
+ * On a triangular grid, three rotations are possible.
|
|
|
+ * We represent rotations by specifying which edge the base of the given part ends up at.
|
|
|
+ *
|
|
|
+ * The task of our core logic function below is to figure out whether a given part can fit on a
|
|
|
+ * specified location on the map.
|
|
|
+ *
|
|
|
+ * Returns true if the part can fit at the specified location on the map.
|
|
|
+ */
|
|
|
+pub fn check_part(map: &MapSide, part: &Part, x: u8, y: u8, rotation: Edge) -> bool {
|
|
|
+ /* Make sure the start triangle can actually be placed */
|
|
|
+ assert!(in_bound(x, y));
|
|
|
+
|
|
|
+ if !map[y as usize][x as usize].is_empty() {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ let mut mx = x;
|
|
|
+ let mut my = y;
|
|
|
+
|
|
|
+ for movement in part {
|
|
|
+ let result = move_along_edge(mx, my, *movement);
|
|
|
+
|
|
|
+ if result.is_none() {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ (mx, my) = result.unwrap();
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+#[cfg(test)]
|
|
|
+mod test {
|
|
|
+ use super::*;
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_is_upright() {
|
|
|
+ for (x, y) in [(0,0), (1, 1), (2,0)] {
|
|
|
+ assert!(is_upright(x, y));
|
|
|
+ }
|
|
|
+
|
|
|
+ for (x, y) in [(0, 1), (0,3), (2,1)] {
|
|
|
+ assert!(!is_upright(x, y));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|