diff --git a/src/day15.rs b/src/day15.rs new file mode 100644 index 0000000..5aa1f3b --- /dev/null +++ b/src/day15.rs @@ -0,0 +1,172 @@ +use crate::util::maps::*; +use itertools::Itertools; +use pest::{ + iterators::{Pair, Pairs}, + Parser, +}; +use pest_derive::Parser; +use std::fmt::Display; + +const DOUBLE_WIDE: bool = true; + +pub fn day15() { + let (mut map, directions) = parse(&crate::input(15)); + if DOUBLE_WIDE { + map = double_wide_map(map); + } + + let (mut x, mut y, _) = map + .enumerate() + .find(|(_, _, &t)| t == Terrain::Robot) + .unwrap(); + + for direction in directions { + if let Some(new_map) = move_on_map(&map, x, y, direction) { + map = new_map; + (x, y) = map.travel(x, y, direction).unwrap(); + } + } + + let sum_gps: usize = map + .enumerate() + .filter(|(_, _, &t)| { + t == Terrain::Block(None) || t == Terrain::Block(Some(Direction::East)) + }) + .map(|(x, y, _)| y * 100 + x) + .sum(); + + println!("{}", map); + println!("Sum of GPS Coordinates: {}", sum_gps); +} + +fn move_on_map( + map: &Map, + x: usize, + y: usize, + direction: Direction, +) -> Option> { + let cascading_move = || { + let (nx, ny) = map.travel(x, y, direction)?; + let mut downstream = move_on_map(map, nx, ny, direction)?; + downstream.set(nx, ny, map.get(x, y).unwrap()); + downstream.set(x, y, Terrain::Empty); + Some(downstream) + }; + + match map.get(x, y).unwrap() { + Terrain::Wall => None, + Terrain::Empty => Some(map.clone()), + Terrain::Block(None) => (cascading_move)(), + Terrain::Block(Some(d)) if d.is_parallel(direction) => (cascading_move)(), + Terrain::Robot => (cascading_move)(), + Terrain::Block(Some(Direction::West)) => { + let (partner_x, partner_y) = map.travel(x, y, Direction::West).unwrap(); + move_on_map(map, partner_x, partner_y, direction) + } + Terrain::Block(Some(Direction::East)) => { + let (partner_x, partner_y) = map.travel(x, y, Direction::East).unwrap(); + + let mut downstream = (cascading_move)()?; + let (partner_nx, partner_ny) = map.travel(partner_x, partner_y, direction).unwrap(); + downstream = move_on_map(&downstream, partner_nx, partner_ny, direction)?; + downstream.set( + partner_nx, + partner_ny, + map.get(partner_x, partner_y).unwrap(), + ); + downstream.set(partner_x, partner_y, Terrain::Empty); + + Some(downstream) + } + Terrain::Block(Some(_)) => unimplemented!("North/South blocks are not implemented."), + } +} + +fn double_wide_map(map: Map) -> Map { + let new_map = map + .iter_rows() + .map(|row| { + row.iter() + .flat_map(|&t| match t { + Terrain::Wall => vec![Terrain::Wall; 2], + Terrain::Empty => vec![Terrain::Empty; 2], + Terrain::Block(None) => vec![ + Terrain::Block(Some(Direction::East)), + Terrain::Block(Some(Direction::West)), + ], + Terrain::Robot => vec![Terrain::Robot, Terrain::Empty], + Terrain::Block(Some(_)) => unreachable!(), + }) + .collect() + }) + .collect(); + Map::from_2d_vec(new_map) +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +enum Terrain { + Wall, + Empty, + Block(Option), + Robot, +} + +impl Display for Terrain { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let s = match self { + Terrain::Robot => "@", + Terrain::Wall => "#", + Terrain::Empty => ".", + Terrain::Block(Some(Direction::East)) => "[", + Terrain::Block(Some(Direction::West)) => "]", + Terrain::Block(_) => "O", + }; + f.write_str(s) + } +} + +#[derive(Parser)] +#[grammar = "grammars/day15.pest"] +struct Day15Parser {} + +fn parse(input: &str) -> (Map, Vec) { + let (map, directions) = Day15Parser::parse(Rule::input, input) + .unwrap() + .nth(0) + .unwrap() + .into_inner() + .map(Pair::<'_, Rule>::into_inner) + .collect_tuple() + .unwrap(); + + (parse_map(map), parse_directions(directions)) +} + +fn parse_map(input: Pairs<'_, Rule>) -> Map { + let map_vec = input + .map(Pair::<'_, Rule>::into_inner) + .map(|row| { + row.map(|terrain| match terrain.as_rule() { + Rule::wall => Terrain::Wall, + Rule::empty => Terrain::Empty, + Rule::block => Terrain::Block(None), + Rule::robot => Terrain::Robot, + _ => unreachable!(), + }) + .collect() + }) + .collect(); + Map::from_2d_vec(map_vec) +} + +fn parse_directions(input: Pairs<'_, Rule>) -> Vec { + input + .map(|d| match d.as_rule() { + Rule::north => Direction::North, + Rule::west => Direction::West, + Rule::east => Direction::East, + Rule::south => Direction::South, + _ => unreachable!(), + }) + .collect() +} diff --git a/src/grammars/day15.pest b/src/grammars/day15.pest new file mode 100644 index 0000000..cf94863 --- /dev/null +++ b/src/grammars/day15.pest @@ -0,0 +1,19 @@ +_WHITESPACE = _{ " " } +NEWLINE = _{ "\n" } + +wall = @{ "#" } +empty = @{ "." } +block = @{ "O" } +robot = @{ "@" } +space = _{ wall | empty | block | robot } +row = { space+ ~ NEWLINE+ } +map = { row+ } + +north = @{ "^" } +west = @{ "<" } +east = @{ ">" } +south = @{ "v" } +direction_list = { ((north | west | east | south) ~ NEWLINE*)+ } + +eoi = _{ !ANY } +input = { SOI ~ map ~ direction_list ~ eoi } diff --git a/src/main.rs b/src/main.rs index 4d70f0f..8f686d5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -26,10 +26,12 @@ mod day11; mod day12; #[allow(dead_code)] mod day13; +#[allow(dead_code)] mod day14; +mod day15; fn main() { - day14::day14(); + day15::day15(); } pub fn input(day: u8) -> String { diff --git a/src/util/maps.rs b/src/util/maps.rs index 55072f8..88b21ff 100644 --- a/src/util/maps.rs +++ b/src/util/maps.rs @@ -1,6 +1,8 @@ +use std::fmt::Display; + use itertools::Itertools; -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum Direction { North, South, @@ -12,12 +14,25 @@ impl Direction { pub fn all_variants() -> Vec { vec![Self::North, Self::East, Self::West, Self::South] } + + pub fn opposite(self) -> Self { + match self { + Self::North => Self::South, + Self::East => Self::West, + Self::West => Self::East, + Self::South => Self::North, + } + } + + pub fn is_parallel(self, other: Self) -> bool { + self == other || self == other.opposite() + } } #[derive(Clone)] pub struct Map where - T: Copy, + T: Copy + Display, { pub map: Vec>, } @@ -36,7 +51,7 @@ impl Map { impl Map where - T: Copy, + T: Copy + Display, { pub fn from_2d_vec(map: Vec>) -> Self { Self { map } @@ -69,13 +84,23 @@ where } } + pub fn get(&self, x: usize, y: usize) -> Option { + Some(*self.map.get(y)?.get(x)?) + } + pub fn travel_get(&self, x: usize, y: usize, direction: Direction) -> Option { let (nx, ny) = self.travel(x, y, direction)?; self.get(nx, ny) } - pub fn get(&self, x: usize, y: usize) -> Option { - Some(*self.map.get(y)?.get(x)?) + pub fn travel_and_get( + &self, + x: usize, + y: usize, + direction: Direction, + ) -> Option<(usize, usize, T)> { + let (nx, ny) = self.travel(x, y, direction)?; + Some((nx, ny, self.get(nx, ny)?)) } pub fn set(&mut self, x: usize, y: usize, value: T) { @@ -98,6 +123,10 @@ where self.map.iter().flat_map(|row| row.iter()) } + pub fn iter_rows(&self) -> impl Iterator> { + self.map.iter() + } + pub fn coordinates(&self) -> impl Iterator { (0..self.width()).cartesian_product(0..self.height()) } @@ -106,3 +135,21 @@ where self.coordinates().map(|(x, y)| (x, y, &self.map[y][x])) } } + +impl Display for Map +where + T: Copy + Display, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut final_string = "".to_string(); + for row in self.map.iter() { + let s = row.iter().fold("".to_string(), |mut acc, e| { + acc.push_str(&format!("{}", e)); + acc + }); + final_string.push_str("\n"); + final_string.push_str(&s); + } + f.write_str(&final_string) + } +}