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();