From 2aca99aa2b4afca933de68bbe0bd74c118f5aeab Mon Sep 17 00:00:00 2001 From: Shadowfacts Date: Mon, 18 Dec 2023 16:30:48 -0500 Subject: [PATCH] Day 18 --- input/day18.txt | 634 +++++++++++++++++++++++++++++++++++++++++++++++ src/day18.rs | 646 ++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 3 +- 3 files changed, 1282 insertions(+), 1 deletion(-) create mode 100644 input/day18.txt create mode 100644 src/day18.rs diff --git a/input/day18.txt b/input/day18.txt new file mode 100644 index 0000000..349f123 --- /dev/null +++ b/input/day18.txt @@ -0,0 +1,634 @@ +R 4 (#9505a2) +U 3 (#984f41) +R 5 (#763772) +U 4 (#5e0883) +R 8 (#6dff52) +D 4 (#5e0881) +R 9 (#220312) +U 7 (#984f43) +L 8 (#1eacc2) +U 8 (#0f1673) +L 2 (#7cab42) +U 9 (#7dae23) +L 5 (#0d4642) +D 13 (#2be373) +L 4 (#225752) +D 4 (#3f8d43) +L 7 (#2930b2) +U 10 (#67f943) +L 6 (#25a0a0) +D 12 (#69b2d3) +L 3 (#8be9f0) +U 4 (#207593) +L 3 (#b18a92) +U 5 (#63c483) +L 9 (#613142) +U 2 (#a6aff3) +L 4 (#7a5082) +U 4 (#2beb03) +R 11 (#050d70) +U 2 (#665ed3) +R 2 (#5422f2) +U 6 (#264a73) +L 7 (#5422f0) +U 3 (#47fcc3) +L 6 (#050d72) +U 8 (#0d1f63) +L 7 (#2828e2) +U 5 (#440971) +L 6 (#547822) +D 8 (#9961b1) +L 5 (#547820) +D 6 (#304541) +L 6 (#8bd7f2) +D 3 (#4eb533) +R 11 (#5c4602) +D 4 (#096b13) +L 11 (#260da0) +D 4 (#400e13) +L 8 (#5f2bb0) +D 5 (#760133) +L 7 (#6a72f0) +D 9 (#33bdc3) +L 3 (#80d940) +U 4 (#155633) +L 4 (#8f4e82) +U 8 (#85bc63) +L 3 (#26b562) +U 4 (#66fa93) +L 7 (#b26d12) +U 11 (#67b8f3) +L 2 (#2bb2f0) +U 3 (#b531a3) +R 4 (#4013d0) +U 6 (#2bd863) +R 2 (#4a6a80) +U 12 (#0f7c13) +R 5 (#6cc370) +U 5 (#109003) +R 4 (#457c40) +U 9 (#935793) +R 4 (#1edea0) +U 4 (#5bc2b3) +R 3 (#4e6750) +U 6 (#947b93) +R 6 (#7fafb0) +U 5 (#8f4c91) +R 2 (#0fa3b0) +U 6 (#21c311) +L 12 (#485f20) +U 3 (#3f2ea1) +L 3 (#8688c0) +U 4 (#0175f3) +R 4 (#7283b0) +U 6 (#64da33) +R 7 (#23e570) +U 7 (#869213) +R 4 (#12f200) +D 8 (#42f3b1) +R 5 (#854f90) +D 3 (#34eaf1) +R 9 (#854f92) +D 6 (#695841) +R 12 (#05b840) +D 6 (#0a3561) +L 3 (#901430) +D 3 (#5ece03) +L 8 (#5f85b0) +D 8 (#3856f3) +R 9 (#4e6aa0) +D 9 (#1cba43) +L 9 (#4b8f20) +D 5 (#730143) +R 11 (#726f40) +D 7 (#2bc223) +R 6 (#2617e0) +U 6 (#2c3d13) +R 4 (#6482c0) +U 5 (#2d55f3) +R 4 (#6e4650) +U 8 (#2d55f1) +R 6 (#0138d0) +U 2 (#243eb3) +R 7 (#17bd10) +U 5 (#7c0aa3) +R 4 (#31a982) +U 7 (#5ae133) +R 4 (#b5ad22) +U 9 (#6a6203) +R 5 (#046852) +U 4 (#12d6b3) +R 6 (#0d09e2) +U 7 (#526203) +R 11 (#3f8962) +U 5 (#4245e3) +L 4 (#699702) +U 6 (#17dce3) +L 11 (#44f9a2) +U 3 (#0c7143) +L 4 (#09aec2) +D 4 (#746e03) +L 6 (#49edf0) +U 4 (#839f83) +L 5 (#5e9ce0) +U 5 (#839f81) +R 9 (#5c47d0) +U 3 (#17f3f3) +R 2 (#3180a0) +U 11 (#7a7973) +R 3 (#7d7750) +U 7 (#9ed523) +R 3 (#6b5450) +D 9 (#523bb3) +R 5 (#9a1b80) +D 3 (#4323e3) +R 3 (#299430) +U 2 (#97c0e3) +R 4 (#6e8450) +U 10 (#448eb1) +R 5 (#63bda2) +U 3 (#702a61) +R 3 (#2c2690) +U 12 (#7379e1) +R 4 (#2c2692) +U 7 (#0c4041) +R 3 (#63bda0) +U 7 (#45cc21) +L 4 (#4cd2e0) +U 4 (#2ae8f1) +L 7 (#738540) +D 11 (#a34001) +L 6 (#738542) +D 2 (#16d291) +L 4 (#05ba40) +D 5 (#8872a1) +L 9 (#186db0) +D 3 (#8e7ce1) +L 5 (#9c6d10) +U 6 (#8e7ce3) +L 9 (#56a350) +U 2 (#9df721) +L 6 (#4e9042) +U 3 (#2659a1) +R 4 (#3bd022) +U 5 (#2659a3) +R 11 (#811db2) +U 5 (#ad2b91) +L 9 (#05ba42) +U 5 (#72a9c1) +R 8 (#211510) +U 5 (#13cb31) +R 6 (#6c2480) +D 7 (#13cb33) +R 6 (#2f0f60) +U 7 (#4683f1) +R 4 (#0054d0) +U 4 (#8bf741) +L 13 (#7fb5c0) +U 2 (#149691) +L 3 (#196d60) +U 6 (#5fef73) +R 3 (#0cf060) +U 5 (#52b473) +R 6 (#022db0) +U 9 (#346de3) +R 6 (#1f0e40) +U 5 (#251e63) +R 10 (#777a02) +U 4 (#6f8d93) +R 6 (#777a00) +U 12 (#3cdb63) +L 3 (#426ac2) +U 8 (#1cd103) +L 10 (#83a360) +U 5 (#07e093) +L 3 (#2a5f40) +U 6 (#22d423) +R 2 (#448862) +U 6 (#8da513) +R 7 (#697a42) +U 2 (#6478e3) +R 3 (#426ac0) +U 4 (#8c62b3) +R 11 (#908510) +U 2 (#625113) +R 2 (#7dcc10) +U 4 (#315be3) +L 10 (#242e40) +U 2 (#352c83) +L 3 (#68e2f0) +U 4 (#658603) +R 3 (#4aa570) +U 4 (#387113) +L 3 (#3616a0) +U 4 (#3bcef3) +L 6 (#3616a2) +U 12 (#68c1b3) +L 2 (#4aa572) +U 8 (#028b23) +L 10 (#4126a0) +U 3 (#14e773) +R 10 (#763032) +U 2 (#86bf23) +R 4 (#3f43a2) +U 5 (#468f11) +R 7 (#0ec812) +U 3 (#99ca31) +L 7 (#4bc6a0) +U 8 (#67a621) +L 3 (#4bc6a2) +U 3 (#53c3a1) +L 10 (#6d0ad0) +U 6 (#218a01) +R 12 (#6d0ad2) +U 3 (#4e8fe1) +R 8 (#52f3a2) +U 7 (#2ae183) +R 8 (#420262) +U 3 (#a3eba3) +R 3 (#6f0962) +U 11 (#9018d3) +R 6 (#444152) +U 7 (#acf6f3) +R 5 (#4caec2) +D 10 (#47f963) +R 3 (#2094f2) +U 13 (#02f073) +R 3 (#2cb412) +U 7 (#29d5b3) +R 5 (#999602) +D 2 (#52abf3) +R 4 (#7fbe72) +D 9 (#5210d3) +R 4 (#b456f2) +D 9 (#378693) +R 5 (#b456f0) +U 10 (#58bea3) +R 5 (#111ef2) +D 7 (#1eb663) +R 3 (#873e92) +D 2 (#02cbb3) +R 3 (#8756c2) +D 12 (#682d83) +R 3 (#64e760) +U 7 (#6478a3) +R 8 (#7a9d20) +D 4 (#6ec453) +R 8 (#8aec52) +D 3 (#858fa3) +R 5 (#8aec50) +D 6 (#5fe253) +L 7 (#804c10) +D 4 (#5721c1) +L 3 (#5a26e0) +D 5 (#344fc1) +L 2 (#260642) +D 3 (#947521) +L 10 (#260640) +D 4 (#10be21) +L 6 (#28aba0) +U 7 (#239181) +R 4 (#a84ed0) +U 5 (#594d81) +L 4 (#523490) +U 4 (#5d20e3) +L 3 (#621ce0) +D 3 (#5d20e1) +L 9 (#45f630) +U 5 (#07f8b1) +L 2 (#9bceb0) +U 4 (#541ae1) +L 9 (#56a000) +D 7 (#5cdf31) +L 9 (#2f94d0) +D 2 (#1705b3) +L 7 (#7f1d70) +D 7 (#99f463) +R 6 (#8a9190) +D 5 (#9f9111) +R 5 (#02d950) +D 4 (#1b0841) +R 7 (#199fc0) +D 2 (#739481) +R 8 (#199fc2) +U 4 (#4b47c1) +R 9 (#7014b0) +D 4 (#24c0a1) +R 15 (#30ae00) +D 4 (#8e1d01) +L 8 (#85f080) +D 3 (#2f10a1) +L 3 (#46b8e2) +D 4 (#830d61) +L 12 (#04b302) +D 2 (#9d5ae1) +L 3 (#018932) +D 5 (#04ada3) +L 6 (#2fe952) +D 5 (#3ff843) +L 7 (#8e4dd2) +D 6 (#78d433) +L 12 (#127612) +D 6 (#3f20f1) +R 12 (#994c62) +D 4 (#5cb071) +R 3 (#994c60) +D 3 (#21a8b1) +R 8 (#533c92) +D 3 (#58d461) +R 7 (#316010) +D 5 (#3772e1) +R 4 (#3efac2) +D 3 (#185d41) +R 10 (#2a5450) +D 4 (#66b841) +R 3 (#2a5452) +D 7 (#4c84c1) +R 4 (#3efac0) +D 3 (#0544d1) +R 4 (#959a70) +D 6 (#07b843) +R 2 (#789922) +D 7 (#97a0f3) +R 5 (#789920) +D 3 (#68f8c3) +R 3 (#632b70) +D 7 (#0628c1) +R 3 (#46b8e0) +D 9 (#0a5911) +R 3 (#0caab0) +U 6 (#247553) +R 3 (#451e30) +U 9 (#53d613) +R 5 (#85d060) +U 7 (#1e3623) +R 5 (#044a50) +D 9 (#558213) +R 7 (#481c60) +D 5 (#49bbd3) +L 7 (#481c62) +D 6 (#830fb3) +R 2 (#6b1ef0) +D 2 (#0e5861) +R 9 (#070aa0) +D 6 (#693991) +R 4 (#070aa2) +U 4 (#4c5121) +R 9 (#205ad0) +U 5 (#0c67d1) +R 7 (#062fc0) +U 6 (#16c763) +R 8 (#669720) +U 5 (#4e9843) +R 6 (#05bc32) +U 4 (#9513d3) +R 3 (#05bc30) +U 2 (#928d03) +R 4 (#a01410) +D 11 (#7b2af1) +R 6 (#4c41d0) +D 13 (#52bb51) +R 3 (#9001e0) +D 2 (#0a18d1) +R 4 (#968322) +D 5 (#509641) +L 13 (#1abda2) +D 6 (#540201) +L 9 (#2b02f2) +D 2 (#106921) +L 5 (#3a2dc0) +D 6 (#319fd3) +R 9 (#56ac20) +D 3 (#319fd1) +R 5 (#400070) +D 7 (#735a51) +R 10 (#20c2b2) +D 2 (#a17341) +R 3 (#005a72) +D 5 (#224c51) +L 7 (#022422) +D 3 (#3a9061) +L 9 (#402e82) +D 8 (#4d0491) +L 5 (#6e9c32) +D 2 (#0b58d1) +L 15 (#2bb0f0) +U 4 (#8a1051) +L 9 (#2bb0f2) +U 5 (#84c961) +L 3 (#39dea2) +U 13 (#75c911) +L 4 (#3cb3c2) +D 4 (#2466e1) +L 8 (#3cb3c0) +D 8 (#685971) +L 4 (#6adcc2) +D 6 (#1e7673) +L 5 (#526972) +D 4 (#1b2ad3) +L 7 (#853862) +D 9 (#696ea3) +R 10 (#0d3450) +D 4 (#4633b1) +R 3 (#7c6780) +D 9 (#4633b3) +R 5 (#4e0600) +D 8 (#5f7983) +R 7 (#8861d2) +U 5 (#79c723) +R 5 (#4b16b2) +U 4 (#b58831) +R 3 (#06a1b2) +U 9 (#59fd71) +R 7 (#6fa5e2) +U 6 (#873f01) +R 3 (#6fa5e0) +U 3 (#61b3c1) +R 5 (#06a1b0) +D 7 (#6931a1) +R 9 (#ab7272) +U 7 (#640df3) +R 10 (#00b292) +D 7 (#8488f3) +R 6 (#8572c2) +D 8 (#105483) +L 7 (#5f6022) +U 5 (#3fadd3) +L 2 (#5f6020) +U 7 (#7ee933) +L 3 (#333b12) +D 10 (#221823) +L 3 (#a61062) +D 2 (#880f83) +L 5 (#0c2a12) +D 9 (#79c721) +L 5 (#7ccff2) +D 3 (#35a183) +R 8 (#14e432) +U 6 (#557103) +R 5 (#b5e082) +U 2 (#6bee81) +R 8 (#261612) +D 2 (#1f2401) +R 4 (#af2ae2) +D 11 (#40ed21) +L 6 (#1c2ef2) +D 5 (#293701) +L 7 (#3699c0) +D 6 (#5708b1) +L 3 (#8dd6c0) +D 12 (#2894f1) +L 5 (#513a12) +D 7 (#198a23) +R 12 (#20de32) +D 2 (#198a21) +R 9 (#525842) +D 4 (#273111) +L 10 (#3dd5a0) +D 5 (#14f9b1) +L 11 (#269190) +D 5 (#3d5191) +L 10 (#18a862) +U 3 (#71c981) +L 5 (#18a860) +U 4 (#1c7671) +R 8 (#6c8330) +U 11 (#5356c3) +L 8 (#41d400) +U 9 (#6b4d03) +R 5 (#41d402) +U 14 (#0cedc3) +L 5 (#686260) +D 5 (#717c11) +L 7 (#2afee0) +D 8 (#103261) +L 5 (#95f2c0) +D 9 (#81ae73) +L 8 (#88ebb0) +D 10 (#0ccc91) +L 7 (#5866b0) +D 9 (#304491) +L 3 (#2e6b60) +D 7 (#67abd1) +L 5 (#2e6b62) +D 9 (#2b3131) +L 2 (#5866b2) +D 11 (#375731) +L 8 (#5855c0) +D 7 (#33fcb1) +L 4 (#4b9132) +D 6 (#5bc731) +R 5 (#4b9130) +D 5 (#36f2b1) +R 7 (#68cbf2) +D 4 (#2e6191) +R 8 (#a1b162) +D 6 (#585861) +R 4 (#004682) +U 5 (#23f741) +R 6 (#abffe2) +D 5 (#46fcd3) +R 10 (#8cc712) +D 6 (#46fcd1) +R 6 (#31aad2) +U 3 (#4ed551) +R 3 (#9a6852) +U 5 (#7b9781) +R 9 (#5d9402) +D 8 (#20bc71) +R 10 (#577972) +U 6 (#624ef1) +R 7 (#2e7e40) +D 8 (#0e1851) +R 4 (#868f30) +D 6 (#9ac001) +R 3 (#91bd02) +D 7 (#8455f1) +R 6 (#3dc142) +D 4 (#6bf421) +L 9 (#262a72) +D 2 (#6c0b41) +L 2 (#2def60) +D 3 (#2e8621) +L 7 (#2def62) +D 7 (#5cd0a1) +L 3 (#2eabf2) +D 9 (#87f891) +L 7 (#7801e2) +U 4 (#2d78c1) +L 4 (#884eb2) +U 8 (#5f91d1) +L 8 (#b069c2) +U 3 (#706ec3) +L 10 (#14d6a0) +U 2 (#2e1ee3) +L 5 (#6fcd20) +U 2 (#2e1ee1) +L 4 (#6a8280) +U 9 (#10aed3) +L 6 (#28d1e0) +U 8 (#9ef053) +L 9 (#0e4470) +U 3 (#9fcdf3) +L 6 (#6be0a0) +U 4 (#4b5df3) +L 13 (#3d1b92) +D 5 (#0a7c03) +L 4 (#4225d2) +D 4 (#1a1661) +L 12 (#012740) +D 5 (#3ce951) +L 3 (#52df42) +D 7 (#ace6c1) +L 8 (#52df40) +D 4 (#255791) +R 7 (#012742) +D 3 (#4a15d1) +R 10 (#318982) +D 3 (#510b33) +L 14 (#7cfeb2) +D 2 (#510b31) +L 3 (#13c862) +D 5 (#126f93) +L 8 (#204922) +D 8 (#7b3623) +L 5 (#6c3780) +U 4 (#0c8663) +L 9 (#6c3782) +U 3 (#794583) +L 11 (#204920) +U 8 (#5fe243) +L 3 (#508b42) +U 4 (#62a383) +R 10 (#67e9b2) +U 8 (#94a731) +R 6 (#8e9b22) +U 4 (#858011) +L 10 (#8e9b20) +U 7 (#4c8cd1) +L 6 (#8f5152) +U 8 (#483551) +L 4 (#42e142) +D 12 (#40e3a1) +L 4 (#0b5b92) +D 9 (#0d1471) +R 4 (#37cb02) +D 5 (#b1c241) +L 8 (#67aa22) +D 5 (#799ad1) +L 5 (#3aa9f2) +D 7 (#473731) +L 4 (#60de22) +D 6 (#65e001) +L 2 (#80c4d0) +D 9 (#2fbf71) +L 3 (#826d60) +U 4 (#848001) +L 3 (#687032) +U 11 (#9768d1) +L 4 (#5b7e22) +D 5 (#25fb53) +L 7 (#ade7d2) +U 2 (#25fb51) +L 4 (#394662) +U 11 (#840763) diff --git a/src/day18.rs b/src/day18.rs new file mode 100644 index 0000000..5f17795 --- /dev/null +++ b/src/day18.rs @@ -0,0 +1,646 @@ +use std::{collections::VecDeque, ops::RangeInclusive, thread}; + +use itertools::{Itertools, MinMaxResult}; +use regex::{Captures, Regex, Replacer}; + +pub fn run() { + let input = include_str!("../input/day18.txt"); + // let input = r#" + // R 6 (#70c710) + // D 5 (#0dc571) + // L 2 (#5713f0) + // D 2 (#d2c081) + // R 2 (#59c680) + // D 2 (#411b91) + // L 5 (#8ceee2) + // U 2 (#caa173) + // L 1 (#1b58a2) + // U 2 (#caa171) + // R 2 (#7807d2) + // U 3 (#a77fa3) + // L 2 (#015232) + // U 2 (#7a21e3) + // "#; + + // let input = r#" + // R 4 (#000000) + // U 4 (#000000) + // R 6 (#000000) + // U 10 (#000000) + // L 4 (#000000) + // D 6 (#000000) + // L 6 (#000000) + // D 8 (#000000) + // "#; + + // let input = r#" + // L 3 (#000000) + // U 5 (#000000) + // L 3 (#000000) + // D 3 (#000000) + // L 3 (#000000) + // U 6 (#000000) + // R 9 (#000000) + // D 8 (#000000) + // "#; + + let instructions = parse_instructions(input); + let path = run_instructions(&instructions); + dbg!(count_enclosed2(&path)); + + // let instructions2 = parse_instructions_part2(input); + // let path2 = run_instructions(&instructions2); + // dbg!(count_enclosed2(&path2)); +} + +fn parse_instructions(s: &str) -> Vec { + s.trim() + .lines() + .map(|l| { + let mut parts = l.trim().split(" "); + let dir: Direction = parts.next().unwrap().into(); + let distance: u32 = parts.next().unwrap().parse().unwrap(); + Instruction { + direction: dir, + distance, + } + }) + .collect() +} + +fn parse_instructions_part2(s: &str) -> Vec { + s.trim() + .lines() + .map(|l| { + let hex = l + .trim() + .split(" ") + .skip(2) + .next() + .unwrap() + .strip_prefix("(#") + .unwrap() + .strip_suffix(")") + .unwrap(); + let dir = match hex.chars().last().unwrap() { + '0' => Direction::Right, + '1' => Direction::Down, + '2' => Direction::Left, + '3' => Direction::Up, + c => panic!("unexpected dir {}", c), + }; + let distance = u32::from_str_radix(hex.split_at(5).0, 16).unwrap(); + Instruction { + direction: dir, + distance, + } + }) + .collect() +} + +#[derive(Debug, Clone, Copy)] +struct Instruction { + direction: Direction, + distance: u32, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Direction { + Up, + Down, + Left, + Right, +} + +impl From<&str> for Direction { + fn from(value: &str) -> Self { + match value { + "U" => Self::Up, + "D" => Self::Down, + "L" => Self::Left, + "R" => Self::Right, + _ => panic!("invalid direction {}", value), + } + } +} + +impl Direction { + fn is_vertical(&self) -> bool { + match self { + Self::Up | Self::Down => true, + _ => false, + } + } + + fn move_point(&self, (x, y): (isize, isize), distance: isize) -> (isize, isize) { + match self { + Self::Up => (x, y - distance), + Self::Down => (x, y + distance), + Self::Left => (x - distance, y), + Self::Right => (x + distance, y), + } + } + + fn turn(&self, other: Self) -> Turn { + match (self, other) { + (Self::Up, Self::Left) => Turn::Left, + (Self::Up, Self::Right) => Turn::Right, + (Self::Up, Self::Up) => Turn::None, + (Self::Up, Self::Down) => Turn::Back, + (Self::Down, Self::Left) => Turn::Right, + (Self::Down, Self::Right) => Turn::Left, + (Self::Down, Self::Down) => Turn::None, + (Self::Down, Self::Up) => Turn::Back, + (Self::Left, Self::Up) => Turn::Right, + (Self::Left, Self::Down) => Turn::Left, + (Self::Left, Self::Left) => Turn::None, + (Self::Left, Self::Right) => Turn::Back, + (Self::Right, Self::Up) => Turn::Left, + (Self::Right, Self::Down) => Turn::Right, + (Self::Right, Self::Right) => Turn::None, + (Self::Right, Self::Left) => Turn::Back, + } + } + + fn between(a: (isize, isize), b: (isize, isize)) -> Self { + if a.0 == b.0 { + if a.1 > b.1 { + Self::Up + } else { + Self::Down + } + } else if a.1 == b.1 { + if a.0 > b.0 { + Self::Left + } else { + Self::Right + } + } else { + panic!() + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Turn { + None, + Back, + Left, + Right, +} + +fn run_instructions(instructions: &[Instruction]) -> Vec { + let mut path = vec![]; + let mut prev = (0, 0); + for insn in instructions { + let next = insn.direction.move_point(prev, insn.distance as isize); + path.push(Segment { + start: prev, + end: next, + direction: insn.direction, + }); + prev = next; + } + path +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +struct Segment { + start: (isize, isize), + end: (isize, isize), + direction: Direction, +} + +impl Segment { + fn contains(&self, (x, y): (isize, isize)) -> bool { + if self.start.0 == self.end.0 { + let min_y = self.start.1.min(self.end.1); + let max_y = self.start.1.max(self.end.1); + x == self.start.0 && y >= min_y && y <= max_y + } else if self.start.1 == self.end.1 { + let min_x = self.start.0.min(self.end.0); + let max_x = self.start.0.max(self.end.0); + x >= min_x && x <= max_x && y == self.start.1 + } else { + panic!(); + } + } + + fn contains_y(&self, y: isize) -> bool { + let min_y = self.start.1.min(self.end.1); + let max_y = self.start.1.max(self.end.1); + y >= min_y && y <= max_y + } + + fn intersects(&self, min_x: isize, max_x: isize, min_y: isize, max_y: isize) -> bool { + if self.direction.is_vertical() { + if self.start.0 >= min_x && self.start.0 <= max_x { + let ys = self.start.1.min(self.end.1)..=self.start.1.max(self.end.1); + return range_intersects(ys, min_y..=max_y); + } + } else { + if self.start.1 >= min_y && self.start.1 <= max_y { + let xs = self.start.0.min(self.end.0)..=self.start.0.max(self.end.0); + return range_intersects(xs, min_x..=max_x); + } + } + false + } +} + +fn range_intersects(a: RangeInclusive, b: RangeInclusive) -> bool { + if b.contains(a.start()) { + true + } else if b.contains(a.end()) { + true + } else { + a.start() < b.start() && a.end() > b.end() + } +} + +fn path_bounds(path: &[Segment]) -> (isize, isize, isize, isize) { + let xs = path + .iter() + .flat_map(|segment| [segment.start.0, segment.end.0].into_iter()) + .minmax(); + let (min_x, max_x) = match xs { + MinMaxResult::NoElements => panic!(), + MinMaxResult::OneElement(x) => (x, x), + MinMaxResult::MinMax(min, max) => (min, max), + }; + let ys = path + .iter() + .flat_map(|segment| [segment.start.1, segment.end.1].into_iter()) + .minmax(); + let (min_y, max_y) = match ys { + MinMaxResult::NoElements => panic!(), + MinMaxResult::OneElement(y) => (y, y), + MinMaxResult::MinMax(min, max) => (min, max), + }; + (min_x, max_x, min_y, max_y) +} + +fn count_enclosed2(path: &[Segment]) -> usize { + // println!("initial segments: {}", path.len()); + // print_path(path); + + let mut count = 0; + + let mut path = path.to_vec(); + + let mut last_segment_count = None; + loop { + // println!("--------------"); + // print_path(&path); + if let Some(c) = last_segment_count { + if c == path.len() { + print_path(&path); + panic!("stuck at {} segments", c); + } else { + last_segment_count = Some(path.len()); + } + } else { + last_segment_count = Some(path.len()); + } + // println!("segments remaining: {}", path.len()); + // if iterations == 4 { + // break; + // } + if path.len() > 4 { + 'start_idx: for start_idx in 0..path.len() - 3 { + let a = path[start_idx]; + let b = path[start_idx + 1]; + let c = path[start_idx + 2]; + + let ab = a.direction.turn(b.direction); + let bc = b.direction.turn(c.direction); + if !((ab == Turn::Right && bc == Turn::Right) + || (ab == Turn::Left && bc == Turn::Left)) + { + continue; + } + + let min_x: isize; + let max_x: isize; + let min_y: isize; + let max_y: isize; + if !b.direction.is_vertical() { + if b.direction == Direction::Left { + min_x = b.end.0; + max_x = b.start.0; + } else { + min_x = b.start.0; + max_x = b.end.0; + } + + let up: Segment; + let down: Segment; + if a.direction == Direction::Up { + up = a; + down = c; + } else { + up = c; + down = a; + } + + min_y = up.end.1.max(down.start.1); + max_y = up.start.1.min(down.end.1); + + // skip this start_idx if removing the rect would cause the path to intersect + // itself + for (i, s) in path.iter().enumerate() { + if i == start_idx || i == start_idx + 1 || i == start_idx + 2 { + continue; + } + if b.direction.is_vertical() == s.direction.is_vertical() { + continue; + } + if s.intersects(min_x, max_x, min_y, max_y) { + continue 'start_idx; + } + } + + // dbg!(start_idx); + + let width = 1 + max_x - min_x; + let height = 1 + max_y - min_y; + if is_inside(&path, (max_x, max_y - 1)) { + // println!("+{}", width * (height - 1)); + count += width * (height - 1); + } else { + // println!("-{}", (width - 2) * (height - 1)); + count -= (width - 2) * (height - 1); + } + + if a.direction == Direction::Down { + path[start_idx].end.1 = min_y; + path[start_idx + 1].start.1 = min_y; + path[start_idx + 1].end.1 = min_y; + path[start_idx + 2].start.1 = min_y; + } else { + path[start_idx].end.1 = max_y; + path[start_idx + 1].start.1 = max_y; + path[start_idx + 1].end.1 = max_y; + path[start_idx + 2].start.1 = max_y; + } + } else { + if b.direction == Direction::Down { + min_y = b.start.1; + max_y = b.end.1; + } else { + min_y = b.end.1; + max_y = b.start.1; + } + + let left: Segment; + let right: Segment; + if a.direction == Direction::Left { + left = a; + right = c; + } else { + left = c; + right = a; + } + + min_x = left.end.0.max(right.start.0); + max_x = left.start.0.min(right.end.0); + + // skip this start_idx if removing the rect would cause the path to intersect + // itself + for (i, s) in path.iter().enumerate() { + if i == start_idx || i == start_idx + 1 || i == start_idx + 2 { + continue; + } + if b.direction.is_vertical() == s.direction.is_vertical() { + continue; + } + if s.intersects(min_x, max_x, min_y, max_y) { + continue 'start_idx; + } + } + + // dbg!(start_idx); + + let width = 1 + max_x - min_x; + let height = 1 + max_y - min_y; + // dbg!((width, height)); + if is_inside(&path, (max_x, max_y - 1)) { + // println!("+{}", (width - 1) * height); + count += (width - 1) * height; + } else { + // println!("-{}", (width - 1) * (height - 2)); + count -= (width - 1) * (height - 2); + } + + if a.direction == Direction::Left { + path[start_idx].end.0 = max_x; + path[start_idx + 1].start.0 = max_x; + path[start_idx + 1].end.0 = max_x; + path[start_idx + 2].start.0 = max_x; + } else { + path[start_idx].end.0 = min_x; + path[start_idx + 1].start.0 = min_x; + path[start_idx + 1].end.0 = min_x; + path[start_idx + 2].start.0 = min_x; + } + } + simplify_path(&mut path); + break; + } + } else if path.len() == 4 { + println!("four segments remaining, almost done"); + let (min_x, max_x, min_y, max_y) = rect_around(path[0], path[1], path[2]); + let width = 1 + max_x - min_x; + let height = 1 + max_y - min_y; + // dbg!((width, height)); + count += width * height; + break; + } + } + + // dbg!(count); + count as usize +} + +fn is_inside(path: &[Segment], p: (isize, isize)) -> bool { + let (min_x, _, _, _) = path_bounds(path); + + let row_paths = path + .iter() + .filter(|s| s.contains_y(p.1)) + .collect::>(); + + let mut inside = false; + + for x in min_x..p.0 { + let containing = row_paths + .iter() + .filter(|s| s.contains((x, p.1))) + .take(2) + .collect::>(); + + if containing.len() == 2 { + // only count the top corners + let vert = if containing[0].direction.is_vertical() { + containing[0] + } else { + containing[1] + }; + let min_y = vert.start.1.min(vert.end.1); + if p.1 == min_y { + inside = !inside; + } + } else if containing.len() == 1 { + if containing[0].direction.is_vertical() { + inside = !inside; + } + } + } + inside +} + +fn rect_around(a: Segment, b: Segment, c: Segment) -> (isize, isize, isize, isize) { + let mut min_x = a.start.0; + let mut max_x = a.start.0; + let mut min_y = a.start.1; + let mut max_y = a.start.1; + for s in [a, b, c] { + min_x = min_x.min(s.start.0).min(s.end.0); + max_x = max_x.max(s.start.0).max(s.end.0); + min_y = min_y.min(s.start.1).min(s.end.1); + max_y = max_y.max(s.start.1).max(s.end.1); + } + (min_x, max_x, min_y, max_y) +} + +fn simplify_path(path: &mut Vec) { + // println!("before simplification:"); + // print_path(path); + + let mut i = 0; + while i < path.len() - 1 { + let a = path[i]; + let b = path[i + 1]; + if b.start == b.end { + path.remove(i + 1); + continue; + } + path[i].direction = Direction::between(a.start, a.end); + let turn = a.direction.turn(b.direction); + match turn { + Turn::None | Turn::Back => { + if path[i].direction.is_vertical() { + path[i].end.1 = path[i + 1].end.1; + } else { + path[i].end.0 = path[i + 1].end.0; + } + path.remove(i + 1); + } + _ => i += 1, + } + } + + assert_eq!(path[0].start, path[path.len() - 1].end); + if path[0].direction == path[path.len() - 1].direction { + path[0].start = path[path.len() - 1].start; + path.remove(path.len() - 1); + } +} + +fn print_path(path: &[Segment]) { + // for segment in path { + // println!( + // "{:?} {:?} -> {:?}", + // segment.direction, segment.start, segment.end + // ); + // } + + let (min_x, max_x, min_y, max_y) = path_bounds(path); + + for y in min_y..=max_y { + for x in min_x..=max_x { + if (x, y) == path[0].start { + print!("X"); + } else if let Some(s) = path.iter().find(|s| s.contains((x, y))) { + let c = match s.direction { + Direction::Up => '^', + Direction::Down => 'V', + Direction::Left => '<', + Direction::Right => '>', + }; + print!("{}", c); + } else { + print!("."); + } + } + println!(); + } + println!(); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_run_instructions() { + let instructions = parse_instructions( + r#" +R 6 (#70c710) +D 5 (#0dc571) +L 2 (#5713f0) +D 2 (#d2c081) + "#, + ); + let path = run_instructions(&instructions); + assert_eq!( + path, + vec![ + Segment { + start: (0, 0), + end: (6, 0), + direction: Direction::Right + }, + Segment { + start: (6, 0), + end: (6, 5), + direction: Direction::Down + }, + Segment { + start: (6, 5), + end: (4, 5), + direction: Direction::Left + }, + Segment { + start: (4, 5), + end: (4, 7), + direction: Direction::Down + }, + ] + ); + } + + #[test] + fn test_count_inside() { + let instructions = parse_instructions( + r#" + R 6 (#70c710) + D 5 (#0dc571) + L 2 (#5713f0) + D 2 (#d2c081) + R 2 (#59c680) + D 2 (#411b91) + L 5 (#8ceee2) + U 2 (#caa173) + L 1 (#1b58a2) + U 2 (#caa171) + R 2 (#7807d2) + U 3 (#a77fa3) + L 2 (#015232) + U 2 (#7a21e3) + "#, + ); + let path = run_instructions(&instructions); + assert_eq!(count_enclosed2(&path), 62); + } +} diff --git a/src/main.rs b/src/main.rs index 07c5fee..1c6533c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,7 +18,8 @@ mod day14; mod day15; mod day16; mod day17; +mod day18; fn main() { - day17::run(); + day18::run(); }