--- /dev/null
+use std::error::Error;
+use std::path::Path;
+use std::collections::HashMap;
+
+struct Puzzle {
+ map: HashMap<(isize, isize), u8>,
+ robot: (isize, isize),
+ moves: String,
+}
+
+impl Puzzle {
+ pub fn new(input: &str) -> Self {
+ let mut map: HashMap<(isize, isize), u8> = HashMap::new();
+ let mut robot: (isize, isize) = (-1, -1);
+ let (map_str, moves) = input.split_once("\n\n").unwrap();
+ map_str.lines().enumerate()
+ .for_each(|(row, l)| {
+ l.as_bytes().into_iter().enumerate()
+ .for_each(|(col, v)| {
+ map.insert((row as isize, col as isize), *v);
+ if *v == b'@' {
+ robot = (row as isize, col as isize);
+ }
+ });
+ });
+ let moves = moves.to_string();
+ Self { map, robot, moves }
+ }
+
+ fn set_type(&mut self, pos: (isize, isize), t: u8) {
+ if self.map.contains_key(&pos) {
+ let p = self.map.get_mut(&pos).unwrap();
+ *p = t;
+ }
+ }
+
+ fn is_of_type(&self, pos: (isize, isize), t: u8) -> bool {
+ self.map.contains_key(&pos) && *self.map.get(&pos).unwrap() == t
+ }
+
+ fn is_box(&self, pos: (isize, isize)) -> bool {
+ self.is_of_type(pos, b'O')
+ }
+
+ fn is_border(&self, pos: (isize, isize)) -> bool {
+ self.is_of_type(pos, b'#')
+ }
+
+ fn push_boxes(&mut self, dir: (isize, isize)) -> bool {
+ let pos = self.robot;
+ let mut pos = (pos.0 + dir.0, pos.1 + dir.1);
+ let mut boxes: Vec<(isize, isize)> = Vec::new();
+ while self.is_box(pos) {
+ boxes.push(pos);
+ pos = (pos.0 + dir.0, pos.1 + dir.1);
+ }
+ if !self.is_of_type(pos, b'.') {
+ false
+ }
+ else {
+ if let Some(box_last) = boxes.last() {
+ let next_pos = (box_last.0 + dir.0, box_last.1 + dir.1);
+ self.set_type(next_pos, b'O');
+ }
+ if let Some(box_first) = boxes.first() {
+ self.set_type(*box_first, b'.');
+ }
+ true
+ }
+ }
+
+ fn robot_can_move(&mut self, dir: (isize, isize)) -> bool {
+ let pos = self.robot;
+ let next_pos = (pos.0 + dir.0, pos.1 + dir.1);
+ !self.is_border(next_pos) && self.push_boxes(dir)
+ }
+
+ fn run(&mut self) {
+ let moves = self.moves.clone().as_bytes().to_vec();
+ for m in moves.iter() {
+ let dir = match m {
+ b'^' => (-1, 0),
+ b'>' => (0, 1),
+ b'v' => (1, 0),
+ b'<' => (0, -1),
+ b'\n' => (0, 0),
+ _ => unreachable!()
+ };
+
+ if self.robot_can_move(dir) {
+ let pos = self.robot;
+ self.set_type(pos, b'.');
+ self.robot = (pos.0 + dir.0, pos.1 + dir.1);
+ self.set_type(self.robot, b'@');
+ }
+ }
+ }
+
+ fn gps_sum(&self) -> u32 {
+ self.map.iter()
+ .filter(|&(_, v)| *v == b'O')
+ .map(|(pos, _)| (100*pos.0 + pos.1) as u32)
+ .sum()
+ }
+}
+
+fn run_part1(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running {} - part 1", get_day());
+
+ let mut puzzle = Puzzle::new(input);
+ puzzle.run();
+
+ Ok(puzzle.gps_sum())
+}
+
+fn run_part2(input: &str) -> Result<u32, Box<dyn Error>> {
+ println!("Running {} - part 2", get_day());
+
+ Ok(0)
+}
+
+pub fn run(input: &str) -> Result<(), Box<dyn Error>> {
+ let res = run_part1(input)?;
+ println!("{res}");
+
+ let res = run_part2(input)?;
+ println!("{res}");
+
+ Ok(())
+}
+
+fn get_day() -> String {
+ let filename = file!();
+ Path::new(filename).file_stem().unwrap().to_str().unwrap().to_string()
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ static TEXT_INPUT_0: &str = "\
+########
+#..O.O.#
+##@.O..#
+#...O..#
+#.#.O..#
+#...O..#
+#......#
+########
+
+<^^>>>vv<v>>v<<";
+ static TEXT_INPUT: &str = "\
+##########
+#..O..O.O#
+#......O.#
+#.OO..O.O#
+#..O@..O.#
+#O#..O...#
+#O..O..O.#
+#.OO.O.OO#
+#....O...#
+##########
+
+<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
+vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
+><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
+<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
+^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
+^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
+>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
+<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
+^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
+v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^";
+
+ #[test]
+ fn test_part1() {
+ assert_eq!(2028, run_part1(TEXT_INPUT_0).unwrap());
+ assert_eq!(10092, run_part1(TEXT_INPUT).unwrap());
+ }
+
+ #[test]
+ fn test_part2() {
+ assert_eq!(0, run_part2(TEXT_INPUT).unwrap());
+ }
+}