/// --- Part Two ---
///
/// You realize you misread the scan. There isn't an endless void at the bottom of the scan -
/// there's floor, and you're standing on it!
///
/// You don't have time to scan the floor, so assume the floor is an infinite horizontal line with
/// a y coordinate equal to two plus the highest y coordinate of any point in your scan.
///
/// In the example above, the highest y coordinate of any point is 9, and so the floor is at y=11.
/// (This is as if your scan contained one extra rock path like -infinity,11 -> infinity,11.) With
/// the added floor, the example above now looks like this:
///
/// ```
/// ...........+........
/// ....................
/// ....................
/// ....................
/// .........#...##.....
/// .........#...#......
/// .......###...#......
/// .............#......
/// .............#......
/// .....#########......
/// ....................
/// <-- etc #################### etc -->
/// ```
///
/// To find somewhere safe to stand, you'll need to simulate falling sand until a unit of sand
/// comes to rest at 500,0, blocking the source entirely and stopping the flow of sand into the
/// cave. In the example above, the situation finally looks like this after 93 units of sand come
/// to rest:
///
/// ```
/// ............o............
/// ...........ooo...........
/// ..........ooooo..........
/// .........ooooooo.........
/// ........oo#ooo##o........
/// .......ooo#ooo#ooo.......
/// ......oo###ooo#oooo......
/// .....oooo.oooo#ooooo.....
/// ....oooooooooo#oooooo....
/// ...ooo#########ooooooo...
/// ..ooooo.......ooooooooo..
/// #########################
/// ```
///
/// Using your scan, simulate the falling sand until the source of the sand becomes blocked. How
/// many units of sand come to rest?
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 };
const FLOOR_OFFSET: usize = 2;
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)
{
continue;
}
if self.get(&next_pos) == &Terrain::Air {
history.push(next_pos);
pos = next_pos;
continue 'block;
}
}
count += 1;
if pos == SAND_SOURCE {
break '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
}
}
#[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 mut 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();
bounds.maxy = bounds.maxy + FLOOR_OFFSET as i32;
let max_x_len = 2 * bounds.maxy - 1;
let minx = SAND_SOURCE.x - (max_x_len - 1) / 2;
let maxx = SAND_SOURCE.x + (max_x_len - 1) / 2;
assert!(minx < bounds.minx);
assert!(maxx > bounds.maxx);
bounds.minx = minx;
bounds.maxx = maxx;
geography.push(RockStructure(vec![
Coords {
x: minx,
y: bounds.maxy,
},
Coords {
x: maxx,
y: bounds.maxy,
},
]));
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:?}");
}