#![feature(test)] use core::fmt; use std::{str::FromStr, cmp::{min, max}}; use anyhow::Result; use aoc::Solver; #[cfg(feature = "gif")] use gif::{Encoder, Repeat, Frame}; #[cfg(feature = "gif")] use std::{borrow::Cow, fs::File}; // -- Runners -- fn main() -> Result<()> { Day::solve() } #[cfg(test)] mod tests { use super::*; #[test] fn part1_test1() -> Result<()> { Day::test(Day::part1, "test-1", 24) } #[test] fn part1_solution() -> Result<()> { Day::test(Day::part1, "input", 1406) } #[test] fn part2_test1() -> Result<()> { Day::test(Day::part2, "test-1", 93) } #[test] fn part2_solution() -> Result<()> { Day::test(Day::part2, "input", 20870) } // Benchmarks extern crate test; #[bench] #[ignore] fn part1_bench(b: &mut test::Bencher) { Day::benchmark(Day::part1, b) } #[bench] #[ignore] fn part2_bench(b: &mut test::Bencher) { Day::benchmark(Day::part2, b) } } #[derive(Debug)] struct Point { x: usize, y: usize, } impl fmt::Display for Point { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "({}, {})", self.x, self.y) } } impl FromStr for Point { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let (x, y) = s.split_once(",").unwrap(); let x = x.parse()?; let y = y.parse()?; Ok(Point{x, y}) } } #[derive(Debug)] struct Path (Vec); impl fmt::Display for Path { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (idx, point) in self.0.iter().enumerate() { write!(f, "{}", point)?; if idx+1 != self.0.len() { write!(f, " -> ")?; } } Ok(()) } } impl FromStr for Path { type Err = anyhow::Error; fn from_str(s: &str) -> Result { let points = s .split(" -> ") .flat_map(Point::from_str) .collect(); Ok(Path(points)) } } #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum Block { Air, Rock, Sand, Source, Void, } struct Cave { grid: Vec>, source: usize, size: (usize, usize), } impl<'a> Cave { // Also returns the width and height of the cave fn new(mut paths: Vec, floor: bool) -> Self { // The sand source is at 500, 0 // This means that xmax starts at 500 and we know ymin is 0 let mut source = 500; let (mut xmin, mut xmax, mut ymax) = (usize::MAX, source, 0); for path in paths.iter() { for point in path.0.iter() { xmin = min(xmin, point.x); xmax = max(xmax, point.x); ymax = max(ymax, point.y); } } if floor { ymax += 2; xmin = min(xmin, source - ymax); xmax = max(xmax, source + ymax); let path = Path(vec![Point{x: xmin, y: ymax}, Point{x: xmax, y: ymax}]); paths.push(path); } for path in paths.iter_mut() { for point in path.0.iter_mut() { point.x -= xmin; } } let width = xmax-xmin+1; let height = ymax+1; let mut grid = vec![vec![Block::Air; width]; height]; for path in paths { for line in path.0.windows(2) { let ystart = min(line[0].y, line[1].y); let yend = max(line[0].y, line[1].y)+1; for y in ystart..yend { let xstart = min(line[0].x, line[1].x); let xend = max(line[0].x, line[1].x)+1; for x in xstart..xend { grid[y][x] = Block::Rock; } } } } source -= xmin; grid[0][source] = Block::Source; Self { grid, source, size: (xmax-xmin+1, ymax+1) } } fn from_str(s: &str, floor: bool) -> Self { let paths = s .lines() .flat_map(Path::from_str) .collect(); Self::new(paths, floor) } fn check(&self, x: isize, y: isize) -> Block { if x < 0 || y < 0 || x >= self.size.0 as isize || y >= self.size.1 as isize { return Block::Void; } return self.grid[y as usize][x as usize]; } fn simulate_sand(&mut self) -> bool { let (mut x, mut y): (isize, isize) = (self.source as isize, -1); loop { let positions = vec![ (x, y+1), (x-1, y+1), (x+1, y+1), ]; let mut moved = false; for position in positions { match self.check(position.0, position.1) { // The sand can move into the position Block::Air | Block::Source => { // Update the coordinates x = position.0; y = position.1; // Mark that the sand has moved moved = true; // Simulate the next step break; }, // This position is occupied, check the next position Block::Rock | Block::Sand => continue, // The sand fell into the void, any sand we add will also fall into the void // Return false to signify this Block::Void => return false, } } // If the sand did not move, the sand has settled and we can return if !moved { let block = self.grid[y as usize][x as usize]; self.grid[y as usize][x as usize] = Block::Sand; // If we replaced the source block we are also done return block != Block::Source; } } } #[cfg(feature = "gif")] fn frame(&self) -> Cow<[u8]> { let buffer = self.grid.iter().flat_map(|line| line.iter().map(|block| match block { Block::Air => 0, Block::Rock => 1, Block::Sand => 2, Block::Source => 3, Block::Void => 4, }).collect::>()).collect::>(); Cow::from(buffer) } fn count_sand(&self) -> usize { self.grid.iter().flatten().filter(|&block| *block == Block::Sand).count() } } #[cfg(feature = "gif")] fn write_frame(encoder: &mut Encoder, cave: &Cave, delay: u16) { let mut frame = Frame::default(); frame.width = cave.size.0 as u16; frame.height = cave.size.1 as u16; frame.buffer = cave.frame(); frame.delay = delay; encoder.write_frame(&frame).unwrap(); } // -- Solution -- pub struct Day; impl aoc::Solver for Day { type Output1 = usize; type Output2 = usize; fn day() -> u8 { 14 } fn part1(input: &str) -> Self::Output1 { let mut cave = Cave::from_str(input, false); #[cfg(feature = "gif")] let color_map = &[0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xea, 0xc7, 0x99, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF]; #[cfg(feature = "gif")] let mut image = File::create("visualize/14/part1.gif").unwrap(); #[cfg(feature = "gif")] let mut encoder = Encoder::new(&mut image, cave.size.0 as u16, cave.size.1 as u16, color_map).unwrap(); #[cfg(feature = "gif")] encoder.set_repeat(Repeat::Infinite).unwrap(); #[cfg(feature = "gif")] write_frame(&mut encoder, &mut cave, 100); while cave.simulate_sand() { #[cfg(feature = "gif")] write_frame(&mut encoder, &mut cave, 1); } #[cfg(feature = "gif")] write_frame(&mut encoder, &mut cave, 1000); cave.count_sand() } fn part2(input: &str) -> Self::Output2 { let mut cave = Cave::from_str(input, true); #[cfg(feature = "gif")] let color_map = &[0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xea, 0xc7, 0x99, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF]; #[cfg(feature = "gif")] let mut image = File::create("visualize/14/part2.gif").unwrap(); #[cfg(feature = "gif")] let mut encoder = Encoder::new(&mut image, cave.size.0 as u16, cave.size.1 as u16, color_map).unwrap(); #[cfg(feature = "gif")] encoder.set_repeat(Repeat::Infinite).unwrap(); #[cfg(feature = "gif")] write_frame(&mut encoder, &mut cave, 100); while cave.simulate_sand() { #[cfg(feature = "gif")] write_frame(&mut encoder, &mut cave, 1); } #[cfg(feature = "gif")] write_frame(&mut encoder, &mut cave, 1000); cave.count_sand() } }