332 lines
8.8 KiB
Rust
332 lines
8.8 KiB
Rust
#![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<Self, Self::Err> {
|
|
let (x, y) = s.split_once(",").unwrap();
|
|
let x = x.parse()?;
|
|
let y = y.parse()?;
|
|
|
|
Ok(Point{x, y})
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Path (Vec<Point>);
|
|
|
|
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<Self, Self::Err> {
|
|
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<Vec<Block>>,
|
|
source: usize,
|
|
size: (usize, usize),
|
|
}
|
|
|
|
impl<'a> Cave {
|
|
// Also returns the width and height of the cave
|
|
fn new(mut paths: Vec<Path>, 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::<Vec<_>>()).collect::<Vec<u8>>();
|
|
|
|
Cow::from(buffer)
|
|
}
|
|
|
|
fn count_sand(&self) -> usize {
|
|
self.grid.iter().flatten().filter(|&block| *block == Block::Sand).count()
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "gif")]
|
|
fn write_frame<T: std::io::Write>(encoder: &mut Encoder<T>, 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()
|
|
}
|
|
}
|