|
@@ -20,6 +20,8 @@ use std::collections::HashMap;
|
|
|
* +--------+ \/
|
|
|
* 1
|
|
|
*
|
|
|
+ * counter-clockwise clockwise
|
|
|
+ *
|
|
|
*/
|
|
|
|
|
|
#[derive(Debug,PartialEq,Copy,Clone)]
|
|
@@ -39,7 +41,8 @@ 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
|
|
|
+ * traversing edges in the following order:
|
|
|
+ * 3, 3, 1, 1, 2, 1, 3
|
|
|
* ____
|
|
|
* \ /\
|
|
|
* \/__\
|
|
@@ -49,7 +52,85 @@ pub type Part = Vec<Edge>;
|
|
|
* \/
|
|
|
*
|
|
|
* (1)
|
|
|
+ *
|
|
|
+ * Rotations look as follows:
|
|
|
+ *
|
|
|
+ * 120 degrees.
|
|
|
+ * Our edges are now 1, 2, 2, 3, 3, 3, 1
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * --> /\
|
|
|
+ * /__\___
|
|
|
+ * /\ /\ /
|
|
|
+ * /__\/__\/
|
|
|
+ * \ /
|
|
|
+ * \/
|
|
|
+ *
|
|
|
+ * 240 degrees.
|
|
|
+ *
|
|
|
+ * 2, 1, 3, 2, 1, 2, 2
|
|
|
+ * ____
|
|
|
+ * \ /\
|
|
|
+ * \/__\____
|
|
|
+ * /\ /\ /
|
|
|
+ * --> /__\/__\/
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * We observe the following principle:
|
|
|
+ *
|
|
|
+ * 0 deg 120deg 240deg
|
|
|
+ *
|
|
|
+ * 3 /\ 2 2 /\ 1 1 /\ 3
|
|
|
+ * /__\ /__\ /__\
|
|
|
+ * 1 3 2
|
|
|
+ *
|
|
|
+ * 1 2 3
|
|
|
+ * __v_ __v_ __v_
|
|
|
+ * \ / \ / \ /
|
|
|
+ * 3 \/ 2 1 \/ 3 2 \/ 1
|
|
|
+ *
|
|
|
+ *
|
|
|
+ * For upright triangles the edges can be translated as follows: 1 => 2, 2 => 3, 3 => 1.
|
|
|
+ *
|
|
|
+ * For upside-down triangles, we need to take special care, since their order is specified
|
|
|
+ * clockwise, in order to keep or left-right-convention. Thus, the edge translation looks as
|
|
|
+ * follows: 1 => 3, 2 => 1, 3 => 2
|
|
|
+ *
|
|
|
+ * In other words, for upright triangles we do a left-shift, on upside-down triangles we do
|
|
|
+ * a right-shift.
|
|
|
+ *
|
|
|
*/
|
|
|
+
|
|
|
+impl Edge {
|
|
|
+ fn from_int(x: i8) -> Edge {
|
|
|
+ match x {
|
|
|
+ 0 => Edge::BOTTOMTOP,
|
|
|
+ 1 => Edge::RIGHT,
|
|
|
+ 2 => Edge::LEFT,
|
|
|
+ _ => panic!("Invalid argument"),
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn to_int(&self) -> i8 {
|
|
|
+ match self {
|
|
|
+ Edge::BOTTOMTOP => 0,
|
|
|
+ Edge::RIGHT => 1,
|
|
|
+ Edge::LEFT => 2,
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ fn rotate(&self, other: &Edge, upright: bool) -> Edge {
|
|
|
+ let shift_amount = other.to_int();
|
|
|
+ let shift_direction = if upright { -1 } else { 1 };
|
|
|
+
|
|
|
+ let shifted = (self.to_int() + shift_direction * shift_amount).rem_euclid(3);
|
|
|
+ println!("Shifted: {}", shifted);
|
|
|
+
|
|
|
+ Edge::from_int(shifted)
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
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]),
|
|
@@ -110,8 +191,10 @@ impl Cell {
|
|
|
* 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);
|
|
|
+pub const MAP_WIDTH : u8 = 16;
|
|
|
+pub const MAP_HEIGHT : u8 = 5;
|
|
|
+pub type Map = [[Cell; MAP_WIDTH as usize]; MAP_HEIGHT as usize];
|
|
|
+
|
|
|
|
|
|
/**
|
|
|
* Helper function that tells us whether a given coordinate is an upright or upside-down triangle
|
|
@@ -130,7 +213,7 @@ pub fn is_upright(x: u8, y: u8) -> bool {
|
|
|
* 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
|
|
|
+ x < MAP_WIDTH && y < MAP_HEIGHT
|
|
|
}
|
|
|
|
|
|
/**
|
|
@@ -147,14 +230,14 @@ pub fn move_along_edge(x: u8, y: u8, edge: Edge) -> Option<(u8, u8)> {
|
|
|
return Some((x - 1, y));
|
|
|
},
|
|
|
Edge::RIGHT => {
|
|
|
- if x == 15 {
|
|
|
+ if x == MAP_WIDTH - 1 {
|
|
|
return None;
|
|
|
}
|
|
|
|
|
|
return Some((x + 1, y));
|
|
|
},
|
|
|
Edge::BOTTOMTOP => {
|
|
|
- if (is_upright(x, y) && y == 15)
|
|
|
+ if (is_upright(x, y) && y == MAP_HEIGHT - 1)
|
|
|
|| (!is_upright(x,y) && y == 0) {
|
|
|
return None;
|
|
|
}
|
|
@@ -169,6 +252,7 @@ pub fn move_along_edge(x: u8, y: u8, edge: Edge) -> Option<(u8, u8)> {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/*
|
|
|
* Parts can be inverted (flipped on the other side) and also rotated.
|
|
|
* On a triangular grid, three rotations are possible.
|
|
@@ -179,7 +263,7 @@ pub fn move_along_edge(x: u8, y: u8, edge: Edge) -> Option<(u8, u8)> {
|
|
|
*
|
|
|
* 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 {
|
|
|
+pub fn check_part(map: &Map, part: &Part, x: u8, y: u8, rotation: Edge) -> bool {
|
|
|
/* Make sure the start triangle can actually be placed */
|
|
|
assert!(in_bound(x, y));
|
|
|
|
|
@@ -190,9 +274,9 @@ pub fn check_part(map: &MapSide, part: &Part, x: u8, y: u8, rotation: Edge) -> b
|
|
|
let mut mx : u8 = x;
|
|
|
let mut my : u8 = y;
|
|
|
|
|
|
- println!("Starting at {}, {}", x, y);
|
|
|
for movement in part {
|
|
|
- let result = move_along_edge(mx, my, *movement);
|
|
|
+ let rotated_movement = movement.rotate(&rotation, is_upright(x, y));
|
|
|
+ let result = move_along_edge(mx, my, rotated_movement);
|
|
|
|
|
|
if result.is_none() {
|
|
|
return false;
|
|
@@ -203,13 +287,79 @@ pub fn check_part(map: &MapSide, part: &Part, x: u8, y: u8, rotation: Edge) -> b
|
|
|
if !map[my as usize][mx as usize].is_empty() || !in_bound(mx, my) {
|
|
|
return false;
|
|
|
}
|
|
|
- println!("Went to {}, {}", mx, my);
|
|
|
}
|
|
|
println!("");
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
+/* Once we have found a valid position with `check_part`, we can place the part on the map.
|
|
|
+ */
|
|
|
+fn place_part(map: &mut Map, id: PartID, part: &Part, x: u8, y: u8, rotation: Edge) {
|
|
|
+ map[y as usize][x as usize] = Cell::Occupied(id);
|
|
|
+
|
|
|
+ let mut mx : u8 = x;
|
|
|
+ let mut my : u8 = y;
|
|
|
+
|
|
|
+ for movement in part {
|
|
|
+ let rotated_movement = movement.rotate(&rotation, is_upright(x, y));
|
|
|
+ (mx, my) = move_along_edge(mx, my, rotated_movement).unwrap();
|
|
|
+ map[my as usize][mx as usize] = Cell::Occupied(id);
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+/* If a part is not placed right, we might need to remove it from the map again
|
|
|
+ */
|
|
|
+fn erase_part(map: &mut Map, part: &Part, x: u8, y: u8, rotation: Edge) {
|
|
|
+ map[y as usize][x as usize] = Cell::Empty;
|
|
|
+
|
|
|
+ let mut mx : u8 = x;
|
|
|
+ let mut my : u8 = y;
|
|
|
+
|
|
|
+ for movement in part {
|
|
|
+ let rotated_movement = movement.rotate(&rotation, is_upright(x, y));
|
|
|
+ (mx, my) = move_along_edge(mx, my, rotated_movement).unwrap();
|
|
|
+ map[my as usize][mx as usize] = Cell::Empty;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+pub fn solve(map: &mut Map, parts: &[(PartID, &Part)]) -> Option<()>{
|
|
|
+ if parts.len() == 0 {
|
|
|
+ return Some(());
|
|
|
+ }
|
|
|
+
|
|
|
+ let (id, part) = parts[0];
|
|
|
+
|
|
|
+
|
|
|
+ // Todo: limit to non-barrier positions
|
|
|
+ for x in 0..MAP_WIDTH {
|
|
|
+ for y in 0..MAP_HEIGHT {
|
|
|
+ if !map[y as usize][x as usize].is_empty() {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ for rotation in [Edge::BOTTOMTOP, Edge::LEFT, Edge::RIGHT] {
|
|
|
+ let valid_position = check_part(&map, &part, x, y, rotation);
|
|
|
+
|
|
|
+ if valid_position {
|
|
|
+ // Place our part and try the others
|
|
|
+ place_part(map, id, &part, x, y, rotation);
|
|
|
+ if let Some(()) = solve(map, &parts[1..]) {
|
|
|
+ // We have found a valid solution we can pass back!
|
|
|
+ return Some(());
|
|
|
+ } else {
|
|
|
+ // Remove the part from our position and try again
|
|
|
+ erase_part(map, &part, x, y, rotation);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ None
|
|
|
+}
|
|
|
|
|
|
#[cfg(test)]
|
|
|
mod test {
|
|
@@ -239,48 +389,56 @@ mod test {
|
|
|
|
|
|
}
|
|
|
|
|
|
+ #[test]
|
|
|
+ fn test_rotation() {
|
|
|
+ // 120 deg counter-clockwise rotation
|
|
|
+ for (expected, original, upright) in [
|
|
|
+ (3, 1, true), (1, 2, true), (2, 3, true),
|
|
|
+ (2, 1, false), (1, 3, false), (3, 2, false)] {
|
|
|
+ assert_eq!(Edge::from_int(expected - 1), Edge::from_int(original - 1)
|
|
|
+ .rotate(&Edge::RIGHT, upright));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 240 deg counter-clockwise rotation
|
|
|
+ for (expected, original, upright) in [
|
|
|
+ (3, 2, true), (1, 3, true), (2, 1, true),
|
|
|
+ (2, 3, false), (1, 2, false), (3, 1, false)] {
|
|
|
+ assert_eq!(Edge::from_int(expected - 1), Edge::from_int(original - 1)
|
|
|
+ .rotate(&Edge::LEFT, upright));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
#[test]
|
|
|
fn test_part_placement() {
|
|
|
let B = Cell::Barrier;
|
|
|
let E = Cell::Empty;
|
|
|
|
|
|
- let left : MapSide = [
|
|
|
+ let map : Map = [
|
|
|
[B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
[B, B, E, E, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
[B, B, E, E, E, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
[B, B, E, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
[B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
- [B, B, B, B, B, B, B, B, B, B, B, B, B, B, B, B],
|
|
|
];
|
|
|
|
|
|
let parts = generate_parts();
|
|
|
let brown_part = parts.get(&1).unwrap();
|
|
|
|
|
|
|
|
|
- assert!(check_part(&left, &brown_part, 4, 2, Edge::BOTTOMTOP));
|
|
|
+ assert!(check_part(&map, &brown_part, 4, 2, Edge::BOTTOMTOP));
|
|
|
let mut called : usize = 0;
|
|
|
- for x in 0..15 {
|
|
|
- for y in 0..15 {
|
|
|
+ for x in 0..MAP_WIDTH {
|
|
|
+ for y in 0..MAP_HEIGHT {
|
|
|
for rotation in [Edge::LEFT, Edge::RIGHT, Edge::BOTTOMTOP] {
|
|
|
- if check_part(&left, &brown_part, x, y, Edge::BOTTOMTOP) {
|
|
|
- //assert_eq!(Edge::BOTTOMTOP, rotation);
|
|
|
+ if check_part(&map, &brown_part, x, y, rotation) {
|
|
|
+ assert_eq!(Edge::BOTTOMTOP, rotation);
|
|
|
assert_eq!((4, 2), (x, y));
|
|
|
called += 1;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- assert_eq!(3, called);
|
|
|
+ assert_eq!(1, called);
|
|
|
|
|
|
}
|
|
|
|