lib.rs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. use std::collections::HashMap;
  2. use std::io::Write;
  3. #[derive(Clone,Debug,PartialEq)]
  4. pub struct Coord {
  5. pub a: i8,
  6. pub b: i8,
  7. pub c: i8,
  8. }
  9. impl Coord {
  10. fn from(a : i8, b: i8, c: i8) -> Coord {
  11. Coord {
  12. a: a,
  13. b: b,
  14. c: c
  15. }
  16. }
  17. fn from_ab(a : i8, b : i8, upright: bool) -> Coord {
  18. Coord {
  19. a: a,
  20. b: b,
  21. c: if upright { 2 - a - b} else { 1 - a - b },
  22. }
  23. }
  24. /**
  25. * Rotates coordinate around the origin by 120°
  26. */
  27. fn rotate(&self, rotations: u8) -> Coord {
  28. match rotations % 3 {
  29. 0 => self.clone(),
  30. 1 => Coord::from(self.b, self.c, self.a),
  31. _ => Coord::from(self.c, self.a, self.b),
  32. }
  33. }
  34. pub fn rotate60(&self) -> Coord {
  35. Coord {
  36. a: 1 - self.c,
  37. b: 1 - self.a,
  38. c: 1 - self.b
  39. }
  40. }
  41. /**
  42. * Flip coordinate at the y-axis
  43. */
  44. fn flip(&self) -> Coord {
  45. Coord::from(self.c, self.b, self.a)
  46. }
  47. fn upright(&self) -> bool {
  48. self.a + self.b + self.c == 2
  49. }
  50. pub fn to_cartesian(&self) -> (i8, i8) {
  51. (self.a - self.c + 1, 1 - self.b)
  52. }
  53. pub fn translate_x(&self, x: i8) -> Coord {
  54. Coord::from_ab(self.a + x, self.b, self.upright())
  55. }
  56. pub fn translate_y(&self, y: i8, origin_upright: bool) -> Coord {
  57. if origin_upright {
  58. Coord::from_ab(self.a + (y + 1) / 2, self.b - y, self.upright())
  59. } else {
  60. Coord::from_ab(self.a + y / 2, self.b - y, self.upright())
  61. }
  62. }
  63. }
  64. impl std::ops::Add<Coord> for Coord {
  65. type Output = Coord;
  66. fn add(self, _rhs: Coord) -> Coord {
  67. return Coord {
  68. a: self.a + _rhs.a,
  69. b: self.b + _rhs.b,
  70. c: self.c + _rhs.c
  71. }
  72. }
  73. }
  74. /* Numbers 1..7 are used to identify a given part */
  75. pub type PartID = u8;
  76. pub type Part = Vec<Coord>;
  77. /** Allow part definition with tuples for brevity
  78. */
  79. fn generate_part(arr: &[(i8, i8, i8)]) -> Part {
  80. let v: Vec<Coord> = arr.iter()
  81. .map(|(a,b,c)| Coord::from(*a,*b,*c))
  82. .collect();
  83. return v;
  84. }
  85. pub fn generate_parts() -> Vec<(PartID, Part)> {
  86. vec![
  87. (1, generate_part(&[(0,1,1), (0,0,1), (1,0,1), (1,0,0), (2,0,0), (1,-1,1)])),
  88. (2, generate_part(&[(0,1,1), (0,1,0), (0,0,1), (1,0,1), (1,0,0), (2,0,0)])),
  89. (3, generate_part(&[(0,1,1), (0,0,1), (1,0,1), (1,0,0), (2,0,0), (2, -1, 0)])),
  90. (4, generate_part(&[(0,1,1), (0,1,0), (1,1,0), (1,0,0), (2,0,0), (2,-1,0)])),
  91. (5, generate_part(&[(0,1,1), (0,1,0), (1,1,0), (1,1,-1), (0,0,1), (0,0,2)])),
  92. (6, generate_part(&[(0,1,1), (0,0,1), (1,0,1), (1,-1,1), (2,-1,1), (1,-1,2)])),
  93. (7, generate_part(&[(0,1,1), (0,0,1), (1,0,1), (1,0,0), (1,1,0), (2,0,0)])),
  94. ]
  95. }
  96. /*
  97. * For in-memory representation however, we simply use a two-dimensional array of cells.
  98. * Each cell can be a Barrier (meaning no part may be placed on the cell), Empty (meaning at the
  99. * current time, it is not occupied by a part) or Occupied.
  100. */
  101. #[derive(PartialEq, Debug, Copy, Clone)]
  102. pub enum Cell {
  103. Barrier,
  104. Empty,
  105. Occupied(PartID)
  106. }
  107. impl Cell {
  108. fn is_empty(&self) -> bool {
  109. match self {
  110. Cell::Empty => true,
  111. Cell::Barrier => false,
  112. Cell::Occupied(_) => false,
  113. }
  114. }
  115. }
  116. impl std::fmt::Display for Cell {
  117. fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
  118. match self {
  119. Cell::Barrier => write!(f, "B"),
  120. Cell::Empty => write!(f, " "),
  121. Cell::Occupied(id) => write!(f, "{}", id),
  122. }
  123. }
  124. }
  125. /*
  126. * A triangular grid looks like this:
  127. * __ __
  128. * /\ /\ /\
  129. * /__\/__\/__\
  130. * \ /\ /\ /
  131. * \/__\/__\/
  132. * /\ /\ /\
  133. * /__\/__\/__\
  134. *
  135. * We require by definition that the left upper-most triangle is upright.
  136. * A map is then represented by a two-dimensional row-major (meaning the outer array represents the
  137. * different rows) two-dimensional array with a maximum
  138. * size of 16x16.
  139. * Since there are two sides, we store both of them in a tuple.
  140. */
  141. pub const MAP_WIDTH : i8 = 16;
  142. pub const MAP_HEIGHT : i8 = 5;
  143. pub type Map = [[Cell; MAP_WIDTH as usize]; MAP_HEIGHT as usize];
  144. /**
  145. * Helper function that returns true if the coordinates are within map bounds
  146. */
  147. pub fn in_bound(x: i8, y: i8) -> bool {
  148. x >= 0 && y >= 0 && x < MAP_WIDTH && y < MAP_HEIGHT
  149. }
  150. /**
  151. * Call f for each triangle of the part at least. If f returns false, iteration will stop and the
  152. * function will return false. Only returns true if all calls of r returned true.
  153. */
  154. pub fn for_each_triangle<F>(part: &Part, x: i8, y: i8, rotations: u8, flipped: bool, mut f: F) -> bool
  155. where F: FnMut(i8, i8) -> bool
  156. {
  157. for coordinate in part {
  158. let mut coord : Coord = if flipped { coordinate.flip() } else { coordinate.clone() };
  159. coord = coord.rotate(rotations);
  160. let origin_upright = (x + y) % 2 == 0;
  161. if !origin_upright {
  162. coord = coord.rotate60();
  163. }
  164. coord = coord.translate_y(y, origin_upright).translate_x(x / 2);
  165. let (wx, wy) = coord.to_cartesian();
  166. if in_bound(wx, wy) == false {
  167. return false;
  168. }
  169. if f(wx, wy) == false {
  170. return false;
  171. }
  172. }
  173. true
  174. }
  175. /*
  176. * Parts can be inverted (flipped on the other side) and also rotated.
  177. * On a triangular grid, three rotations are possible.
  178. *
  179. * The task of our core logic function below is to figure out whether a given part can fit on a
  180. * specified location on the map.
  181. *
  182. * Returns true if the part can fit at the specified location on the map.
  183. */
  184. pub fn check_part(map: &Map, part: &Part, x: i8, y: i8, rotations: u8, flipped: bool) -> bool {
  185. /* Make sure the start triangle can actually be placed */
  186. debug_assert!(in_bound(x, y));
  187. //println!("Testing {},{},{},{}", x, y, rotation.to_int(), flipped);
  188. for_each_triangle(part, x, y, rotations, flipped, |x, y| {
  189. if !in_bound(x,y) {
  190. return false;
  191. }
  192. let prev = map[y as usize][x as usize];
  193. if !(prev.is_empty()) {
  194. return false;
  195. }
  196. return true;
  197. })
  198. }
  199. ///* Once we have found a valid position with `check_part`, we can place the part on the map.
  200. // */
  201. pub fn place_part(map: &mut Map, id: PartID, part: &Part, x: i8, y: i8, rotations: u8, flipped: bool) {
  202. //println!("Placing {},{},{},{}", x, y, rotation.to_int(), flipped);
  203. for_each_triangle(part, x, y, rotations, flipped, |x, y| {
  204. if !in_bound(x,y) {
  205. return false;
  206. }
  207. map[y as usize][x as usize] = Cell::Occupied(id);
  208. return true;
  209. });
  210. }
  211. /* If a part is not placed right, we might need to remove it from the map again
  212. */
  213. fn erase_part(map: &mut Map, part: &Part, x: i8, y: i8, rotation: u8, flipped: bool) {
  214. for_each_triangle(part, x, y, rotation, flipped, |x, y| {
  215. debug_assert!(in_bound(x, y));
  216. map[y as usize][x as usize] = Cell::Empty;
  217. true
  218. });
  219. }
  220. pub fn solve(map: &mut Map, parts: &[(PartID, Part)]) -> Option<()>
  221. {
  222. if parts.len() == 0 {
  223. return Some(());
  224. }
  225. let (id, part) = &parts[0];
  226. // TODO: restrict to list of non-barrier position to tighten the for loops
  227. for y in 0..MAP_HEIGHT {
  228. for x in 0..MAP_WIDTH {
  229. if !map[y as usize][x as usize].is_empty() {
  230. continue;
  231. }
  232. for rotation in [0, 1, 2] {
  233. for flipped in [false, true] {
  234. let valid_position = check_part(&map, &part, x, y, rotation, flipped);
  235. if valid_position {
  236. // Place our part and try the others
  237. place_part(map, *id, &part, x, y, rotation, flipped);
  238. //t.as_ref().map(|&&&mut Term| print_map(term, map));
  239. if let Some(()) = solve(map, &parts[1..]) {
  240. // We have found a valid solution we can pass back!
  241. return Some(());
  242. } else {
  243. erase_part(map, &part, x, y, rotation, flipped);
  244. }
  245. }
  246. }
  247. }
  248. }
  249. }
  250. None
  251. }
  252. pub fn foreground_color(idx: u8) {
  253. print!("\x1B[38;5;{}m", idx);
  254. }
  255. pub fn background_color(idx: u8) {
  256. print!("\x1B[48;5;{}m", idx);
  257. }
  258. fn color_for_cell(cell: Cell) -> console::Color {
  259. match cell {
  260. Cell::Barrier => console::Color::Color256(0),
  261. Cell::Empty => console::Color::Color256(15),
  262. Cell::Occupied(1) => console::Color::Color256(94),
  263. Cell::Occupied(2) => console::Color::Color256(89),
  264. Cell::Occupied(3) => console::Color::Color256(202),
  265. Cell::Occupied(4) => console::Color::Color256(220),
  266. Cell::Occupied(5) => console::Color::Color256(22),
  267. Cell::Occupied(6) => console::Color::Color256(196),
  268. Cell::Occupied(7) => console::Color::Color256(27),
  269. _ => console::Color::Black
  270. }
  271. }
  272. // |O###O###O###
  273. // OOO#OOO#OOO#
  274. // ###O###O###O
  275. // |#OOO#OOO#OOO
  276. use console::style;
  277. pub fn print_map(t: &mut console::Term, m: &Map) {
  278. for y in 0..MAP_HEIGHT {
  279. if y % 2 == 0 {
  280. write!(t, "{}", style("█").fg(color_for_cell(Cell::Barrier)));
  281. }
  282. for x in 0..MAP_WIDTH {
  283. let c = if (x + y) % 2 == 0 {
  284. "∧"
  285. } else {
  286. "\\#/"
  287. };
  288. write!(t, "{}", style(c).fg(color_for_cell(m[y as usize][x as usize])));
  289. }
  290. write!(t, "{}", style("\n").fg(color_for_cell(Cell::Barrier)));
  291. if y % 2 == 1 {
  292. write!(t, "{}", style("█").fg(color_for_cell(Cell::Barrier)));
  293. }
  294. for x in 0..MAP_WIDTH {
  295. let c = if (x + y) % 2 == 1 {
  296. "v"
  297. } else {
  298. "/_\\"
  299. };
  300. write!(t, "{}", style(c).fg(color_for_cell(m[y as usize][x as usize])));
  301. }
  302. write!(t, "{}", style("\n").fg(color_for_cell(Cell::Barrier)));
  303. }
  304. }
  305. #[cfg(test)]
  306. mod test {
  307. use super::*;
  308. #[test]
  309. fn test_to_cartesian_coords() {
  310. assert_eq!((0,0), Coord::from(0,1,1).to_cartesian());
  311. assert_eq!((1,0), Coord::from(0,1,0).to_cartesian());
  312. assert_eq!((2,0), Coord::from(1,1,0).to_cartesian());
  313. assert_eq!((0,1), Coord::from(0,0,1).to_cartesian());
  314. assert_eq!((0,-1), Coord::from(-1, 2, 0).to_cartesian());
  315. assert_eq!((2,1), Coord::from(1,0,0).to_cartesian());
  316. assert_eq!((3,1), Coord::from(2,0,0).to_cartesian());
  317. }
  318. #[test]
  319. fn test_translation() {
  320. assert_eq!(Coord::from(1,1,0), Coord::from(-1,1,2).translate_x(2));
  321. assert_eq!(Coord::from(-1,0,3), Coord::from(2,0,0).translate_x(-3));
  322. assert_eq!(Coord::from(0,1,1), Coord::from(-1,3,0).translate_y(2, true));
  323. assert_eq!(Coord::from(3,0,-1), Coord::from(3,-1,0).translate_y(-1, true));
  324. }
  325. }