use nom::{ bytes::complete::tag, character::complete::{i32, line_ending}, combinator::map, multi::separated_list1, sequence::tuple, IResult, }; use std::{collections::BTreeSet, fs}; fn main() -> Result<(), Box> { let input = fs::read_to_string("inputs/day_14.txt")?; let room = Room::parser(&input).unwrap().1; { let mut void_room = room.clone(); let mut room_is_full = false; while !room_is_full { let drop_result = void_room.drop_sand(true); room_is_full = drop_result != DropSandResult::Settled; if drop_result == DropSandResult::RoomFull { return Err("The room filled up to the top!".into()); } } dbg!(void_room.sand.len()); } { let mut floor_room = room.clone(); let mut room_is_full = false; while !room_is_full { let drop_result = floor_room.drop_sand(false); room_is_full = drop_result != DropSandResult::Settled; if drop_result == DropSandResult::FellIntoTheVoid { return Err("This room shouldn't have a void!".into()); } } dbg!(floor_room.sand.len()); } Ok(()) } #[derive(Debug, Clone)] struct Room { walls: Vec, sand: BTreeSet, sand_inlet: Point, void_start_y: i32, } #[derive(Debug, Clone)] enum Wall { Vertical(VerticalWall), Horizontal(HorizontalWall), } #[derive(Debug, Clone)] struct VerticalWall { x: i32, y1: i32, y2: i32, } #[derive(Debug, Clone)] struct HorizontalWall { y: i32, x1: i32, x2: i32, } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] struct Point { x: i32, y: i32, } #[derive(Debug, PartialEq, Eq)] enum DropSandResult { FellIntoTheVoid, Settled, RoomFull, } impl Room { fn parser(input: &str) -> IResult<&str, Self> { map( separated_list1(line_ending, separated_list1(tag(" -> "), Point::parser)), |wall_segments| { let mut walls = Vec::new(); let mut void_start_y = 0; for wall_segment in wall_segments { for point_pair in wall_segment.windows(2) { walls.push(if point_pair[0].x == point_pair[1].x { Wall::Vertical(VerticalWall { x: point_pair[0].x, y1: point_pair[0].y.min(point_pair[1].y), y2: point_pair[0].y.max(point_pair[1].y), }) } else if point_pair[0].y == point_pair[1].y { Wall::Horizontal(HorizontalWall { y: point_pair[0].y, x1: point_pair[0].x.min(point_pair[1].x), x2: point_pair[0].x.max(point_pair[1].x), }) } else { panic!("Invalid wall segment") }); void_start_y = void_start_y.max(point_pair[0].y); void_start_y = void_start_y.max(point_pair[1].y); } void_start_y = void_start_y.max(wall_segment.iter().map(|p| p.y).max().unwrap_or(0)) } Room { walls, sand: BTreeSet::new(), sand_inlet: Point { x: 500, y: 0 }, void_start_y, } }, )(input) } fn point_is_occupied(&self, p: &Point) -> bool { p.y >= self.void_start_y + 2 || self.sand.contains(p) || self.walls.iter().any(|w| w.point_is_occupied(p)) } fn point_is_in_the_void(&self, p: &Point) -> bool { p.y >= self.void_start_y } fn drop_sand(&mut self, allow_infinite_void: bool) -> DropSandResult { if self.point_is_occupied(&self.sand_inlet) { return DropSandResult::RoomFull; } let mut falling_sand = self.sand_inlet.clone(); loop { if allow_infinite_void && self.point_is_in_the_void(&falling_sand) { return DropSandResult::FellIntoTheVoid; } else if !self.point_is_occupied(&Point { x: falling_sand.x, y: falling_sand.y + 1, }) { falling_sand.y += 1; } else if !self.point_is_occupied(&Point { x: falling_sand.x - 1, y: falling_sand.y + 1, }) { falling_sand.x -= 1; falling_sand.y += 1; } else if !self.point_is_occupied(&Point { x: falling_sand.x + 1, y: falling_sand.y + 1, }) { falling_sand.x += 1; falling_sand.y += 1; } else { self.sand.insert(falling_sand); return DropSandResult::Settled; } } } } impl Wall { fn point_is_occupied(&self, p: &Point) -> bool { match self { Wall::Vertical(VerticalWall { x, y1, y2 }) => p.x == *x && p.y >= *y1 && p.y <= *y2, Wall::Horizontal(HorizontalWall { y, x1, x2 }) => p.y == *y && p.x >= *x1 && p.x <= *x2, } } } impl Point { fn parser(input: &str) -> IResult<&str, Self> { map(tuple((i32, tag(","), i32)), |(x, _, y)| Point { x, y })(input) } }