/// --- Day 14: Regolith Reservoir --- /// /// The distress signal leads you to a giant waterfall! Actually, hang on - the signal seems like /// it's coming from the waterfall itself, and that doesn't make any sense. However, you do notice /// a little path that leads behind the waterfall. /// /// Correction: the distress signal leads you behind a giant waterfall! There seems to be a large /// cave system here, and the signal definitely leads further inside. /// /// As you begin to make your way deeper underground, you feel the ground rumble for a moment. /// Sand begins pouring into the cave! If you don't quickly figure out where the sand is going, /// you could quickly become trapped! /// /// Fortunately, your familiarity with analyzing the path of falling material will come in handy /// here. You scan a two-dimensional vertical slice of the cave above you (your puzzle input) and /// discover that it is mostly air with structures made of rock. /// /// Your scan traces the path of each solid rock structure and reports the x,y coordinates that /// form the shape of the path, where x represents distance to the right and y represents distance /// down. Each path appears as a single line of text in your scan. After the first point of each /// path, each point indicates the end of a straight horizontal or vertical line to be drawn from /// the previous point. For example: /// /// ``` /// 498,4 -> 498,6 -> 496,6 /// 503,4 -> 502,4 -> 502,9 -> 494,9 /// ``` /// /// This scan means that there are two paths of rock; the first path consists of two straight /// lines, and the second path consists of three straight lines. (Specifically, the first path /// consists of a line of rock from 498,4 through 498,6 and another line of rock from 498,6 through /// 496,6.) /// /// The sand is pouring into the cave from point 500,0. /// /// Drawing rock as #, air as ., and the source of the sand as +, this becomes: /// /// /// ``` /// 4 5 5 /// 9 0 0 /// 4 0 3 /// 0 ......+... /// 1 .......... /// 2 .......... /// 3 .......... /// 4 ....#...## /// 5 ....#...#. /// 6 ..###...#. /// 7 ........#. /// 8 ........#. /// 9 #########. /// ``` /// /// Sand is produced one unit at a time, and the next unit of sand is not produced until the /// previous unit of sand comes to rest. A unit of sand is large enough to fill one tile of air in /// your scan. /// /// A unit of sand always falls down one step if possible. If the tile immediately below is /// blocked (by rock or sand), the unit of sand attempts to instead move diagonally one step down /// and to the left. If that tile is blocked, the unit of sand attempts to instead move diagonally /// one step down and to the right. Sand keeps moving as long as it is able to do so, at each step /// trying to move down, then down-left, then down-right. If all three possible destinations are /// blocked, the unit of sand comes to rest and no longer moves, at which point the next unit of /// sand is created back at the source. /// /// So, drawing sand that has come to rest as o, the first unit of sand simply falls straight down /// and then stops: /// /// ``` /// ......+... /// .......... /// .......... /// .......... /// ....#...## /// ....#...#. /// ..###...#. /// ........#. /// ......o.#. /// #########. /// ``` /// /// The second unit of sand then falls straight down, lands on the first one, and then comes to /// rest to its left: /// /// ``` /// ......+... /// .......... /// .......... /// .......... /// ....#...## /// ....#...#. /// ..###...#. /// ........#. /// .....oo.#. /// #########. /// ``` /// /// After a total of five units of sand have come to rest, they form this pattern: /// /// ``` /// ......+... /// .......... /// .......... /// .......... /// ....#...## /// ....#...#. /// ..###...#. /// ......o.#. /// ....oooo#. /// #########. /// ``` /// /// After a total of 22 units of sand: /// /// ``` /// ......+... /// .......... /// ......o... /// .....ooo.. /// ....#ooo## /// ....#ooo#. /// ..###ooo#. /// ....oooo#. /// ...ooooo#. /// #########. /// ``` /// /// Finally, only two more units of sand can possibly come to rest: /// /// ``` /// ......+... /// .......... /// ......o... /// .....ooo.. /// ....#ooo## /// ...o#ooo#. /// ..###ooo#. /// ....oooo#. /// .o.ooooo#. /// #########. /// ``` /// /// Once all 24 units of sand shown above have come to rest, all further sand flows out the bottom, /// falling into the endless void. Just for fun, the path any new sand takes before falling /// forever is shown here with ~: /// /// ``` /// .......+... /// .......~... /// ......~o... /// .....~ooo.. /// ....~#ooo## /// ...~o#ooo#. /// ..~###ooo#. /// ..~..oooo#. /// .~o.ooooo#. /// ~#########. /// ~.......... /// ~.......... /// ~.......... /// ``` /// /// Using your scan, simulate the falling sand. How many units of sand come to rest before sand /// starts flowing into the abyss below? use clap::Parser; use itertools::Itertools; use nom::bytes::complete::tag; use nom::character::complete::i32; use nom::combinator::{map, opt}; use nom::error::{ContextError, ErrorKind as NomErrorKind, ParseError}; use nom::multi::many1; use nom::sequence::{terminated, tuple}; use nom::IResult; use std::fs::File; use std::io::prelude::*; use std::io::BufReader; use std::path::PathBuf; const FILEPATH: &'static str = "examples/input.txt"; const SAND_SOURCE: Coords = Coords { x: 500, y: 0 }; pub type Input<'a> = &'a str; pub type Result<'a, T> = IResult, T, Error>>; #[derive(Clone, Debug, PartialEq, Eq)] pub enum ErrorKind { Nom(NomErrorKind), Context(&'static str), Custom(String), } #[derive(Clone, Copy, Debug, PartialEq, Eq)] enum Terrain { Air, Rock, Sand, SandSource, } #[derive(Parser, Debug)] #[clap(author, version, about, long_about = None)] struct Cli { #[clap(short, long, default_value = FILEPATH)] file: PathBuf, } #[derive(Clone, Debug, PartialEq, Eq)] pub struct Error { pub errors: Vec<(I, ErrorKind)>, } #[derive(Clone, Copy, Debug, PartialEq, Eq)] struct Coords { x: i32, y: i32, } impl Coords { fn path_between(&self, other: &Coords) -> Vec { if self == other { panic!() } if self.x == other.x { let yrange = { if self.y > other.y { other.y..=self.y } else if other.y > self.y { self.y..=other.y } else { panic!() } }; yrange.map(|y| Coords { x: self.x, y }).collect_vec() } else { let xrange = { if self.x > other.x { other.x..=self.x } else if other.x > self.x { self.x..=other.x } else { panic!() } }; xrange.map(|x| Coords { x, y: self.y }).collect_vec() } } } #[derive(Clone, Debug)] struct Grid { data: Vec>, bounds: GridBoundaries, } impl Grid { fn new(bounds: GridBoundaries) -> Self { let xlen = (bounds.maxx - bounds.minx) as usize + 1; let ylen = (bounds.maxy - bounds.miny) as usize + 1; let data = vec![vec![Terrain::Air; ylen]; xlen]; Self { data, bounds } } fn get(&self, coord: &Coords) -> &Terrain { &self.data[(coord.x - self.bounds.minx) as usize][(coord.y - self.bounds.miny) as usize] } fn set(&mut self, coord: &Coords, terrain: Terrain) { self.data[(coord.x - self.bounds.minx) as usize][(coord.y - self.bounds.miny) as usize] = terrain; } fn populate(&mut self, geography: Vec) { geography .into_iter() .map(|rstruct| { rstruct .0 .as_slice() .windows(2) .map(|window| window[0].path_between(&window[1])) .flatten() .collect_vec() }) .flatten() .scan(self, |state, coords| { state.data[(coords.x - state.bounds.minx) as usize] [(coords.y - state.bounds.miny) as usize] = Terrain::Rock; Some(()) }) .last() .unwrap() } fn run(&mut self) -> usize { let mut count = 0; let mut pos = SAND_SOURCE; let mut history = vec![SAND_SOURCE]; 'block: loop { for (dx, dy) in [(0, 1), (-1, 1), (1, 1)] { let next_pos = Coords { x: &pos.x + dx, y: &pos.y + dy, }; if (next_pos.x < self.bounds.minx) || (next_pos.x > self.bounds.maxx) || (next_pos.y > self.bounds.maxy) { break 'block; } if self.get(&next_pos) == &Terrain::Air { history.push(next_pos); pos = next_pos; continue 'block; } } self.set(&pos, Terrain::Sand); pos = { loop { let prev_pos = history[history.len() - 1]; match self.get(&prev_pos) { Terrain::Air | Terrain::SandSource => break prev_pos, _ => { let _ = history.pop(); continue; } }; } }; count += 1; } count } } #[derive(Clone, Debug)] struct GridBoundaries { minx: i32, maxx: i32, miny: i32, maxy: i32, } impl GridBoundaries { fn new() -> Self { GridBoundaries { minx: i32::max_value(), maxx: i32::min_value(), miny: 0, maxy: i32::min_value(), } } fn combine(&mut self, other: &GridBoundaries) { self.minx = i32::min(self.minx, other.minx); self.maxx = i32::max(self.maxx, other.maxx); self.miny = i32::min(self.miny, other.miny); self.maxy = i32::max(self.maxy, other.maxy); } fn fold(&self, other: &Coords) -> Self { let mut res = GridBoundaries::new(); res.minx = i32::min(self.minx, other.x); res.maxx = i32::max(self.maxx, other.x); res.miny = i32::min(self.miny, other.y); res.maxy = i32::max(self.maxy, other.y); res } } #[derive(Clone, Debug, PartialEq, Eq)] struct RockStructure(Vec); impl ParseError for Error { fn from_error_kind(input: I, kind: NomErrorKind) -> Self { let errors = vec![(input, ErrorKind::Nom(kind))]; Self { errors } } fn append(input: I, kind: NomErrorKind, mut other: Self) -> Self { other.errors.push((input, ErrorKind::Nom(kind))); other } } impl ContextError for Error { fn add_context(input: I, ctx: &'static str, mut other: Self) -> Self { other.errors.push((input, ErrorKind::Context(ctx))); other } } fn parse_coord(input: &str) -> Result { map(tuple((terminated(i32, tag(",")), i32)), |(x, y)| Coords { x, y, })(input) } fn parse_line(input: &str) -> Result { map(many1(terminated(parse_coord, opt(tag(" -> ")))), |v| { RockStructure(v) })(input) } fn main() { let args = Cli::parse(); let file = File::open(&args.file).unwrap(); let reader = BufReader::new(file); let mut bounds = GridBoundaries::new(); let geography = reader .lines() .map(|l| parse_line(l.unwrap().as_str()).unwrap().1) .scan(&mut bounds, |state, rstruct| { state.combine( &rstruct .0 .iter() .fold(GridBoundaries::new(), |acc, x| acc.fold(x)), ); Some(rstruct) }) .collect_vec(); let mut grid = Grid::new(bounds); grid.set(&SAND_SOURCE, Terrain::SandSource); grid.populate(geography); assert_eq!(grid.get(&SAND_SOURCE), &Terrain::SandSource); let res = grid.run(); println!("{res:?}"); }