From 5bfe0f6ac763a91fa0bd53380a9fcd4c5237147e Mon Sep 17 00:00:00 2001 From: Justin Wernick Date: Wed, 14 Dec 2022 14:53:19 +0200 Subject: Day 14 --- 2022/src/bin/day_14.rs | 184 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 2022/src/bin/day_14.rs (limited to '2022/src') diff --git a/2022/src/bin/day_14.rs b/2022/src/bin/day_14.rs new file mode 100644 index 0000000..ec5b1cb --- /dev/null +++ b/2022/src/bin/day_14.rs @@ -0,0 +1,184 @@ +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) + } +} -- cgit v1.2.3