diff --git a/src/bin/d14p1.rs b/src/bin/d14p1.rs new file mode 100644 index 0000000..47b2391 --- /dev/null +++ b/src/bin/d14p1.rs @@ -0,0 +1,17 @@ +use aoc22::{day14, util}; + +pub fn main() { + let mut cave = day14::parse_cave(&util::parse_input()); + + println!("Initial cave:"); + cave.print(); + + let mut sand = 0; + while cave.drop_sand() { + sand += 1; + } + + println!("Final cave:"); + cave.print(); + println!("Total sand dropped: {}", sand); +} diff --git a/src/day14.rs b/src/day14.rs new file mode 100644 index 0000000..d0c1bc0 --- /dev/null +++ b/src/day14.rs @@ -0,0 +1,121 @@ +use std::collections::HashSet; + +use itertools::Itertools; + +use crate::util::Coord; + +pub const SAND_SOURCE: Coord = (0, 500); + +pub struct Cave { + pub rows: usize, + pub cols: usize, + pub blocked: Vec>, + pub sand_source: Coord, +} + +impl Cave { + pub fn print(&self) { + for row in 0..self.rows { + for col in 0..self.cols { + if (row, col) == self.sand_source { + print!("+") + } else if self.blocked[row][col] { + print!("#"); + } else { + print!("."); + } + } + print!("\n") + } + } + + pub fn drop_sand(&mut self) -> bool { + let (y, x) = self.sand_source; + let mut y = y as isize; + let mut x = x as isize; + + while y < self.rows as isize && x > 0 && x < self.cols as isize { + if !self.is_blocked(y + 1, x) { + y += 1; + } else if !self.is_blocked(y + 1, x - 1) { + y += 1; + x -= 1; + } else if !self.is_blocked(y + 1, x + 1) { + y += 1; + x += 1; + } else { + self.blocked[y as usize][x as usize] = true; + return true; + } + } + + false + } + + fn is_blocked(&self, y: isize, x: isize) -> bool { + !(y < 0 || y >= self.rows as isize || x < 0 || x >= self.cols as isize) + && self.blocked[y as usize][x as usize] + } +} + +pub fn parse_cave(input: &String) -> Cave { + let mut paths = HashSet::new(); + + for line in input.lines() { + let mut path = line.split("->").map(|n| parse_coord(n.trim())); + let mut last = path.next().unwrap(); + paths.insert(last); + for next in path { + let y_min = next.0.min(last.0); + let y_max = next.0.max(last.0); + let x_min = next.1.min(last.1); + let x_max = next.1.max(last.1); + let horizontal = y_min == y_max; + let vertical = x_min == x_max; + + assert!(horizontal ^ vertical); + if horizontal { + for x in x_min..=x_max { + paths.insert((y_min, x)); + } + } else { + for y in y_min..=y_max { + paths.insert((y, x_max)); + } + } + + last = next; + } + } + + assert_ne!(paths.len(), 0); + + let (y_min, y_max) = paths.iter().map(|c| c.0).minmax().into_option().unwrap(); + let (x_min, x_max) = paths.iter().map(|c| c.1).minmax().into_option().unwrap(); + + // Sand source is always at y = 0, x = 500 + let y_min = y_min.min(SAND_SOURCE.0); + let x_min = x_min.min(SAND_SOURCE.1); + let x_max = x_max.max(SAND_SOURCE.1); + + let rows = y_max - y_min + 1; + let cols = x_max - x_min + 1; + + let mut blocked = vec![vec![false; cols]; rows]; + + for coord in paths { + blocked[coord.0 - y_min][coord.1 - x_min] = true; + } + + Cave { + rows, + cols, + blocked, + sand_source: (SAND_SOURCE.0 - y_min, SAND_SOURCE.1 - x_min), + } +} + +fn parse_coord(input: &str) -> Coord { + let (x, y) = input.split_once(',').unwrap(); + (y.parse().unwrap(), x.parse().unwrap()) +} diff --git a/src/lib.rs b/src/lib.rs index 78de87a..8b2fb31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ pub mod day10; pub mod day11; pub mod day12; pub mod day13; +pub mod day14; pub mod day2; pub mod day3; pub mod day4; diff --git a/src/util.rs b/src/util.rs index e066d5d..051583d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -35,3 +35,5 @@ pub fn max_n(slice: &[T], n: usize) -> Result, ()> { Ok(max_vals) } + +pub type Coord = (usize, usize);