From ade16938e1dd7b636b2d49f5ab6fa6d7c3704372 Mon Sep 17 00:00:00 2001 From: Louis Vallat Date: Mon, 16 Dec 2024 23:27:32 +0100 Subject: [PATCH] feat: added day 15 part 1 and 2 Signed-off-by: Louis Vallat --- .gitea/workflows/build_and_run.yml | 2 +- day15/Cargo.toml | 6 + day15/src/main.rs | 180 +++++++++++++++++++++++++++++ 3 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 day15/Cargo.toml create mode 100644 day15/src/main.rs diff --git a/.gitea/workflows/build_and_run.yml b/.gitea/workflows/build_and_run.yml index 24c815b..557f94f 100644 --- a/.gitea/workflows/build_and_run.yml +++ b/.gitea/workflows/build_and_run.yml @@ -11,7 +11,7 @@ jobs: name: Challenge for day strategy: matrix: - day_number: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14] + day_number: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] runs-on: rust-bookworm steps: - name: Check out repository code diff --git a/day15/Cargo.toml b/day15/Cargo.toml new file mode 100644 index 0000000..203eb55 --- /dev/null +++ b/day15/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day15" +version = "0.1.0" +edition = "2021" + +[dependencies] diff --git a/day15/src/main.rs b/day15/src/main.rs new file mode 100644 index 0000000..207f576 --- /dev/null +++ b/day15/src/main.rs @@ -0,0 +1,180 @@ +use std::collections::HashSet; +use std::{env, fs}; + +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] +enum Position { + Free, + Box, + RightHalfBox, + LeftHalfBox, + Wall, +} + +fn read_input(path: &str) -> String { + fs::read_to_string(path).expect("Cannot read file.") +} + +fn parse_input( + input: &str, +) -> ( + Vec>, + Vec>, + Vec<(i32, i32)>, + (usize, usize), +) { + let mut map = Vec::new(); + let mut enlarged_map = Vec::new(); + let mut moves = Vec::new(); + let mut robot = (0, 0); + let (raw_map, raw_moves) = input.split_once("\n\n").unwrap(); + for (row, line) in raw_map.lines().enumerate() { + map.push(Vec::new()); + enlarged_map.push(Vec::new()); + for (col, char) in line.chars().enumerate() { + match char { + '#' => { + map.last_mut().unwrap().push(Position::Wall); + enlarged_map + .last_mut() + .unwrap() + .extend(vec![Position::Wall; 2]); + } + 'O' => { + map.last_mut().unwrap().push(Position::Box); + enlarged_map + .last_mut() + .unwrap() + .extend(vec![Position::LeftHalfBox, Position::RightHalfBox]); + } + '.' | '@' => { + map.last_mut().unwrap().push(Position::Free); + enlarged_map + .last_mut() + .unwrap() + .extend(vec![Position::Free; 2]); + if char == '@' { + robot = (row, col); + } + } + _ => { + unreachable!() + } + } + } + } + + for line in raw_moves.lines().filter(|l| !l.is_empty()) { + for movement in line.chars() { + match movement { + '^' => moves.push((-1, 0)), + '>' => moves.push((0, 1)), + 'v' => moves.push((1, 0)), + '<' => moves.push((0, -1)), + _ => { + unreachable!() + } + } + } + } + + moves.reverse(); + (map, enlarged_map, moves, robot) +} + +fn boxes_to_move( + map: &Vec>, + pos: &(usize, usize), + movement: &(i32, i32), +) -> Option> { + let target_pos = ( + (pos.0 as i32 + movement.0) as usize, + (pos.1 as i32 + movement.1) as usize, + ); + match map[pos.0][pos.1] { + Position::Free => return Some(vec![]), + Position::Wall => return None, + Position::Box => { + if let Some(mut boxes) = boxes_to_move(&map, &target_pos, movement) { + boxes.push((pos.0, pos.1, Position::Box)); + return Some(boxes); + } + } + Position::LeftHalfBox | Position::RightHalfBox => { + let other_box; + let offset; + if map[pos.0][pos.1] == Position::LeftHalfBox { + other_box = Position::RightHalfBox; + offset = 1; + } else { + other_box = Position::LeftHalfBox; + offset = -1; + } + let target_other_half = (target_pos.0, (target_pos.1 as i32 + offset) as usize); + if let Some(mut boxes) = boxes_to_move(&map, &target_pos, movement) { + boxes.push((pos.0, pos.1, map[pos.0][pos.1])); + if movement.0 != 0 { + let other_boxes = boxes_to_move(&map, &target_other_half, movement); + return if other_boxes.is_some() { + boxes.append(&mut other_boxes.unwrap()); + boxes.push((pos.0, (pos.1 as i32 + offset) as usize, other_box)); + Some(boxes) + } else { + None + }; + } + return Some(boxes); + } + } + } + + None +} + +fn apply_all_moves(map: &mut Vec>, robot: &(usize, usize), moves: &Vec<(i32, i32)>) { + let mut robot = *robot; + for movement in moves.iter().rev() { + let target_pos = ( + (robot.0 as i32 + movement.0) as usize, + (robot.1 as i32 + movement.1) as usize, + ); + if let Some(box_positions) = boxes_to_move(map, &target_pos, &movement) { + let mut positions_moved = HashSet::new(); + for box_position in box_positions { + if positions_moved.insert(box_position) { + map[(box_position.0 as i32 + movement.0) as usize] + [(box_position.1 as i32 + movement.1) as usize] = box_position.2; + map[box_position.0][box_position.1] = Position::Free; + } + } + robot = target_pos; + } + } +} + +fn compute_gps_score(map: &Vec>) -> usize { + let mut sum = 0; + for (row, line) in map.iter().enumerate() { + for (col, position) in line.iter().enumerate() { + if *position == Position::Box || *position == Position::LeftHalfBox { + sum += 100 * row + col; + } + } + } + sum +} + +fn main() { + let args: Vec = env::args().collect(); + for arg in args.iter().skip(1) { + let input = read_input(&arg); + let (mut map, mut enlarged_map, moves, robot) = parse_input(&input); + apply_all_moves(&mut map, &robot, &moves); + apply_all_moves(&mut enlarged_map, &(robot.0, robot.1 * 2), &moves); + println!("[{}]", &arg); + println!("\t[Part 1] => Answer is '{}'.", compute_gps_score(&map)); + println!( + "\t[Part 2] => Answer is '{}'.", + compute_gps_score(&enlarged_map) + ); + } +}