diff --git a/day14/Cargo.lock b/day14/Cargo.lock index 2914dfa..836f0fd 100644 --- a/day14/Cargo.lock +++ b/day14/Cargo.lock @@ -26,6 +26,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "atty" version = "0.2.14" @@ -177,6 +183,9 @@ checksum = "db6d7e329c562c5dfab7a46a2afabc8b987ab9a4834c9d1ca04dc54c1546cef8" [[package]] name = "linebuffer" version = "0.1.1" +dependencies = [ + "arrayvec", +] [[package]] name = "log" diff --git a/day14/src/grid.rs b/day14/src/grid.rs index e8c3f72..fa228b2 100644 --- a/day14/src/grid.rs +++ b/day14/src/grid.rs @@ -1,3 +1,180 @@ //! Grid of lines and sands use super::*; +use std::{ + collections::BTreeMap, + borrow::Cow, + fmt, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Default, Copy)] +#[repr(u8)] +pub enum Cell +{ + #[default] + Air = b'.', + Wall = b'#', + Settled = b'O', + Falling = b'~', + Source = b'+', +} + +impl From for u8 +{ + #[inline(always)] + fn from(from: Cell) -> Self + { + from as u8 + } +} + +impl fmt::Display for Cell +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{}", u8::from(*self) as char) + } +} + + +#[derive(Debug, Clone, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Grid +{ + map: BTreeMap, +} + +impl Grid +{ + #[inline] + pub const fn new() -> Self + { + Self { + map: BTreeMap::new(), + } + } + + #[inline(always)] + pub fn insert_single(&mut self, point: Point, cell: Cell) -> Option + { + self.map.insert(point, cell) + } + + #[inline] + pub fn draw_line_with(&mut self, line: Line, mut with: F) + where F: for<'a> FnMut(&'a Point) -> Cell + { + for point in line.unroll() { + let cell = with(&point); + self.map.insert(point, cell); + } + } + + #[inline] + pub fn draw_line(&mut self, line: Line, cell: Cell) + { + self.draw_line_with(line, move |_| cell) + } + + #[inline] + pub fn draw_lines_with(&mut self, line: I, mut with: F) + where F: for<'a> FnMut(&'a Point) -> Cell, + I: IntoIterator + { + for point in line.into_iter().map(|x| x.normalised().unroll()).flatten() { + let cell = with(&point); + self.map.insert(point, cell); + } + } + + /// Draw these lines with this cell + #[inline] + pub fn draw_lines(&mut self, lines: impl IntoIterator, cell: Cell) + { + self.draw_lines_with(lines, move |_| cell) + } + + /// Add the source to the grid + #[inline(always)] + pub fn draw_source(&mut self, source: Point) + { + self.map.insert(source, Cell::Source); + } +} + +/// Create a new sparse grid, returns the point. +/// +/// A sparse grid does not draw air, only the specified `walls`. +#[inline] +pub fn sparse_grid(walls: Lines) -> (grid::Grid, Point) +{ + let mut grid = grid::Grid::new(); + // Add source + grid.draw_source(ORIGIN_POINT); + + // Draw walls + grid.draw_lines(walls, Cell::Wall); + + (grid, ORIGIN_POINT) +} + +// Non-drawing grid functions +impl Grid +{ + /// Number of points in the grid + #[inline] + pub fn len(&self) -> usize + { + self.map.len() + } + + /// Find the highest point in the collection + #[inline(always)] + pub fn find_high_bound(&self) -> Point + { + let x = self.map.keys().max_by(|a, b| u64::cmp(&a.x, &b.x)).map(|x| x.x).unwrap_or(0); + let y = self.map.keys().max_by(|a, b| u64::cmp(&a.y, &b.y)).map(|y| y.y).unwrap_or(0); + Point{ x, y } + } + + /// Find the lowest point in the collection + #[inline(always)] + pub fn find_low_bound(&self) -> Point + { + let x = self.map.keys().min_by(|a, b| u64::cmp(&a.x, &b.x)).map(|x| x.x).unwrap_or(0); + let y = self.map.keys().min_by(|a, b| u64::cmp(&a.y, &b.y)).map(|y| y.y).unwrap_or(0); + Point{ x, y } + } + + /// Find the low and high bound as a flattened line + #[inline] + pub fn find_bound(&self) -> Line + { + let start = self.find_low_bound(); + let end = self.find_high_bound(); + Line{ start, end } + } + + /// Iterate over a sparse grid, bounded by `bound`, any not set cells will be drawn with air. + #[inline] + pub fn iterate_sparse<'g>(&'g self, bound: Line) -> impl Iterator + 'g + { + const AIR: Cell = Cell::Air; + //let mut grid = start.map.iter().fuse().peekable(); + bound.normalised().unroll().into_iter() + .map(move |point| { + if let Some(cell) = self.map.get(&point) { + // Contains the point, return that reference + return cell; + } + /* + if let Some(&(next_wall, cell)) = grid.peek() { + if next_wall == &point { + // Take this point off the iterator if it matches + return grid.next().unwrap().1; + } + }*/ + &AIR + }) + } +} diff --git a/day14/src/main.rs b/day14/src/main.rs index 51518d3..818b645 100644 --- a/day14/src/main.rs +++ b/day14/src/main.rs @@ -7,6 +7,7 @@ use std::{ io, iter, path::Path, + fmt, }; use color_eyre::{ @@ -29,14 +30,27 @@ const _:() = { mod grid; +/// Base coordinate +pub type Coord = u64; + /// A point is a vector #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] #[repr(simd)] -struct Point { - pub x: u64, - pub y: u64, +pub struct Point { + pub x: Coord, + pub y: Coord, } +impl fmt::Display for Point +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{},{}", self.x, self.y) + } +} + + impl str::FromStr for Point { type Err = ::Err; @@ -52,14 +66,86 @@ impl str::FromStr for Point /// A `Line` is potentially two X any Ys #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)] -struct Line { +pub struct Line { start: Point, end: Point, } +impl fmt::Display for Line +{ + #[inline] + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f, "{} -> {}", self.start, self.end) + } +} + + +impl From for (Coord, Coord) +{ + #[inline] + fn from(from: Point) -> Self + { + (from.x, from.y) + } +} + +impl From<(Coord, Coord)> for Point +{ + #[inline(always)] + fn from((x,y): (Coord, Coord)) -> Self + { + Self {x,y} + } +} + + +impl Line { + + fn normalised(self) -> Self + { + let x = std::cmp::min(self.start.x, self.end.x); + let y = std::cmp::min(self.start.y, self.end.y); + let start = Point {x,y}; + + let x = std::cmp::max(self.start.x, self.end.x); + let y = std::cmp::max(self.start.y, self.end.y); + let end = Point {x,y}; + + Self { + start, end + } + } + #[inline] + fn normalise(&mut self) + { + *self = self.clone().normalised(); + } + #[inline] + pub fn over_horizontal(&self) -> impl IntoIterator + 'static + { + let x = self.start.x; + let y = self.start.y; + (self.start.x..=self.end.x).map(move |x| (x, y).into()) + } + #[inline] + pub fn over_vertical(&self) -> impl IntoIterator + 'static + { + let x = self.start.x; + (self.start.y..=self.end.y).map(move |y| (x, y).into()) + } + + #[inline] + pub fn unroll(&self) -> impl IntoIterator + 'static + { + self.over_horizontal().into_iter() + .chain(self.over_vertical().into_iter()) + } +} + /// A single parsed line into `Line`s. #[derive(Debug, Clone, Default)] -struct Lines { +pub struct Lines { lines: Vec, } @@ -75,6 +161,28 @@ impl IntoIterator for Lines } } +impl FromIterator for Lines +{ + #[inline] + fn from_iter>(lines: I) -> Self + { + Self { + lines: Self::flattening(lines).into_iter().collect(), + } + } +} + +impl FromIterator for Lines +{ + #[inline] + fn from_iter>(lines: I) -> Self + { + Self { + lines: lines.into_iter().collect() + } + } +} + impl Lines { #[inline] @@ -84,16 +192,16 @@ impl Lines { self } #[inline(always)] - pub fn flattening<'a>(lines: impl IntoIterator + 'a) -> impl IntoIterator + 'a + pub fn flattening<'a, T: 'a>(lines: impl IntoIterator + 'a) -> impl IntoIterator + 'a + where T: IntoIterator + { - lines.into_iter().map(|x| x.lines.into_iter()).flatten() + lines.into_iter().map(|x| x.into_iter()).flatten() } #[inline] pub fn flatten(lines: impl IntoIterator) -> Self { - Self { - lines: Self::flattening(lines).into_iter().collect(), - } + lines.into_iter().collect() } } @@ -190,29 +298,62 @@ fn init() -> eyre::Result<()> Ok(()) } +fn draw_grid(mut to: W, bound: Line, grid: &grid::Grid) -> io::Result<()> +{ + for cells in grid.iterate_sparse(bound).chunk((bound.end.x - bound.start.x) as usize) + { + for cell in cells { + write!(&mut to, "{}", cell)?; + } + write!(&mut to, "\n")?; + } + Ok(()) +} + +/// Simulate the grid falling from `origin` in `grid`. +fn simulate(grid: &mut grid::Grid, origin: &Point) -> eyre::Result<()> +{ + + Ok(()) +} + fn main() -> eyre::Result<()> { init().wrap_err("Failed to install handlers and hooks")?; - // Input - let input = load_input_from(INPUT_FILENAME) - .wrap_err("Failed to open input file")?; + // Parsed input + let input = { + // Input file + let input = load_input_from(INPUT_FILENAME) + .wrap_err("Failed to open input file")?; - // Parse input - let input = Input::parse(input) - .wrap_err("Failed to parse input")?; + // Parse input + Input::parse(input) + .wrap_err("Failed to parse input")? + }; if cfg!(feature="status") { debug!("Read {} zipped lines from input", input.zipped_len()); trace!("Input is:\n```{input:#?}```"); } + + //Draw lines into grid before starting simulation of sand from `origin`. + let (mut grid, origin) = grid::sparse_grid(input.all); - todo!("Deconstruction, draw, and simulation from `ORIGIN_POINT`"); - //TODO: Deconstruct each `Line` into the horizontal and vertical movements + debug!("Read {} unzipped lines", grid.len()); - //TODO: Draw lines into grid before starting simulation of sand from `ORIGIN_POINT`. + //TODO: Simulate falling, adding and counting each stopped particle until the sand drops below the bottom of the lowest vertical line. + simulate(&mut grid, &origin) + .wrap_err("Failed to simulate grid")?; - //TODO: Simulate falling, adding and counting each stopped particle until the sand drops below the bottom of the lowest vertical line. + // Draw grid + if cfg!(feature="status") + { + let bound = grid.find_bound(); + debug!("Current map (bound: {}):", bound); + draw_grid(io::stderr().lock(), bound, &grid).wrap_err("Failed to draw grid to stderr")?; + } + Ok(()) }