summaryrefslogtreecommitdiff
path: root/2022/src/bin/day_14.rs
diff options
context:
space:
mode:
Diffstat (limited to '2022/src/bin/day_14.rs')
-rw-r--r--2022/src/bin/day_14.rs184
1 files changed, 184 insertions, 0 deletions
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<dyn std::error::Error>> {
+ 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<Wall>,
+ sand: BTreeSet<Point>,
+ 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)
+ }
+}