Ver código fonte

Split in main, lib. Add tests.

Christoph Stelz 1 ano atrás
pai
commit
2f0c2d9bf1
2 arquivos alterados com 222 adições e 98 exclusões
  1. 222 0
      src/lib.rs
  2. 0 98
      src/main.rs

+ 222 - 0
src/lib.rs

@@ -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));
+        }
+    }
+
+}

+ 0 - 98
src/main.rs

@@ -1,101 +1,3 @@
-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
- *
- */
-
-type Edge = u8;
-
-
-/* Numbers 1..7 are used to identify a given part */
-type PartID = u8;
-
-/* Parts are described by a given upright triangle and a sequence of edges that are extended */
-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
- *      ____
- *      \  /\
- *       \/__\
- *       /\  /\
- *      /__\/__\ <--
- *      \  /
- *       \/
- *
- *
- *
- */
-fn generate_parts() -> HashMap<PartID, Part> {
-    HashMap::from([
-        (1, vec![3, 3, 1, 1, 2, 1, 3]),
-        (2, vec![2, 1, 2, 2, 1]),
-        (3, vec![2, 1, 3, 1, 2]),
-    ])
-}
-
-/*
- * 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.
- */
-type SkipEntryIO = (u8, u8);
-type MapSideIO = Vec<Vec<SkipEntryIO>>;
-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.
- */
-enum Cell {
-    Barrier,
-    Empty,
-    Occupied(PartID)
-}
-
-/*
- * 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.
- */
-type MapSide = [[Cell; 16]; 16];
-type Map = (MapSide, MapSide);
-
-
 fn main() {
     println!("Hello, world!");
 }