676 lines
20 KiB
Rust
676 lines
20 KiB
Rust
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!(path_bounds(&path));
|
|
dbg!(count_enclosed2(&path, true));
|
|
|
|
// let instructions2 = parse_instructions_part2(input);
|
|
// let path2 = run_instructions(&instructions2);
|
|
// dbg!(count_enclosed2(&path2, false));
|
|
}
|
|
|
|
fn parse_instructions(s: &str) -> Vec<Instruction> {
|
|
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<Instruction> {
|
|
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<Segment> {
|
|
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<isize>, b: RangeInclusive<isize>) -> 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], visualize: bool) -> usize {
|
|
// println!("initial segments: {}", path.len());
|
|
// print_path(path);
|
|
|
|
let mut count = 0;
|
|
|
|
let mut path = path.to_vec();
|
|
let orig_bounds = path_bounds(&path);
|
|
dbg!(orig_bounds);
|
|
|
|
let mut last_segment_count = None;
|
|
let mut iteration = 0;
|
|
loop {
|
|
if visualize {
|
|
write_visualization(&path, orig_bounds, iteration);
|
|
}
|
|
iteration += 1;
|
|
|
|
// 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!(iteration);
|
|
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::<Vec<_>>();
|
|
|
|
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::<Vec<_>>();
|
|
|
|
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<Segment>) {
|
|
// 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!();
|
|
}
|
|
|
|
fn write_visualization(
|
|
path: &[Segment],
|
|
(min_x, max_x, min_y, max_y): (isize, isize, isize, isize),
|
|
iteration: u32,
|
|
) {
|
|
let mut buf = image::ImageBuffer::new((1 + max_x - min_x) as u32, (1 + max_y - min_y) as u32);
|
|
|
|
for (x, y, pixel) in buf.enumerate_pixels_mut() {
|
|
let world_x = min_x + x as isize;
|
|
let world_y = min_y + y as isize;
|
|
if path.iter().any(|s| s.contains((world_x, world_y))) {
|
|
*pixel = image::Rgb([0u8, 0, 0]);
|
|
} else {
|
|
*pixel = image::Rgb([255, 255, 255]);
|
|
}
|
|
}
|
|
|
|
buf.save(format!("vis/{}.png", iteration)).unwrap();
|
|
}
|
|
|
|
#[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, false), 62);
|
|
}
|
|
}
|