diff --git a/aoc_2024/src/day15.rs b/aoc_2024/src/day15.rs new file mode 100644 index 0000000..ab1f854 --- /dev/null +++ b/aoc_2024/src/day15.rs @@ -0,0 +1,417 @@ +use aoc_runner_derive::{aoc, aoc_generator}; + +use hashbrown::HashSet; +use nom::branch::alt; +use nom::character::complete::{multispace0, one_of}; +use nom::combinator::map; +use nom::error::Error; +use nom::multi::many1; +use nom::sequence::preceded; +use nom::{character::complete::newline, multi::separated_list0, sequence::separated_pair}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Cell { + Wall, + Empty, + Box, + Sub, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum Cell2 { + Wall, + Empty, + BoxLeft, + BoxRight, + Sub, +} + +type Input = (Vec>, Vec<(i32, i32)>); + +#[aoc_generator(day15)] +fn parse(input: &str) -> Input { + separated_pair( + separated_list0( + newline::<&str, Error<&str>>, + many1(map(one_of("#.O@"), |c| match c { + '#' => Cell::Wall, + '.' => Cell::Empty, + 'O' => Cell::Box, + '@' => Cell::Sub, + _ => panic!("Invalid cell"), + })), + ), + multispace0, + many1(map( + alt((preceded(newline, one_of("^v<>")), one_of("^v<>"))), + |c| match c { + '^' => (0, -1), + 'v' => (0, 1), + '<' => (-1, 0), + '>' => (1, 0), + _ => panic!("Invalid direction"), + }, + )), + )(input) + .unwrap() + .1 +} + +#[aoc(day15, part1)] +fn part1(input: &Input) -> usize { + let mut map = input.0.clone(); + let steps = input.1.clone(); + let (mut x, mut y): (usize, usize) = map + .iter() + .enumerate() + .find_map(|(y, row)| { + row.iter().enumerate().find_map(|(x, cell)| match cell { + Cell::Sub => Some((x, y)), + _ => None, + }) + }) + .unwrap(); + + for step in steps { + let (dx, dy) = step; + let (nx, ny) = ((x as i32 + dx) as usize, (y as i32 + dy) as usize); + match map[ny][nx] { + Cell::Wall => continue, + Cell::Box => { + let (mut bx, mut by) = ((nx as i32 + dx) as usize, (ny as i32 + dy) as usize); + while map[by][bx] == Cell::Box { + (bx, by) = ((bx as i32 + dx) as usize, (by as i32 + dy) as usize); + } + match map[by][bx] { + Cell::Box => unreachable!(), + Cell::Sub => unreachable!(), + Cell::Empty => { + map[ny][nx] = Cell::Sub; + map[by][bx] = Cell::Box; + map[y][x] = Cell::Empty; + y = ny; + x = nx; + } + Cell::Wall => continue, + } + } + Cell::Empty => { + map[y][x] = Cell::Empty; + map[ny][nx] = Cell::Sub; + x = nx; + y = ny; + } + Cell::Sub => unreachable!(), + }; + } + + map.iter() + .enumerate() + .map(|(y, line)| { + line.iter() + .enumerate() + .map(|(x, cell)| match cell { + Cell::Box => x + 100 * y, + _ => 0, + }) + .sum::() + }) + .sum() +} + +#[aoc(day15, part2)] +fn part2(input: &Input) -> usize { + let mut map = input + .0 + .clone() + .iter() + .map(|line| { + line.iter() + .flat_map(|cell| match cell { + Cell::Wall => vec![Cell2::Wall, Cell2::Wall], + Cell::Empty => vec![Cell2::Empty, Cell2::Empty], + Cell::Box => vec![Cell2::BoxLeft, Cell2::BoxRight], + Cell::Sub => vec![Cell2::Sub, Cell2::Empty], + }) + .collect() + }) + .collect::>>(); + let steps = input.1.clone(); + let (mut x, mut y): (usize, usize) = map + .iter() + .enumerate() + .find_map(|(y, row)| { + row.iter().enumerate().find_map(|(x, cell)| match cell { + Cell2::Sub => Some((x, y)), + _ => None, + }) + }) + .unwrap(); + + // for line in &map { + // for cell in line { + // print!( + // "{}", + // match cell { + // Cell2::Wall => '#', + // Cell2::Empty => ' ', + // Cell2::BoxLeft => '[', + // Cell2::BoxRight => ']', + // Cell2::Sub => '@', + // } + // ); + // } + // println!(); + // } + + for step in steps { + let (dx, dy) = step; + let (nx, ny) = ((x as i32 + dx) as usize, (y as i32 + dy) as usize); + match map[ny][nx] { + Cell2::Wall => continue, + Cell2::Empty => { + map[y][x] = Cell2::Empty; + map[ny][nx] = Cell2::Sub; + x = nx; + y = ny; + } + Cell2::Sub => unreachable!(), + Cell2::BoxLeft => { + if dy == 0 { + let (mut bx, mut by) = ((x as i32 + dx) as usize, (y as i32 + dy) as usize); + while map[by][bx] == Cell2::BoxLeft { + (bx, by) = ((bx as i32 + 2 * dx) as usize, (by as i32 + 2 * dy) as usize); + } + match map[by][bx] { + Cell2::BoxLeft | Cell2::BoxRight => unreachable!(), + Cell2::Sub => unreachable!(), + Cell2::Empty => { + for i in nx..bx { + map[y][bx + nx - i] = map[y][bx + nx - i - 1]; + } + map[ny][nx] = Cell2::Sub; + map[y][x] = Cell2::Empty; + y = ny; + x = nx; + } + Cell2::Wall => continue, + } + } else { + let mut pushing = vec![(x, y)]; + let mut pushed = HashSet::new(); + let mut pushable = true; + while let Some(test) = pushing.pop() { + match map[(test.1 as i32 + dy) as usize][test.0] { + Cell2::Sub => unreachable!(), + Cell2::Wall => { + pushable = false; + break; + } + Cell2::Empty => { + pushed.insert(test); + } + Cell2::BoxLeft => { + pushed.insert(test); + pushing.push((test.0, (test.1 as i32 + dy) as usize)); + pushing.push((test.0 + 1, (test.1 as i32 + dy) as usize)); + } + Cell2::BoxRight => { + pushed.insert(test); + pushing.push((test.0, (test.1 as i32 + dy) as usize)); + pushing.push((test.0 - 1, (test.1 as i32 + dy) as usize)); + } + } + } + if !pushable { + continue; + } else { + let moved = pushed.clone(); + let mut pushed = pushed.into_iter().collect::>(); + pushed.sort_by(|a, b| (b.1 as i32 * dy).cmp(&(a.1 as i32 * dy))); + pushed.into_iter().for_each(|(x, y)| { + map[(y as i32 + dy) as usize][x] = map[y][x]; + if !moved.contains(&(x, (y as i32 - dy) as usize)) { + map[y][x] = Cell2::Empty; + } + }); + y = ny; + x = nx; + } + } + } + Cell2::BoxRight => { + if dy == 0 { + let (mut bx, mut by) = ((x as i32 + dx) as usize, (y as i32 + dy) as usize); + while map[by][bx] == Cell2::BoxRight { + (bx, by) = ((bx as i32 + 2 * dx) as usize, (by as i32 + 2 * dy) as usize); + } + match map[by][bx] { + Cell2::BoxLeft | Cell2::BoxRight => unreachable!(), + Cell2::Sub => unreachable!(), + Cell2::Empty => { + for i in bx..nx { + map[y][i] = map[y][i + 1]; + } + map[ny][nx] = Cell2::Sub; + map[y][x] = Cell2::Empty; + y = ny; + x = nx; + } + Cell2::Wall => continue, + } + } else { + let mut pushing = vec![(x, y)]; + let mut pushed = HashSet::new(); + let mut pushable = true; + while let Some(test) = pushing.pop() { + match map[(test.1 as i32 + dy) as usize][test.0] { + Cell2::Sub => unreachable!(), + Cell2::Wall => { + pushable = false; + break; + } + Cell2::Empty => { + pushed.insert(test); + } + Cell2::BoxLeft => { + pushed.insert(test); + pushing.push((test.0, (test.1 as i32 + dy) as usize)); + pushing.push((test.0 + 1, (test.1 as i32 + dy) as usize)); + } + Cell2::BoxRight => { + pushed.insert(test); + pushing.push((test.0, (test.1 as i32 + dy) as usize)); + pushing.push((test.0 - 1, (test.1 as i32 + dy) as usize)); + } + } + } + if !pushable { + continue; + } else { + let moved = pushed.clone(); + let mut pushed = pushed.into_iter().collect::>(); + pushed.sort_by(|a, b| (b.1 as i32 * dy).cmp(&(a.1 as i32 * dy))); + pushed.into_iter().for_each(|(x, y)| { + map[(y as i32 + dy) as usize][x] = map[y][x]; + if !moved.contains(&(x, (y as i32 - dy) as usize)) { + map[y][x] = Cell2::Empty; + } + }); + y = ny; + x = nx; + } + } + } + }; + + // println!("\nStep: {:?}", step); + // for line in &map { + // for cell in line { + // print!( + // "{}", + // match cell { + // Cell2::Wall => '#', + // Cell2::Empty => ' ', + // Cell2::BoxLeft => '[', + // Cell2::BoxRight => ']', + // Cell2::Sub => '@', + // } + // ); + // } + // println!(); + // } + } + + map.iter() + .enumerate() + .map(|(y, line)| { + line.iter() + .enumerate() + .map(|(x, cell)| match cell { + Cell2::BoxLeft => x + 100 * y, + _ => 0, + }) + .sum::() + }) + .sum() +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn part1_example() { + assert_eq!( + part1(&parse( + "######## +#..O.O.# +##@.O..# +#...O..# +#.#.O..# +#...O..# +#......# +######## + +<^^>>>vv>v<<" + )), + 2028 + ); + assert_eq!( + part1(&parse( + "########## +#..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^<<^" + )), + 10092 + ); + } + + #[test] + fn part2_example() { + assert_eq!( + part2(&parse( + "########## +#..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^<<^" + )), + 9021 + ); + } +} diff --git a/aoc_2024/src/lib.rs b/aoc_2024/src/lib.rs index fbe7cda..0e6b7ae 100644 --- a/aoc_2024/src/lib.rs +++ b/aoc_2024/src/lib.rs @@ -4,6 +4,7 @@ mod day11; mod day12; mod day13; mod day14; +mod day15; mod day2; mod day3; mod day4;