|
| 1 | +use std::fmt::{Debug, Write}; |
| 2 | + |
| 3 | +use aoc_lib::{direction::cardinal::Direction, matrix::Matrix}; |
| 4 | +use common::{solution, Answer}; |
| 5 | +use nd_vec::{vector, Vec2}; |
| 6 | + |
| 7 | +solution!("Warehouse Woes", 15); |
| 8 | + |
| 9 | +fn part_a(input: &str) -> Answer { |
| 10 | + let mut problem = Problem::parse(input, false); |
| 11 | + problem.tick_all(false); |
| 12 | + problem.score().into() |
| 13 | +} |
| 14 | + |
| 15 | +fn part_b(input: &str) -> Answer { |
| 16 | + let mut problem = Problem::parse(input, true); |
| 17 | + problem.tick_all(true); |
| 18 | + problem.score().into() |
| 19 | +} |
| 20 | + |
| 21 | +struct Problem { |
| 22 | + pos: Vec2<usize>, |
| 23 | + idx: usize, |
| 24 | + |
| 25 | + board: Matrix<Tile>, |
| 26 | + instructions: Vec<Direction>, |
| 27 | +} |
| 28 | + |
| 29 | +#[derive(PartialEq, Eq, Clone, Copy)] |
| 30 | +enum Tile { |
| 31 | + Robot, |
| 32 | + Wall, |
| 33 | + Box, |
| 34 | + BoxRight, |
| 35 | + Empty, |
| 36 | +} |
| 37 | + |
| 38 | +impl Problem { |
| 39 | + fn parse(input: &str, part_b: bool) -> Self { |
| 40 | + let (board, instructions) = input.split_once("\n\n").unwrap(); |
| 41 | + let mut board = Matrix::new_chars(board, |chr| match chr { |
| 42 | + '@' => Tile::Robot, |
| 43 | + '#' => Tile::Wall, |
| 44 | + 'O' => Tile::Box, |
| 45 | + '.' => Tile::Empty, |
| 46 | + _ => panic!(), |
| 47 | + }); |
| 48 | + |
| 49 | + if part_b { |
| 50 | + board.size = vector!(board.size.x() * 2, board.size.y()); |
| 51 | + let mut new = Vec::new(); |
| 52 | + for data in board.data { |
| 53 | + new.push(data); |
| 54 | + new.push(match data { |
| 55 | + Tile::Box => Tile::BoxRight, |
| 56 | + Tile::Robot => Tile::Empty, |
| 57 | + _ => data, |
| 58 | + }); |
| 59 | + } |
| 60 | + board.data = new; |
| 61 | + } |
| 62 | + |
| 63 | + let instructions = instructions |
| 64 | + .chars() |
| 65 | + .filter_map(|x| { |
| 66 | + Some(match x { |
| 67 | + '<' => Direction::Left, |
| 68 | + '>' => Direction::Right, |
| 69 | + '^' => Direction::Up, |
| 70 | + 'v' => Direction::Down, |
| 71 | + _ => return None, |
| 72 | + }) |
| 73 | + }) |
| 74 | + .collect::<Vec<_>>(); |
| 75 | + |
| 76 | + let pos = board.find(Tile::Robot).unwrap(); |
| 77 | + board.set(pos, Tile::Empty); |
| 78 | + |
| 79 | + Self { |
| 80 | + pos, |
| 81 | + idx: 0, |
| 82 | + |
| 83 | + board, |
| 84 | + instructions, |
| 85 | + } |
| 86 | + } |
| 87 | + |
| 88 | + fn tick_all(&mut self, part_b: bool) { |
| 89 | + for _ in 0..self.instructions.len() { |
| 90 | + self.tick(part_b); |
| 91 | + } |
| 92 | + } |
| 93 | + |
| 94 | + fn tick(&mut self, part_b: bool) { |
| 95 | + let dir = self.instructions[self.idx]; |
| 96 | + self.idx += 1; |
| 97 | + |
| 98 | + let new = dir.advance(self.pos); |
| 99 | + if { |
| 100 | + if part_b { |
| 101 | + self.push_b(new, dir) |
| 102 | + } else { |
| 103 | + self.push(new, dir) |
| 104 | + } |
| 105 | + } { |
| 106 | + self.pos = new; |
| 107 | + } |
| 108 | + } |
| 109 | + |
| 110 | + // -> was successful |
| 111 | + fn push(&mut self, pos: Vec2<usize>, dir: Direction) -> bool { |
| 112 | + // if we are air, return true |
| 113 | + let value = self.board[pos]; |
| 114 | + match value { |
| 115 | + Tile::Empty => return true, |
| 116 | + Tile::Wall => return false, |
| 117 | + _ => {} |
| 118 | + } |
| 119 | + |
| 120 | + // if where we want to move is full, try to move that |
| 121 | + let new = dir.wrapping_advance(pos); |
| 122 | + if !self.board.contains(new) { |
| 123 | + return false; |
| 124 | + } |
| 125 | + |
| 126 | + if self.board[new] == Tile::Empty || self.push(new, dir) { |
| 127 | + self.board.set(new, value); |
| 128 | + self.board.set(pos, Tile::Empty); |
| 129 | + true |
| 130 | + } else { |
| 131 | + false |
| 132 | + } |
| 133 | + } |
| 134 | + |
| 135 | + fn can_push(&self, pos: Vec2<usize>, dir: Direction) -> bool { |
| 136 | + // println!("{pos:?}, {dir:?}"); |
| 137 | + let value = self.board[pos]; |
| 138 | + match value { |
| 139 | + Tile::Empty => return true, |
| 140 | + Tile::Wall => return false, |
| 141 | + Tile::Box | Tile::BoxRight => {} |
| 142 | + Tile::Robot => unreachable!(), |
| 143 | + } |
| 144 | + |
| 145 | + let other_box = match value { |
| 146 | + Tile::Box => pos + vector!(1, 0), |
| 147 | + Tile::BoxRight => pos - vector!(1, 0), |
| 148 | + _ => unreachable!(), |
| 149 | + }; |
| 150 | + |
| 151 | + // if where we want to move is full, try to move that |
| 152 | + let new_a = dir.wrapping_advance(pos); |
| 153 | + let new_b = dir.wrapping_advance(other_box); |
| 154 | + if !(self.board.contains(new_a) && self.board.contains(new_b)) { |
| 155 | + return false; |
| 156 | + } |
| 157 | + |
| 158 | + (self.board[new_a] == Tile::Empty && self.board[new_b] == Tile::Empty) |
| 159 | + || ((new_a == other_box || self.can_push(new_a, dir)) |
| 160 | + && (new_b == pos || self.can_push(new_b, dir))) |
| 161 | + } |
| 162 | + |
| 163 | + fn push_b(&mut self, pos: Vec2<usize>, dir: Direction) -> bool { |
| 164 | + if self.can_push(pos, dir) { |
| 165 | + let value = self.board[pos]; |
| 166 | + if value == Tile::Empty { |
| 167 | + return true; |
| 168 | + } |
| 169 | + |
| 170 | + assert!(matches!(value, Tile::Box | Tile::BoxRight)); |
| 171 | + let other_box = match value { |
| 172 | + Tile::Box => pos + vector!(1, 0), |
| 173 | + Tile::BoxRight => pos - vector!(1, 0), |
| 174 | + _ => unreachable!(), |
| 175 | + }; |
| 176 | + let other_value = self.board[other_box]; |
| 177 | + |
| 178 | + let new_a = dir.wrapping_advance(pos); |
| 179 | + let new_b = dir.wrapping_advance(other_box); |
| 180 | + if !(self.board.contains(new_a) && self.board.contains(new_b)) { |
| 181 | + return false; |
| 182 | + } |
| 183 | + |
| 184 | + if (self.board[new_a] == Tile::Empty && self.board[new_b] == Tile::Empty) |
| 185 | + || ((new_a == other_box || self.push_b(new_a, dir)) |
| 186 | + && (new_b == pos || self.push_b(new_b, dir))) |
| 187 | + { |
| 188 | + // do push |
| 189 | + self.board.set(new_a, value); |
| 190 | + self.board.set(pos, Tile::Empty); |
| 191 | + |
| 192 | + self.board.set(new_b, other_value); |
| 193 | + if other_box != new_a { |
| 194 | + self.board.set(other_box, Tile::Empty); |
| 195 | + } |
| 196 | + true |
| 197 | + } else { |
| 198 | + false |
| 199 | + } |
| 200 | + } else { |
| 201 | + false |
| 202 | + } |
| 203 | + } |
| 204 | + |
| 205 | + fn score(&self) -> u32 { |
| 206 | + let mut score = 0; |
| 207 | + |
| 208 | + for (pos, _) in self.board.iter().filter(|x| *x.1 == Tile::Box) { |
| 209 | + score += (100 * pos.y() + pos.x()) as u32; |
| 210 | + } |
| 211 | + |
| 212 | + score |
| 213 | + } |
| 214 | + |
| 215 | + fn debug(&self) { |
| 216 | + let mut dbg = self.board.clone(); |
| 217 | + dbg.set(self.pos, Tile::Robot); |
| 218 | + println!("{:?}", dbg); |
| 219 | + } |
| 220 | +} |
| 221 | + |
| 222 | +impl Debug for Tile { |
| 223 | + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { |
| 224 | + match self { |
| 225 | + Tile::Robot => f.write_char('@'), |
| 226 | + Tile::Wall => f.write_char('#'), |
| 227 | + Tile::Box => f.write_char('['), |
| 228 | + Tile::BoxRight => f.write_char(']'), |
| 229 | + Tile::Empty => f.write_char('.'), |
| 230 | + } |
| 231 | + } |
| 232 | +} |
| 233 | + |
| 234 | +#[cfg(test)] |
| 235 | +mod test { |
| 236 | + use indoc::indoc; |
| 237 | + |
| 238 | + const CASE: &str = indoc! {" |
| 239 | + ########## |
| 240 | + #..O..O.O# |
| 241 | + #......O.# |
| 242 | + #.OO..O.O# |
| 243 | + #..O@..O.# |
| 244 | + #O#..O...# |
| 245 | + #O..O..O.# |
| 246 | + #.OO.O.OO# |
| 247 | + #....O...# |
| 248 | + ########## |
| 249 | +
|
| 250 | + <vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^ |
| 251 | + vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v |
| 252 | + ><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv< |
| 253 | + <<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^ |
| 254 | + ^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^>< |
| 255 | + ^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^ |
| 256 | + >^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^ |
| 257 | + <><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<> |
| 258 | + ^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v> |
| 259 | + v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^ |
| 260 | + "}; |
| 261 | + |
| 262 | + #[test] |
| 263 | + fn part_a() { |
| 264 | + assert_eq!(super::part_a(CASE), 10092.into()); |
| 265 | + } |
| 266 | + |
| 267 | + #[test] |
| 268 | + fn part_b() { |
| 269 | + assert_eq!(super::part_b(CASE), 9021.into()); |
| 270 | + } |
| 271 | +} |
0 commit comments