use nalgebra::Point3; use nom::{ bytes::complete::tag, character::complete::{line_ending, u32}, combinator::map, multi::separated_list1, sequence::{separated_pair, tuple}, IResult, }; use std::fs; fn main() -> Result<(), Box> { let input = fs::read_to_string("inputs/day_22.txt")?; let pile = BrickPile::parser(&input).unwrap().1; let settled_pile = pile.settle(); let brickfall_sum = settled_pile.brickfall_sum(); dbg!(&brickfall_sum.count_disintegratable_blocks()); dbg!(&brickfall_sum.sum_brickfalls()); Ok(()) } #[derive(Debug)] struct BrickPile(Vec); #[derive(Debug)] struct SettledBrickPile { bricks: Vec, settled_count: usize, } #[derive(Debug, Clone)] struct Brick { bottom: Point3, // the lowest z will always be here. The top might still have the same z. top: Point3, } #[derive(Debug)] struct BrickfallSum(Vec); impl BrickPile { fn parser(input: &str) -> IResult<&str, Self> { map(separated_list1(line_ending, Brick::parser), BrickPile)(input) } fn settle(&self) -> SettledBrickPile { let mut settled_bricks = self.0.clone(); let mut has_fallen = vec![false; settled_bricks.len()]; let mut all_settled = false; while !all_settled { all_settled = true; for self_i in 0..settled_bricks.len() { let this_brick_is_resting_on_something = settled_bricks[self_i] .is_resting_on_ground() || (0..settled_bricks.len()).any(|other_i| { self_i != other_i && settled_bricks[self_i].is_resting_on_other(&settled_bricks[other_i]) }); if !this_brick_is_resting_on_something { settled_bricks[self_i].fall_one(); has_fallen[self_i] = true; all_settled = false; } } } SettledBrickPile { bricks: settled_bricks, settled_count: has_fallen.iter().filter(|f| **f).count(), } } } impl Brick { fn parser(input: &str) -> IResult<&str, Self> { map( separated_pair(point_parser, tag("~"), point_parser), |(a, b)| { if a.z < b.z { Brick { bottom: a, top: b } } else { Brick { top: a, bottom: b } } }, )(input) } fn min_x(&self) -> u32 { self.bottom.x.min(self.top.x) } fn max_x(&self) -> u32 { self.bottom.x.max(self.top.x) } fn min_y(&self) -> u32 { self.bottom.y.min(self.top.y) } fn max_y(&self) -> u32 { self.bottom.y.max(self.top.y) } fn is_resting_on_ground(&self) -> bool { self.bottom.z == 1 } fn is_resting_on_other(&self, other: &Self) -> bool { self.bottom.z == other.top.z + 1 && self.min_x() <= other.max_x() && other.min_x() <= self.max_x() && self.min_y() <= other.max_y() && other.min_y() <= self.max_y() } fn fall_one(&mut self) { self.bottom.z -= 1; self.top.z -= 1; } } fn point_parser(input: &str) -> IResult<&str, Point3> { map( tuple((u32, tag(","), u32, tag(","), u32)), |(x, _, y, _, z)| Point3::new(x, y, z), )(input) } impl SettledBrickPile { fn brickfall_sum(&self) -> BrickfallSum { BrickfallSum( (0..self.bricks.len()) .map(|self_i| { self.count_bricks_that_would_fall_if_this_one_is_disintegrated(self_i) }) .collect(), ) } fn count_bricks_that_would_fall_if_this_one_is_disintegrated(&self, i: usize) -> usize { let mut unsettled_bricks = self.bricks.clone(); unsettled_bricks.remove(i); let unsettled_bricks = BrickPile(unsettled_bricks); let resettled = unsettled_bricks.settle(); resettled.settled_count } } impl BrickfallSum { fn count_disintegratable_blocks(&self) -> usize { self.0.iter().filter(|fallen| **fallen == 0).count() } fn sum_brickfalls(&self) -> usize { self.0.iter().sum() } }