use aoc2023_niels_overkamp::common::{self, AOCResult}; use std::collections::HashSet; const DAY: &str = "day10"; fn main() -> Result<(), Box> { common::run(DAY, &run) } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Curvature { Right = -1, Straight = 0, Left = 1 } impl Curvature { fn as_number(&self) -> isize { *self as isize } } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Direction { North, East, South, West } impl Direction { fn take_pipe(&self, pipe: &u8) -> Option<(Self, Curvature)> { match (pipe, self) { (b'|', Self::North) => Some((Self::North, Curvature::Straight)), (b'|', Self::South) => Some((Self::South, Curvature::Straight)), (b'-', Self::West) => Some((Self::West, Curvature::Straight)), (b'-', Self::East) => Some((Self::East, Curvature::Straight)), (b'L', Self::South) => Some((Self::East, Curvature::Left)), (b'L', Self::West) => Some((Self::North, Curvature::Right)), (b'J', Self::South) => Some((Self::West, Curvature::Right)), (b'J', Self::East) => Some((Self::North, Curvature::Left)), (b'7', Self::East) => Some((Self::South, Curvature::Right)), (b'7', Self::North) => Some((Self::West, Curvature::Left)), (b'F', Self::West) => Some((Self::South, Curvature::Left)), (b'F', Self::North) => Some((Self::East, Curvature::Right)), _ => None } } fn curvature(&self, other: &Direction) -> Curvature { if *self == *other { Curvature::Straight } else if self.left() == *other { Curvature::Left } else { Curvature::Right } } fn do_move(&self, (x, y): (usize, usize)) -> Option<(usize, usize)> { match self { Self::North => y.checked_sub(1).map(|y| (x, y)), Self::East => Some((x + 1, y)), Self::South => Some((x, y + 1)), Self::West => x.checked_sub(1).map(|x| (x, y)) } } fn left(&self) -> Self { match self { Self::North => Self::West, Self::East => Self::North, Self::South => Self::East, Self::West => Self::South } } fn right(&self) -> Self { match self { Self::West => Self::North, Self::North => Self::East, Self::East => Self::South, Self::South => Self::West } } } const DIRECTIONS: [Direction;4] = [Direction::North, Direction::East, Direction::South, Direction::West]; pub fn run(input: &Vec) -> AOCResult { let mut start = None; 'outer: for (y, line) in input.iter().enumerate() { for (x, b) in line.as_bytes().iter().enumerate() { if *b == b'S' { start = Some((x,y)); break 'outer; } } } let input: Vec<&[u8]> = input.into_iter().map(|s| s.as_bytes()).collect(); let (mut x, mut y) = start.ok_or::>("No S found in input".into())?; let mut start_direction = None; if let Some(_) = Direction::East.take_pipe(&input[y][x + 1]) { start_direction = Some(Direction::East); } else if let Some(_) = Direction::South.take_pipe(&input[y + 1][x]) { start_direction = Some(Direction::South); } else if x > 0 { if let Some(_) = Direction::West.take_pipe(&input[y][x - 1]) { start_direction = Some(Direction::West); } } else if y > 0 { if let Some(_) = Direction::North.take_pipe(&input[y][x - 1]) { start_direction = Some(Direction::North); } } let mut direction = start_direction.ok_or::>("No valid connection to S found".into())?; let start_direction = direction; let mut loop_size = 0; let mut curvature = 0; let mut left_outline = HashSet::new(); let mut line = HashSet::new(); let mut right_outline = HashSet::new(); loop { loop_size += 1; (x, y) = direction.do_move((x, y)).ok_or_else(|| -> Box { format!("Invalid move at {}, {} {:?}", x, y, direction).into() })?; line.insert((x,y)); let pipe = input[y][x]; let (out_direction, pipe_curvature) = if pipe == b'S' { (start_direction, direction.curvature(&start_direction)) } else { direction .take_pipe(&pipe) .ok_or_else(|| -> Box { format!("No valid connection at {}, {} {:?}", x, y, direction).into() })? }; // println!("{}, {}, {:?}-{:?}->{:?}", x, y, direction, pipe_curvature, out_direction); match pipe_curvature { Curvature::Left => { if let Some(coord) = out_direction.right().do_move((x,y)) { // println!("{:?} right", coord); right_outline.insert(coord); } if let Some(coord) = direction.right().do_move((x,y)) { // println!("{:?} right", coord); right_outline.insert(coord); } } Curvature::Right => { if let Some(coord) = out_direction.left().do_move((x,y)) { // println!("{:?} left", coord); left_outline.insert(coord); } if let Some(coord) = direction.left().do_move((x,y)) { // println!("{:?} left", coord); left_outline.insert(coord); } } Curvature::Straight => () } direction = out_direction; curvature += pipe_curvature.as_number(); if pipe == b'S' { break } } // println!("{:?}", left_outline); // println!("{:?}", right_outline); // println!("{:?}", line); // println!("{}", curvature); let outline = if curvature.signum() == (Curvature::Left as isize) { left_outline } else { right_outline }; let mut visited = HashSet::new(); let mut inner_count = 0; let mut frontier = vec![]; for coord in outline.into_iter() { if visited.contains(&coord) || line.contains(&coord) { continue } frontier.push(coord); while let Some(coord) = frontier.pop() { if visited.contains(&coord) || line.contains(&coord) { continue } if !line.contains(&coord) { println!("{:?} {:?}", coord, visited); visited.insert(coord); inner_count += 1; } DIRECTIONS.iter() .for_each(|direction| { direction .do_move(coord) .filter(|coord| !visited.contains(coord) && !line.contains(coord)) .map(|coord| frontier.push(coord)); }); } } Ok([Some((loop_size / 2).to_string()), Some(inner_count.to_string())]) } #[test] pub fn test_day10() { assert!(common::run_test(DAY, &run)) }