Advent of Code solutions
at main 162 lines 5.0 kB view raw
1use std::collections::{HashMap, HashSet}; 2 3use advent_core::{day_stuff, ex_for_day, Day}; 4use utils::{ 5 dir::{Direction, Movement}, 6 grid::Grid, 7 pos::Position, 8 tiles, upos, 9}; 10 11pub struct Day15; 12 13tiles!(Tile, [ 14 '.' => Empty, 15 '@' => Robot, 16 '#' => Wall, 17 'O' => Box, 18 '[' => BoxLeft, 19 ']' => BoxRight, 20]); 21 22fn parse_instruction(c: char) -> Direction { 23 match c { 24 '^' => Direction::North, 25 '<' => Direction::West, 26 '>' => Direction::East, 27 'v' => Direction::South, 28 _ => unreachable!("For {c:?}"), 29 } 30} 31 32type PosMap = HashMap<Position, Tile>; 33 34fn movement(robo: Position, dir: Direction, map: &mut PosMap) -> Option<Position> { 35 let mut next_pos = robo.add(&dir.get_kernel()); 36 let mut to_update = Vec::with_capacity(20); 37 loop { 38 let next_tile = map.get(&next_pos).unwrap(); 39 if *next_tile == Tile::Wall { 40 return None; 41 } else if *next_tile == Tile::Box { 42 to_update.push(next_pos); 43 next_pos = next_pos.add(&dir.get_kernel()); 44 } else { 45 // Is Empty 46 to_update.push(next_pos); 47 break; 48 } 49 } 50 assert!(!to_update.is_empty()); 51 let first = to_update.first().unwrap(); 52 *map.get_mut(first).unwrap() = Tile::Robot; 53 *map.get_mut(&robo).unwrap() = Tile::Empty; 54 if to_update.len() >= 2 { 55 *map.get_mut(to_update.last().unwrap()).unwrap() = Tile::Box; 56 } 57 Some(*first) 58} 59 60fn movement_pt_2(robo: Position, dir: Direction, map: &mut PosMap) -> Option<Position> { 61 let kern = dir.get_kernel(); 62 let mut to_check = HashSet::from_iter([robo.add(&kern)]); 63 let mut to_update = Vec::with_capacity(40); 64 to_update.push((robo.add(&kern), Tile::Robot)); 65 while !to_check.is_empty() { 66 let mut new_check = HashSet::new(); 67 for check in to_check.into_iter() { 68 let new_tile = *map.get(&check).unwrap(); 69 if new_tile == Tile::Wall { 70 return None; 71 } else if new_tile == Tile::BoxLeft && !dir.is_horizontal() { 72 let my_new_pos = check.add(&kern); 73 let r_pos = check.add(&upos!(1, 0)); 74 let r_new_pos = r_pos.add(&kern); 75 to_update.push((my_new_pos, Tile::BoxLeft)); 76 to_update.push((r_new_pos, Tile::BoxRight)); 77 new_check.insert(my_new_pos); 78 new_check.insert(r_new_pos); 79 } else if new_tile == Tile::BoxRight && !dir.is_horizontal() { 80 let my_new_pos = check.add(&kern); 81 let l_pos = check.sub(&upos!(1, 0)); 82 let l_new_pos = l_pos.add(&kern); 83 to_update.push((my_new_pos, Tile::BoxRight)); 84 to_update.push((l_new_pos, Tile::BoxLeft)); 85 new_check.insert(my_new_pos); 86 new_check.insert(l_new_pos); 87 } else if new_tile != Tile::Empty { 88 to_update.push((check.add(&kern), new_tile)); 89 new_check.insert(check.add(&kern)); 90 } 91 } 92 to_check = new_check; 93 } 94 for (pos, tile) in to_update.into_iter().rev() { 95 *map.get_mut(&pos).unwrap() = tile; 96 *map.get_mut(&pos.sub(&kern)).unwrap() = Tile::Empty; 97 } 98 Some(robo.add(&kern)) 99} 100 101fn gps(pos_map: &PosMap) -> usize { 102 pos_map 103 .iter() 104 .filter_map(|(pos, tile)| { 105 if matches!(*tile, Tile::Box | Tile::BoxLeft) { 106 Some(100 * pos.y as usize + pos.x as usize) 107 } else { 108 None 109 } 110 }) 111 .sum() 112} 113 114type Input = (Position, PosMap, Vec<Direction>); 115 116fn actual_parse(input: &str) -> Input { 117 let (map, dirs) = input.trim().split_once("\n\n").unwrap(); 118 let dirs = dirs 119 .split('\n') 120 .flat_map(|l| l.chars().map(parse_instruction)) 121 .collect(); 122 123 let grid = Grid::<Tile>::parse(map); 124 125 let robo = grid.find_tile(&Tile::Robot).unwrap(); 126 127 let pos_map = grid.iter().map(|(pos, tile)| (pos, *tile)).collect(); 128 129 (robo, pos_map, dirs) 130} 131 132fn actual_parse_part_2(input: &str) -> Input { 133 let replace = input.replace('#', "##"); 134 let replace = replace.replace('.', ".."); 135 let replace = replace.replace('O', "[]"); 136 let replace = replace.replace('@', "@."); 137 actual_parse(&replace) 138} 139 140impl Day for Day15 { 141 day_stuff!(15, "10092", "9021"); 142 143 fn part_1(input: Self::Input) -> Option<String> { 144 let (mut robo, mut pos_map, ins) = actual_parse(&input); 145 for i in ins { 146 if let Some(new_pos) = movement(robo, i, &mut pos_map) { 147 robo = new_pos; 148 } 149 } 150 Some(gps(&pos_map).to_string()) 151 } 152 153 fn part_2(input: Self::Input) -> Option<String> { 154 let (mut robo, mut pos_map, ins) = actual_parse_part_2(&input); 155 for i in ins { 156 if let Some(new_pos) = movement_pt_2(robo, i, &mut pos_map) { 157 robo = new_pos; 158 } 159 } 160 Some(gps(&pos_map).to_string()) 161 } 162}