diff --git a/.aoc_tiles/tiles/2024/15.png b/.aoc_tiles/tiles/2024/15.png index 607c129..57ce328 100644 Binary files a/.aoc_tiles/tiles/2024/15.png and b/.aoc_tiles/tiles/2024/15.png differ diff --git a/README.md b/README.md index 983e72b..7b3f43f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- 2024 - 28 ⭐ - Rust + 2024 - 29 ⭐ - Rust

@@ -44,4 +44,7 @@ + + + diff --git a/src/day15.rs b/src/day15.rs new file mode 100644 index 0000000..d4b6810 --- /dev/null +++ b/src/day15.rs @@ -0,0 +1,292 @@ +use std::{ + fmt::Display, + io::{BufRead, Cursor, Lines}, + iter, + str::FromStr, +}; + +use aoc_runner_derive::aoc; +use grid::{AsCoord2d, Coord2d, Grid}; +use itertools::{rev, Itertools}; + +struct Warehouse { + map: Grid, + robot_pos: Coord2d, +} + +impl Display for Warehouse { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.map.fmt(f) + } +} + +impl Warehouse { + fn step_robot(&mut self, m: Move) { + match m { + Move::Left => { + let to_left = &self.map.row(self.robot_pos.y()).unwrap()[0..self.robot_pos.x() as usize]; + let left_chunks = to_left + .chunk_by(|a, b| a == b || (*a == b'[' && *b == b']')) + .collect_vec(); + match left_chunks.last().unwrap().last().unwrap() { + b'.' => { + self.map + .swap(&self.robot_pos, (self.robot_pos.x() - 1, self.robot_pos.y())); + self.robot_pos.x -= 1 + } + b'O' | b'[' | b']' => { + if left_chunks[left_chunks.len() - 2].last().unwrap() == &b'.' { + let y = self.robot_pos.y(); + // swap the whole chunk left + for x_target in self.robot_pos.x() - left_chunks.last().unwrap().len() as i64 + ..=self.robot_pos.x() as i64 + { + self.map.swap((x_target, y), (x_target - 1, y)); + } + self.robot_pos.x -= 1; + } + } + b'#' => {} + c => panic!("unexpected char {}", c), + } + } + Move::Right => { + let to_right = + &self.map.row(self.robot_pos.y()).unwrap()[self.robot_pos.x() as usize + 1..self.map.width()]; + let right_chunks = to_right + .chunk_by(|a, b| a == b || (*a == b'[' && *b == b']')) + .collect_vec(); + match right_chunks[0][0] { + b'.' => { + self.map + .swap(&self.robot_pos, (self.robot_pos.x() + 1, self.robot_pos.y())); + self.robot_pos.x += 1 + } + b'O' | b'[' | b']' => { + if right_chunks[1][0] == b'.' { + let y = self.robot_pos.y(); + // swap the whole chunk right + for x_target in + (self.robot_pos.x() + 1..=self.robot_pos.x() + 1 + right_chunks[0].len() as i64).rev() + { + self.map.swap((x_target, y), (x_target - 1, y)); + } + self.robot_pos.x += 1; + } + } + b'#' => {} + c => panic!("unexpected char {}", c), + } + } + Move::Up => { + let to_up = &self.map.col(self.robot_pos.x()).unwrap()[0..self.robot_pos.y() as usize]; + let up_chunks = to_up.chunk_by(|a, b| a == b).collect_vec(); + match up_chunks.last().unwrap().last().unwrap() { + b'.' => { + self.map + .swap(&self.robot_pos, (self.robot_pos.x(), self.robot_pos.y() - 1)); + self.robot_pos.y -= 1 + } + b'O' => { + if **up_chunks[up_chunks.len() - 2].last().unwrap() == b'.' { + let x = self.robot_pos.x(); + // swap the whole chunk left + for y_target in + self.robot_pos.y() - up_chunks.last().unwrap().len() as i64..=self.robot_pos.y() as i64 + { + self.map.swap((x, y_target), (x, y_target - 1)); + } + self.robot_pos.y -= 1; + } + } + b'#' => {} + c => panic!("unexpected char {}", c), + } + } + Move::Down => { + let to_down = + &self.map.col(self.robot_pos.x()).unwrap()[self.robot_pos.y() as usize + 1..self.map.height()]; + let down_chunks = to_down.chunk_by(|a, b| a == b).collect_vec(); + match down_chunks[0][0] { + b'.' => { + self.map + .swap(&self.robot_pos, (self.robot_pos.x(), self.robot_pos.y() + 1)); + self.robot_pos.y += 1; + } + b'O' => { + if *down_chunks[1][0] == b'.' { + let x = self.robot_pos.x(); + // swap the whole chunk down + for y_target in + (self.robot_pos.y() + 1..=self.robot_pos.y() + 1 + down_chunks[0].len() as i64).rev() + { + self.map.swap((x, y_target), (x, y_target - 1)); + } + self.robot_pos.y += 1; + } + } + b'#' => {} + c => panic!("unexpected char {}", c), + } + } + } + } + + fn embiggen(&mut self) { + let new_lines = (0..self.map.height()) + .map(|r| self.map.row(r as i64).unwrap()) + .map(|row| { + row.iter() + .flat_map(|c| match c { + b'#' => ['#', '#'], + b'O' => ['[', ']'], + b'.' => ['.', '.'], + b'@' => ['@', '.'], + c => panic!("unexpected character {}", c), + }) + .collect::() + }) + .join("\n"); + self.map = Grid::from(Cursor::new(new_lines.as_str())); + self.robot_pos = self.map.find(&b'@').unwrap().to_coord(); + } +} + +#[derive(Debug)] +enum Move { + Left, + Right, + Up, + Down, +} + +impl Display for Move { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Left => f.write_str("Left"), + Self::Right => f.write_str("Right"), + Self::Up => f.write_str("Up"), + Self::Down => f.write_str("Down"), + } + } +} + +impl From for Move { + fn from(c: char) -> Self { + match c { + '<' => Self::Left, + '>' => Self::Right, + '^' => Self::Up, + 'v' => Self::Down, + c => panic!("invalid move {}", c), + } + } +} + +#[derive(Debug)] +struct MovePlan(Vec); + +impl FromStr for MovePlan { + type Err = Box; + fn from_str(s: &str) -> Result { + Ok(MovePlan( + s.chars().filter(|c| *c != '\n').map(|c| Move::from(c)).collect(), + )) + } +} + +fn parse(input: &str) -> (Warehouse, MovePlan) { + let lines = input.lines().collect_vec(); + let parts = lines.split(|l| l.is_empty()).map(|ls| ls.join("\n")).collect_vec(); + let map: Grid = parts[0].parse().unwrap(); + let wh = Warehouse { + robot_pos: map.find(&b'@').unwrap().to_coord(), + map, + }; + let moves = parts[1].parse().unwrap(); + + (wh, moves) +} + +#[aoc(day15, part1)] +pub fn part1(input: &str) -> i64 { + let (mut wh, moves) = parse(input); + // println!("map:\n {}\nmoves: {:?}", wh, moves); + for m in moves.0 { + // println!("{}", m); + wh.step_robot(m); + // println!("{}", wh); + } + wh.map + .data + .iter() + .enumerate() + .filter(|(i, v)| **v == b'O') + .map(|(i, _)| wh.map.coord(i as i64).unwrap().y() * 100 + wh.map.coord(i as i64).unwrap().x()) + .sum() +} + +#[aoc(day15, part2)] +pub fn part2(input: &str) -> i64 { + let (mut wh, moves) = parse(input); + wh.embiggen(); + + let moves: MovePlan = ">>>>>>>>>>>>".parse().unwrap(); + + println!("{}", wh); + for m in moves.0 { + println!("{}", m); + wh.step_robot(m); + println!("{}", wh); + } + + 0 +} + +#[cfg(test)] +mod tests { + use super::*; + const EXAMPLE1: &str = "######## +#..O.O.# +##@.O..# +#...O..# +#.#.O..# +#...O..# +#......# +######## + +<^^>>>vv>v<<"; + const EXAMPLE2: &str = "########## +#..O..O.O# +#......O.# +#.OO..O.O# +#..O@..O.# +#O#..O...# +#O..O..O.# +#.OO.O.OO# +#....O...# +########## + +^v>^vv^v>v<>v^v<<><>>v^v^>^<<<><^ +vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<^<^^>>>^<>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^v^^<^^vv< +<>^^^^>>>v^<>vvv^>^^^vv^^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ +^><^><>>><>^^<<^^v>>><^^>v>>>^v><>^v><<<>vvvv>^<><<>^>< +^>><>^v<><^vvv<^^<><^v<<<><<<^^<^>>^<<<^>>^v^>>^v>vv>^<<^v<>><<><<>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ +<><^^>^^^<>^vv<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> +^^>vv<^v^v^<>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<>< +v^^>>><<^^<>>^v^v^<<>^<^v^v><^<<<><<^vv>>v>v^<<^"; + + #[test] + fn part1_example() { + assert_eq!(part1(EXAMPLE1), 2028); + assert_eq!(part1(EXAMPLE2), 10092); + } + + #[test] + fn part2_example() { + // assert_eq!(part2(EXAMPLE1), 0); + assert_eq!(part2(EXAMPLE2), 9021); + } +} diff --git a/utils/grid/lib.rs b/utils/grid/lib.rs index 9e56c96..99baf3c 100644 --- a/utils/grid/lib.rs +++ b/utils/grid/lib.rs @@ -152,7 +152,7 @@ impl Grid { pub fn height(&self) -> usize { self.data.len() / self.width() } - fn pos(&self, c: &C) -> i64 { + pub fn pos(&self, c: &C) -> i64 { c.y() * self.width + c.x() } pub fn coord(&self, pos: i64) -> Option<(i64, i64)> { @@ -203,13 +203,21 @@ impl Grid { } } pub fn row(&self, y: i64) -> Option<&[T]> { - if y < self.height() as i64 { + if y < self.height() as i64 && y >= 0 { Some(&self.data[self.pos(&(0, y)) as usize..self.pos(&(self.width, y)) as usize]) } else { None } } + pub fn col(&self, x: i64) -> Option> { + if x < self.width() as i64 && x >= 0 { + Some((0..self.height()).map(|y| self.get(&(x, y as i64)).unwrap()).collect()) + } else { + None + } + } + pub fn find(&self, haystack: &T) -> Option<(i64, i64)> { self.coord( self.data @@ -231,6 +239,13 @@ impl Grid { } } + pub fn swap(&mut self, a: A, b: B) { + match (self.valid_pos(&a), self.valid_pos(&b)) { + (Some(a), Some(b)) => self.data.swap(a, b), + _ => {} + } + } + // fn window_compare_impl(&self, needle: &[T]) -> Vec<(i64, i64)> { // if (self.width as usize) < needle.len() { // return Vec::new();