|
@@ -1,158 +1,119 @@
|
|
|
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
|
|
|
- *
|
|
|
- * counter-clockwise clockwise
|
|
|
- *
|
|
|
- */
|
|
|
-
|
|
|
-#[derive(Debug,PartialEq,Copy,Clone)]
|
|
|
-pub enum Edge {
|
|
|
- BOTTOMTOP, // 1
|
|
|
- RIGHT, // 2
|
|
|
- LEFT // 3
|
|
|
+#[derive(Clone,Debug,PartialEq)]
|
|
|
+pub struct Coord {
|
|
|
+ pub a: i8,
|
|
|
+ pub b: i8,
|
|
|
+ pub c: i8,
|
|
|
}
|
|
|
|
|
|
+impl Coord {
|
|
|
|
|
|
-/* Numbers 1..7 are used to identify a given part */
|
|
|
-pub type PartID = u8;
|
|
|
+ fn from(a : i8, b: i8, c: i8) -> Coord {
|
|
|
+ Coord {
|
|
|
+ a: a,
|
|
|
+ b: b,
|
|
|
+ c: c
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
-/* Parts are described by a given upright triangle and a sequence of edges that are extended */
|
|
|
-pub type Part = Vec<Edge>;
|
|
|
+ /**
|
|
|
+ * Rotates coordinate around the origin by 120°
|
|
|
+ */
|
|
|
+ fn rotate(&self, rotations: u8) -> Coord {
|
|
|
+ match rotations % 3 {
|
|
|
+ 0 => self.clone(),
|
|
|
+ 1 => Coord::from(self.b, self.c, self.a),
|
|
|
+ _ => Coord::from(self.c, self.a, self.b),
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
|
|
|
|
|
|
-/* 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
|
|
|
- * ____
|
|
|
- * \ /\
|
|
|
- * \/__\
|
|
|
- * /\ /\
|
|
|
- * /__\/__\ <--
|
|
|
- * \ /
|
|
|
- * \/
|
|
|
- *
|
|
|
- * (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"),
|
|
|
- }
|
|
|
+ /**
|
|
|
+ * Flip coordinate at the y-axis
|
|
|
+ */
|
|
|
+ fn flip(&self) -> Coord {
|
|
|
+ Coord::from(self.c, self.b, self.a)
|
|
|
}
|
|
|
|
|
|
- fn to_int(&self) -> i8 {
|
|
|
- match self {
|
|
|
- Edge::BOTTOMTOP => 0,
|
|
|
- Edge::RIGHT => 1,
|
|
|
- Edge::LEFT => 2,
|
|
|
+ fn upright(&self) -> bool {
|
|
|
+ self.a + self.b + self.c == 2
|
|
|
+ }
|
|
|
+
|
|
|
+ fn right_neighbor(self) -> Coord {
|
|
|
+ if self.upright() {
|
|
|
+ return Coord::from(self.a, self.b, self.c - 1);
|
|
|
+ } else {
|
|
|
+ return Coord::from(self.a + 1, self.b, self.c);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- fn rotate(&self, other: &Edge, upright: bool) -> Edge {
|
|
|
- let shift_amount = other.to_int();
|
|
|
- let shift_direction = if upright { -1 } else { 1 };
|
|
|
+ fn from_cartesian(x: i8, y: i8) -> Coord {
|
|
|
+ assert!(x >= 0 && y >= 0 && x < 63 && y < 63);
|
|
|
+ let upright : bool = (x + y) % 2 == 0;
|
|
|
+
|
|
|
+ let b : i8 = 1 - y;
|
|
|
+ let x_offset : i8 = (b - 1).abs() / 2;
|
|
|
+ let a : i8 = x_offset + x / 2;
|
|
|
+ let c : i8 = if upright { 2 - a - b } else {1 - a - b};
|
|
|
+
|
|
|
+ Coord::from(a,b,c)
|
|
|
+ }
|
|
|
+
|
|
|
+ fn to_cartesian(&self) -> (i8, i8) {
|
|
|
+ let y : i8 = 1 - self.b;
|
|
|
+ let x_offset : i8 = (self.b - 1).abs() / 2;
|
|
|
+ let x : i8 = 2 * (self.a - x_offset) + if self.upright() { y % 2 } else { 1 - (y % 2) };
|
|
|
+
|
|
|
+
|
|
|
|
|
|
- let shifted = (self.to_int() + shift_direction * shift_amount).rem_euclid(3);
|
|
|
+ return (x, y);
|
|
|
|
|
|
- Edge::from_int(shifted)
|
|
|
}
|
|
|
+}
|
|
|
+
|
|
|
+impl std::ops::Add<Coord> for Coord {
|
|
|
+ type Output = Coord;
|
|
|
+
|
|
|
+ fn add(self, _rhs: Coord) -> Coord {
|
|
|
+ return Coord {
|
|
|
+ a: self.a + _rhs.a,
|
|
|
+ b: self.b + _rhs.b,
|
|
|
+ c: self.c + _rhs.c
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+/* Numbers 1..7 are used to identify a given part */
|
|
|
+pub type PartID = u8;
|
|
|
+
|
|
|
+pub type Part = Vec<Coord>;
|
|
|
+
|
|
|
+/** Allow part definition with tuples for brevity
|
|
|
+ */
|
|
|
+fn generate_part(arr: &[(i8, i8, i8)]) -> Part {
|
|
|
+ let v: Vec<Coord> = arr.iter()
|
|
|
+ .map(|(a,b,c)| Coord::from(*a,*b,*c))
|
|
|
+ .collect();
|
|
|
|
|
|
+ return v;
|
|
|
}
|
|
|
|
|
|
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]),
|
|
|
- (4, vec![Edge::BOTTOMTOP, Edge::RIGHT, Edge::RIGHT, Edge::RIGHT, Edge::RIGHT]),
|
|
|
- (5, vec![Edge::LEFT, Edge::BOTTOMTOP, Edge::LEFT, Edge::LEFT, Edge::LEFT]),
|
|
|
- (6, vec![Edge::RIGHT, Edge::RIGHT, Edge::RIGHT, Edge::BOTTOMTOP, Edge::BOTTOMTOP, Edge::RIGHT]),
|
|
|
- (7, vec![Edge::LEFT, Edge::LEFT, Edge::BOTTOMTOP, Edge::LEFT, Edge::RIGHT, Edge::RIGHT]),
|
|
|
+ (1, generate_part(&[(0,1,1), (0,0,1), (1,0,1), (1,0,0), (2,0,0), (1,-1,1)])),
|
|
|
+ (2, generate_part(&[(0,1,1), (0,1,0), (0,0,1), (1,0,1), (1,0,0)])),
|
|
|
+ (3, generate_part(&[(0,1,1), (0,0,1), (1,0,1), (1,0,0), (2,0,0), (2, -1, 0)])),
|
|
|
+ (4, generate_part(&[(0,1,1), (0,1,0), (1,1,0), (1,0,0), (2,0,0), (2,-1,0)])),
|
|
|
+ (5, generate_part(&[(0,1,1), (0,0,1), (1,0,1), (1,-1,1), (1,-1,2), (1,-2,3)])),
|
|
|
+ (6, generate_part(&[(0,1,1), (0,0,1), (1,0,1), (1,-1,1), (2,-1,1), (1,-1,2)])),
|
|
|
+ (7, generate_part(&[(0,1,1), (0,0,1), (1,0,1), (1,0,0), (1,1,0), (2,0,0)])),
|
|
|
])
|
|
|
}
|
|
|
|
|
|
-/*
|
|
|
- * 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
|
|
@@ -168,12 +129,9 @@ pub enum Cell {
|
|
|
impl Cell {
|
|
|
fn is_empty(&self) -> bool {
|
|
|
match self {
|
|
|
- Cell::Empty => {
|
|
|
- return true;
|
|
|
- },
|
|
|
- _ => {
|
|
|
- return false;
|
|
|
- }
|
|
|
+ Cell::Empty => true,
|
|
|
+ Cell::Barrier => false,
|
|
|
+ Cell::Occupied(_) => false,
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -205,148 +163,124 @@ impl std::fmt::Display for Cell {
|
|
|
* size of 16x16.
|
|
|
* Since there are two sides, we store both of them in a tuple.
|
|
|
*/
|
|
|
-pub const MAP_WIDTH : u8 = 16;
|
|
|
-pub const MAP_HEIGHT : u8 = 5;
|
|
|
+pub const MAP_WIDTH : i8 = 16;
|
|
|
+pub const MAP_HEIGHT : i8 = 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
|
|
|
- */
|
|
|
-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 < MAP_WIDTH && y < MAP_HEIGHT
|
|
|
+pub fn in_bound(x: i8, y: i8) -> bool {
|
|
|
+ x >= 0 && y >= 0 && x < MAP_WIDTH && y < MAP_HEIGHT
|
|
|
}
|
|
|
|
|
|
+
|
|
|
/**
|
|
|
- * Moves along an edge and returns a new coordinate pair.
|
|
|
- * If the target would be out of bounds, it returns None.
|
|
|
+ * Call f for each triangle of the part at least. If f returns false, iteration will stop and the
|
|
|
+ * function will return false. Only returns true if all calls of r returned true.
|
|
|
*/
|
|
|
-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 == MAP_WIDTH - 1 {
|
|
|
- return None;
|
|
|
- }
|
|
|
+pub fn for_each_triangle<F>(part: &Part, x: i8, y: i8, rotations: u8, flipped: bool, mut f: F) -> bool
|
|
|
+where F: FnMut(i8, i8) -> bool
|
|
|
+{
|
|
|
+ for coordinate in part {
|
|
|
+ let mut coord : Coord = if flipped { coordinate.flip() } else { coordinate.clone() };
|
|
|
+ coord = coord.rotate(rotations);
|
|
|
|
|
|
- return Some((x + 1, y));
|
|
|
- },
|
|
|
- Edge::BOTTOMTOP => {
|
|
|
- if (is_upright(x, y) && y == MAP_HEIGHT - 1)
|
|
|
- || (!is_upright(x,y) && y == 0) {
|
|
|
- return None;
|
|
|
- }
|
|
|
+ let world_pos = Coord::from_cartesian(x,y) + coord;
|
|
|
+ let (wx, wy) = world_pos.to_cartesian();
|
|
|
|
|
|
- if is_upright(x,y) {
|
|
|
- return Some((x, y + 1));
|
|
|
- } else {
|
|
|
- return Some((x, y - 1));
|
|
|
- }
|
|
|
+ if in_bound(wx, wy) == false {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
+ if f(wx, wy) == false {
|
|
|
+ return false;
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+
|
|
|
+ true
|
|
|
}
|
|
|
|
|
|
|
|
|
/*
|
|
|
* 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: &Map, part: &Part, x: u8, y: u8, rotation: Edge) -> bool {
|
|
|
+pub fn check_part(map: &Map, part: &Part, x: i8, y: i8, rotations: u8, flipped: bool) -> 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 : u8 = x;
|
|
|
- let mut my : u8 = y;
|
|
|
-
|
|
|
- for movement in part {
|
|
|
- let rotated_movement = movement.rotate(&rotation, is_upright(x, y));
|
|
|
- let result = move_along_edge(mx, my, rotated_movement);
|
|
|
-
|
|
|
- if result.is_none() {
|
|
|
+ //println!("Testing {},{},{},{}", x, y, rotation.to_int(), flipped);
|
|
|
+ let success = for_each_triangle(part, x, y, rotations, flipped, |x, y| {
|
|
|
+ if !in_bound(x,y) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- (mx, my) = result.unwrap();
|
|
|
-
|
|
|
- if !map[my as usize][mx as usize].is_empty() || !in_bound(mx, my) {
|
|
|
+ let prev = map[y as usize][x as usize];
|
|
|
+ if !(prev.is_empty()) {
|
|
|
return false;
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- return true;
|
|
|
-}
|
|
|
+ //println!("Check for {},{} passed", x, y);
|
|
|
|
|
|
-/* Once we have found a valid position with `check_part`, we can place the part on the map.
|
|
|
- */
|
|
|
-pub 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();
|
|
|
- let prev = map[my as usize][mx as usize];
|
|
|
- assert!(prev == Cell::Empty || prev == Cell::Occupied(id));
|
|
|
- map[my as usize][mx as usize] = Cell::Occupied(id);
|
|
|
+ return true;
|
|
|
+ });
|
|
|
+ if success == true {
|
|
|
+ //println!("Accepted.");
|
|
|
+ return true;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
|
|
|
-/* 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;
|
|
|
+///* Once we have found a valid position with `check_part`, we can place the part on the map.
|
|
|
+// */
|
|
|
+pub fn place_part(map: &mut Map, id: PartID, part: &Part, x: i8, y: i8, rotations: u8, flipped: bool) {
|
|
|
+ //println!("Placing {},{},{},{}", x, y, rotation.to_int(), flipped);
|
|
|
+ for_each_triangle(part, x, y, rotations, flipped, |x, y| {
|
|
|
+ if !in_bound(x,y) {
|
|
|
+ panic!();
|
|
|
+ }
|
|
|
+ //println!("placing {},{}", x, y);
|
|
|
|
|
|
- let mut mx : u8 = x;
|
|
|
- let mut my : u8 = y;
|
|
|
+ let prev = map[y as usize][x as usize];
|
|
|
+ if let Cell::Occupied(i) = prev {
|
|
|
+ assert_eq!(id, i);
|
|
|
+ } else {
|
|
|
+ assert!(prev.is_empty());
|
|
|
+ }
|
|
|
|
|
|
- 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;
|
|
|
- }
|
|
|
+ map[y as usize][x as usize] = Cell::Occupied(id);
|
|
|
|
|
|
+ return true;
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
-pub fn solve(map: &mut Map, parts: &[(PartID, &Part)]) -> Option<()>{
|
|
|
+///* 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, flipped: bool) {
|
|
|
+// for_each_triangle(part, x, y, rotation, flipped, |x, y| {
|
|
|
+// assert!(in_bound(x, y));
|
|
|
+//
|
|
|
+// map[y as usize][x as usize] = Cell::Empty;
|
|
|
+// true
|
|
|
+// });
|
|
|
+//}
|
|
|
+//
|
|
|
+pub fn solve(map: Map, parts: &[(PartID, &Part)]) -> Option<()>{
|
|
|
if parts.len() == 0 {
|
|
|
return Some(());
|
|
|
}
|
|
|
|
|
|
let (id, part) = parts[0];
|
|
|
-
|
|
|
+ println!("{} parts remaining", parts.len());
|
|
|
|
|
|
// TODO: restrict to list of non-barrier position to tighten the for loops
|
|
|
for x in 0..MAP_WIDTH {
|
|
@@ -355,18 +289,21 @@ pub fn solve(map: &mut Map, parts: &[(PartID, &Part)]) -> Option<()>{
|
|
|
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);
|
|
|
+ for rotation in [0, 1, 2] {
|
|
|
+ for flipped in [true, false] {
|
|
|
+ println!("Testing rotation {} and flipped={}", rotation, flipped);
|
|
|
+ let valid_position = check_part(&map, &part, x, y, rotation, flipped);
|
|
|
+
|
|
|
+ if valid_position {
|
|
|
+ // Place our part and try the others
|
|
|
+ let mut map_copy = map.clone();
|
|
|
+ place_part(&mut map_copy, id, &part, x, y, rotation, flipped);
|
|
|
+ print_map(&map_copy);
|
|
|
+ println!("\n");
|
|
|
+ if let Some(()) = solve(map_copy, &parts[1..]) {
|
|
|
+ // We have found a valid solution we can pass back!
|
|
|
+ return Some(());
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -376,53 +313,102 @@ pub fn solve(map: &mut Map, parts: &[(PartID, &Part)]) -> Option<()>{
|
|
|
None
|
|
|
}
|
|
|
|
|
|
+pub fn foreground_color(idx: u8) {
|
|
|
+ print!("\x1B[38;5;{}m", idx);
|
|
|
+}
|
|
|
+
|
|
|
+pub fn background_color(idx: u8) {
|
|
|
+ print!("\x1B[48;5;{}m", idx);
|
|
|
+}
|
|
|
+
|
|
|
+fn switch_color_to_cell(cell: Cell) {
|
|
|
+ match cell {
|
|
|
+ Cell::Barrier => foreground_color(0),
|
|
|
+ Cell::Empty => foreground_color(15),
|
|
|
+ Cell::Occupied(1) => foreground_color(94),
|
|
|
+ Cell::Occupied(2) => foreground_color(89),
|
|
|
+ Cell::Occupied(3) => foreground_color(202),
|
|
|
+ Cell::Occupied(4) => foreground_color(220),
|
|
|
+ Cell::Occupied(5) => foreground_color(22),
|
|
|
+ Cell::Occupied(6) => foreground_color(196),
|
|
|
+ Cell::Occupied(7) => foreground_color(27),
|
|
|
+ _ => {}
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+//
|
|
|
+// |O###O###O###
|
|
|
+// OOO#OOO#OOO#
|
|
|
+// ###O###O###O
|
|
|
+// |#OOO#OOO#OOO
|
|
|
+pub fn print_map(m: &Map) {
|
|
|
+ for y in 0..MAP_HEIGHT {
|
|
|
+ if y % 2 == 0 {
|
|
|
+ switch_color_to_cell(Cell::Barrier);
|
|
|
+ print!("█");
|
|
|
+ }
|
|
|
+ for x in 0..MAP_WIDTH {
|
|
|
+ switch_color_to_cell(m[y as usize][x as usize]);
|
|
|
+
|
|
|
+ if (x + y) % 2 == 0 {
|
|
|
+ print!("o");
|
|
|
+ } else {
|
|
|
+ print!("###");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ switch_color_to_cell(Cell::Barrier);
|
|
|
+ println!("");
|
|
|
+
|
|
|
+ if y % 2 == 1 {
|
|
|
+ switch_color_to_cell(Cell::Barrier);
|
|
|
+ print!("█");
|
|
|
+ }
|
|
|
+ for x in 0..MAP_WIDTH {
|
|
|
+ switch_color_to_cell(m[y as usize][x as usize]);
|
|
|
+ if (x + y) % 2 == 1 {
|
|
|
+ print!("#");
|
|
|
+ } else {
|
|
|
+ print!("ooo");
|
|
|
+ }
|
|
|
+ }
|
|
|
+ println!("");
|
|
|
+
|
|
|
+ }
|
|
|
+ foreground_color(15);
|
|
|
+ background_color(0);
|
|
|
+}
|
|
|
+
|
|
|
#[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));
|
|
|
- }
|
|
|
+ fn test_from_cartesian_coords() {
|
|
|
+ assert_eq!(Coord::from(0, 1, 1), Coord::from_cartesian(0,0));
|
|
|
+ assert_eq!(Coord::from(5, -1, -2), Coord::from_cartesian(8, 2));
|
|
|
+ assert_eq!(Coord::from(2, -1, 0), Coord::from_cartesian(3, 2));
|
|
|
+ assert_eq!(Coord::from(3,0,-2), Coord::from_cartesian(6, 1));
|
|
|
}
|
|
|
|
|
|
#[test]
|
|
|
- fn test_edge_movement() {
|
|
|
- let (x1, y1) = move_along_edge(0, 0, Edge::RIGHT).unwrap();
|
|
|
- assert_eq!((1, 0), (x1, y1));
|
|
|
+ fn test_to_cartesian_coords() {
|
|
|
+ assert_eq!((0,0), Coord::from(0,1,1).to_cartesian());
|
|
|
+ assert_eq!((1,0), Coord::from(0,1,0).to_cartesian());
|
|
|
+ assert_eq!((2,0), Coord::from(1,1,0).to_cartesian());
|
|
|
+ assert_eq!((0,1), Coord::from(0,0,1).to_cartesian());
|
|
|
|
|
|
- assert!(move_along_edge(15, 0, Edge::RIGHT).is_none());
|
|
|
- assert!(move_along_edge(0, 1, Edge::LEFT).is_none());
|
|
|
+ assert_eq!((0,-1), Coord::from(-1, 2, 0).to_cartesian());
|
|
|
|
|
|
- let (x2, y2) = move_along_edge(2, 1, Edge::BOTTOMTOP).unwrap();
|
|
|
- assert_eq!((2, 0), (x2, y2));
|
|
|
-
|
|
|
- }
|
|
|
+ for x in 0..63 {
|
|
|
+ for y in 0..63 {
|
|
|
+ assert_eq!((x,y), Coord::from_cartesian(x,y).to_cartesian());
|
|
|
|
|
|
- #[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;
|
|
@@ -440,12 +426,12 @@ mod test {
|
|
|
let brown_part = parts.get(&1).unwrap();
|
|
|
|
|
|
|
|
|
- assert!(check_part(&map, &brown_part, 4, 2, Edge::BOTTOMTOP));
|
|
|
+ assert!(check_part(&map, &brown_part, 4, 2, Edge::BOTTOMTOP, false));
|
|
|
let mut called : usize = 0;
|
|
|
for x in 0..MAP_WIDTH {
|
|
|
for y in 0..MAP_HEIGHT {
|
|
|
for rotation in [Edge::LEFT, Edge::RIGHT, Edge::BOTTOMTOP] {
|
|
|
- if check_part(&map, &brown_part, x, y, rotation) {
|
|
|
+ if check_part(&map, &brown_part, x, y, rotation, false) {
|
|
|
assert_eq!(Edge::BOTTOMTOP, rotation);
|
|
|
assert_eq!((4, 2), (x, y));
|
|
|
called += 1;
|
|
@@ -457,6 +443,29 @@ mod test {
|
|
|
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ #[test]
|
|
|
+ fn test_erasing() {
|
|
|
+ let E = Cell::Empty;
|
|
|
+ let map : Map = [
|
|
|
+ [E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E],
|
|
|
+ [E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E],
|
|
|
+ [E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E],
|
|
|
+ [E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E],
|
|
|
+ [E, E, E, E, E, E, E, E, E, E, E, E, E, E, E, E],
|
|
|
+ ];
|
|
|
+ let parts = generate_parts();
|
|
|
+ let mut cmap = map.clone();
|
|
|
+
|
|
|
+ place_part(&mut cmap, 1, &parts[&1], 4, 4, Edge::BOTTOMTOP, true);
|
|
|
+ assert_ne!(map, cmap);
|
|
|
+ erase_part(&mut cmap, &parts[&1], 4, 4, Edge::BOTTOMTOP, true);
|
|
|
+
|
|
|
+ assert_eq!(map, cmap);
|
|
|
+
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
#[test]
|
|
|
fn halting_problem() {
|
|
|
let B = Cell::Barrier;
|
|
@@ -490,4 +499,5 @@ mod test {
|
|
|
|
|
|
|
|
|
}
|
|
|
+*/
|
|
|
}
|