/ day15 / src / main.rs
main.rs
  1  use std::collections::HashSet;
  2  
  3  #[derive(Debug, Clone, Copy, PartialEq)]
  4  enum Tile {
  5      Empty,
  6      Box,
  7      Wall,
  8  }
  9  
 10  
 11  #[derive(Debug, Clone, Copy, PartialEq)]
 12  enum Tile2 {
 13      Empty,
 14      LeftBox,
 15      RightBox,
 16      Wall,
 17  }
 18  
 19  #[derive(Debug, Clone, Copy)]
 20  enum Movement {
 21      Up,
 22      Down,
 23      Left,
 24      Right,
 25  }
 26  
 27  #[derive(Debug, Clone)]
 28  struct Robot {
 29      x: i16,
 30      y: i16,
 31  }
 32  
 33  type Map = Vec<Vec<Tile>>;
 34  type Map2 = Vec<Vec<Tile2>>;
 35  
 36  impl Robot {
 37      fn get_offsets(mov: Movement) -> (i16, i16) {
 38          match mov {
 39              Movement::Up => (0, -1),
 40              Movement::Down => (0, 1),
 41              Movement::Left => (-1, 0),
 42              Movement::Right => (1, 0),
 43          }
 44      }
 45  }
 46  
 47  fn main() {
 48      // Parse map
 49      let mut initial_x: Option<u8> = None;
 50      let mut initial_y: Option<u8> = None;
 51      let mut initial_x2: Option<u8> = None;
 52      let mut initial_y2: Option<u8> = None;
 53      let input_parts = std::str::from_utf8(include_bytes!("input"))
 54          .unwrap()
 55          .split("\n\n")
 56          .collect::<Vec<&str>>();
 57      let mut map: Map = vec![];
 58      for (y, line) in input_parts.first().unwrap().lines().enumerate() {
 59          map.push(
 60              line.chars()
 61                  .enumerate()
 62                  .map(|(x, c)| match c {
 63                      '#' => Tile::Wall,
 64                      'O' => Tile::Box,
 65                      '.' => Tile::Empty,
 66                      '@' => {
 67                          initial_x = Some(x as u8);
 68                          initial_y = Some(y as u8);
 69                          return Tile::Empty;
 70                      }
 71                      _ => todo!("Unknown map tile"),
 72                  })
 73                  .collect(),
 74          );
 75      }
 76      let mut map2: Vec<Vec<Tile2>> = vec![];
 77      for (y, line) in input_parts.first().unwrap().lines().enumerate() {
 78          map2.push(
 79              line.chars()
 80                  .enumerate()
 81                  .map(|(x, c)| match c {
 82                      '#' => vec![Tile2::Wall, Tile2::Wall],
 83                      'O' => vec![Tile2::LeftBox, Tile2::RightBox],
 84                      '.' => vec![Tile2::Empty, Tile2::Empty],
 85                      '@' => {
 86                          initial_x2 = Some(2 * x as u8);
 87                          initial_y2 = Some(y as u8);
 88                          return vec![Tile2::Empty, Tile2::Empty];
 89                      }
 90                      _ => todo!("Unknown map tile"),
 91                  })
 92                  .flatten()
 93                  .collect(),
 94          );
 95      }
 96  
 97      let (h, _) = (map.len() as i16, map[0].len() as i16);
 98  
 99      let mut robot = Robot {
100          x: initial_x.unwrap() as i16,
101          y: initial_y.unwrap() as i16,
102      };
103      let mut robot2 = Robot {
104          x: initial_x2.unwrap() as i16,
105          y: initial_y2.unwrap() as i16,
106      };
107      // Parse movements
108      let movements = input_parts
109          .last()
110          .unwrap()
111          .chars()
112          .map(|c| match c {
113              '<' => Some(Movement::Left),
114              'v' => Some(Movement::Down),
115              '>' => Some(Movement::Right),
116              '^' => Some(Movement::Up),
117              _ => None,
118          })
119          .filter(|m| m.is_some())
120          .map(|m| m.unwrap())
121          .collect::<Vec<Movement>>();
122  
123      // Part 1
124      for mov in &movements {
125          //print_map(&map, Some(&robot));
126          // println!("Moving: {:?}", &mov);
127          let (dx, dy) = Robot::get_offsets(*mov);
128          let (nx, ny) = ((robot.x + dx) as usize, (robot.y + dy) as usize);
129          match map[ny][nx] {
130              Tile::Empty => {
131                  robot.x += dx;
132                  robot.y += dy;
133              }
134  
135              Tile::Box => match mov {
136                  Movement::Up | Movement::Down => {
137                      let range = match mov {
138                          Movement::Up => (0..=ny).rev().collect::<Vec<usize>>(),
139                          Movement::Down => (ny..h as usize).collect::<Vec<usize>>(),
140                          _ => todo!("Error"),
141                      };
142  
143                      let mut first_empty_row: Option<usize> = None;
144                      for y in range {
145                          if map[y][nx] == Tile::Wall {
146                              break;
147                          }
148                          if map[y][nx] == Tile::Empty {
149                              first_empty_row = Some(y);
150                              break;
151                          }
152                      }
153                      if let Some(free_y) = first_empty_row {
154                          map[free_y][nx] = Tile::Box;
155                          map[ny][nx] = Tile::Empty;
156                          robot.x += dx;
157                          robot.y += dy;
158                      }
159                  }
160                  Movement::Left => {
161                      if let Some((i, _)) = map[robot.y as usize]
162                          .iter()
163                          .enumerate()
164                          .rev()
165                          .find(|(i, &t)| *i < nx && t == Tile::Empty)
166                      {
167                          if !map[robot.y as usize][i..nx].contains(&Tile::Wall) {
168                              map[ny][nx] = Tile::Empty;
169                              map[ny][i] = Tile::Box;
170                              robot.x += dx;
171                              robot.y += dy;
172                          }
173                      }
174                  }
175                  Movement::Right => {
176                      if let Some((i, _)) = map[robot.y as usize]
177                          .iter()
178                          .enumerate()
179                          .find(|(i, &t)| *i > nx as usize && t == Tile::Empty)
180                      {
181                          if !map[robot.y as usize][nx..i].contains(&Tile::Wall) {
182                              map[ny][nx] = Tile::Empty;
183                              map[ny][i] = Tile::Box;
184                              robot.x += dx;
185                              robot.y += dy;
186                          }
187                      }
188                  }
189              },
190              Tile::Wall => (),
191          }
192      }
193      println!("Part 1 : {}", sum_gps(&map));
194      for mov in movements {
195          // print_map(&map, Some(&robot));
196          // println!("Moving: {:?}", &mov);
197          let (dx, dy) = Robot::get_offsets(mov);
198          let (nx, ny) = ((robot2.x + dx) as usize, (robot2.y + dy) as usize);
199          match map2[ny][nx] {
200              Tile2::Empty => {
201                  robot2.x += dx;
202                  robot2.y += dy;
203              }
204              Tile2::RightBox => match mov {
205                  Movement::Left => {
206                      if let Some((i, _)) = map2[robot2.y as usize]
207                          .iter()
208                          .enumerate()
209                          .rev()
210                          .find(|(i, &t)| *i < nx && t == Tile2::Empty)
211                      {
212                          if !map2[robot2.y as usize][i..nx].contains(&Tile2::Wall) {
213                              for x in i..nx {
214                                  map2[robot2.y as usize][x] = match map2[robot2.y as usize][x] {
215                                      Tile2::LeftBox => Tile2::RightBox,
216                                      Tile2::RightBox => Tile2::LeftBox,
217                                      Tile2::Empty => Tile2::LeftBox,
218                                      _ => todo!(),
219                                  };
220                              }
221                              map2[ny][nx] = Tile2::Empty;
222                              robot2.x += dx;
223                              robot2.y += dy;
224                          }
225                      }
226                  }
227                  Movement::Down | Movement::Up => {
228                      if let Some(to_move) = is_movable_wrapper((nx as i16 - 1, ny as i16), mov, &map2)
229                      {
230                          let reversed = match mov {
231                              Movement::Up => false,
232                              Movement::Down => true,
233                              _ => todo!("Unreachable"),
234                          };
235                          let mut sorted = to_move.iter().map(|a| *a).collect::<Vec<(i16, i16)>>();
236                          sorted.sort_by_key(|x| (x.1, x.0));
237                          if reversed {
238                              sorted.reverse();
239                          }
240                          for (_x, _y) in sorted {
241                              map2[_y as usize][_x as usize] = Tile2::Empty;
242                              map2[_y as usize][_x as usize + 1] = Tile2::Empty;
243                              map2[(_y + dy) as usize][_x as usize] = Tile2::LeftBox;
244                              map2[(_y + dy) as usize][_x as usize + 1] = Tile2::RightBox;
245                          }
246                          map2[ny as usize][nx as usize] = Tile2::Empty;
247                          map2[ny as usize][nx as usize - 1] = Tile2::Empty;
248                          robot2.x += dx;
249                          robot2.y += dy;
250                      }
251                  }
252                  _ => todo!(),
253              },
254              Tile2::LeftBox => match mov {
255                  Movement::Right => {
256                      if let Some((i, _)) = map2[robot2.y as usize]
257                          .iter()
258                          .enumerate()
259                          .find(|(i, &t)| *i > nx as usize && t == Tile2::Empty)
260                      {
261                          if !map2[robot2.y as usize][nx..i].contains(&Tile2::Wall) {
262                              for x in nx..=i {
263                                  map2[robot2.y as usize][x] = match map2[robot2.y as usize][x] {
264                                      Tile2::LeftBox => Tile2::RightBox,
265                                      Tile2::RightBox => Tile2::LeftBox,
266                                      Tile2::Empty => Tile2::RightBox,
267                                      _ => todo!(),
268                                  };
269                              }
270                              map2[ny][nx] = Tile2::Empty;
271                              robot2.x += dx;
272                              robot2.y += dy;
273                          }
274                      }
275                  }
276                  Movement::Down | Movement::Up => {
277                      if let Some(to_move) = is_movable_wrapper((nx as i16, ny as i16), mov, &map2) {
278                          let reversed = match mov {
279                              Movement::Up => false,
280                              Movement::Down => true,
281                              _ => todo!("Unreachable"),
282                          };
283                          let mut sorted = to_move.iter().map(|a| *a).collect::<Vec<(i16, i16)>>();
284                          sorted.sort_by_key(|x| (x.1, x.0));
285                          if reversed {
286                              sorted.reverse();
287                          }
288                          for (_x, _y) in sorted {
289                              map2[_y as usize][_x as usize] = Tile2::Empty;
290                              map2[_y as usize][_x as usize + 1] = Tile2::Empty;
291                              map2[(_y + dy) as usize][_x as usize] = Tile2::LeftBox;
292                              map2[(_y + dy) as usize][_x as usize + 1] = Tile2::RightBox;
293                          }
294                          map2[ny as usize][nx as usize] = Tile2::Empty;
295                          map2[ny as usize][nx as usize + 1] = Tile2::Empty;
296                          robot2.x += dx;
297                          robot2.y += dy;
298                      }
299                  }
300                  _ => todo!(),
301              },
302              Tile2::Wall => (),
303          }
304      }
305      println!("Part 2 : {}", sum_gps2(&map2));
306  }
307  
308  fn gps(x: usize, y: usize) -> usize {
309      return x + y * 100;
310  }
311  
312  fn sum_gps(map: &Vec<Vec<Tile>>) -> usize {
313      let mut sum: usize = 0;
314      for (y, line) in map.iter().enumerate() {
315          for (x, tile) in line.iter().enumerate() {
316              match tile {
317                  Tile::Box => {
318                      sum += gps(x, y);
319                  }
320                  _ => (),
321              }
322          }
323      }
324      sum
325  }
326  
327  fn sum_gps2(map: &Vec<Vec<Tile2>>) -> usize {
328      let mut sum: usize = 0;
329      for (y, line) in map.iter().enumerate() {
330          for (x, tile) in line.iter().enumerate() {
331              match tile {
332                  Tile2::LeftBox => {
333                      sum += gps(x, y);
334                  }
335                  _ => (),
336              }
337          }
338      }
339      sum
340  }
341  
342  fn is_movable(
343      left_part_coords: (i16, i16),
344      direction: Movement,
345      map: &Map2,
346      to_move: &mut HashSet<(i16, i16)>,
347  ) -> bool {
348      let (dx, dy) = Robot::get_offsets(direction);
349      let (nx, ny) = ((left_part_coords.0 + dx), (left_part_coords.1 + dy));
350      to_move.insert(left_part_coords);
351      (match map[ny as usize][nx as usize] {
352          // Check the left side of the box
353          Tile2::Empty => true,
354          Tile2::LeftBox => is_movable((nx, ny), direction, map, to_move),
355          Tile2::RightBox => is_movable((nx - 1, ny), direction, map, to_move),
356          Tile2::Wall => false,
357      } && match map[ny as usize][nx as usize + 1] {
358          // Check the right side of the box
359          Tile2::Empty => true,
360          Tile2::LeftBox => is_movable((nx + 1, ny), direction, map, to_move),
361          Tile2::RightBox => is_movable((nx, ny), direction, map, to_move),
362          Tile2::Wall => false,
363      })
364  }
365  
366  fn is_movable_wrapper(
367      left_part_coords: (i16, i16),
368      direction: Movement,
369      map: &Map2,
370  ) -> Option<HashSet<(i16, i16)>> {
371      let mut to_move = HashSet::new();
372  
373      if is_movable(left_part_coords, direction, map, &mut to_move) {
374          Some(to_move)
375      } else {
376          None
377      }
378  }