DEVELOPMENT ENVIRONMENT

~liljamo/aoc2024

d978ab7908e2a7fd929ea3144d9ff925f6400e15 — Jonni Liljamo a month ago 8cb64e2
feat: day15 partly

* part2 has a bug that can be produced with examplecustom, fix plz
A input/day15/example => input/day15/example +21 -0
@@ 0,0 1,21 @@
##########
#..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^<<^

A input/day15/examplecustom => input/day15/examplecustom +11 -0
@@ 0,0 1,11 @@
########
#......#
#......#
#..O...#
#...O@.#
#.OOO..#
#.#.#..#
#......#
########

<^^<<vv

A input/day15/examplesmall => input/day15/examplesmall +10 -0
@@ 0,0 1,10 @@
########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########

<^^>>>vv<v>>v<<

A input/day15/examplesmallwide => input/day15/examplesmallwide +9 -0
@@ 0,0 1,9 @@
#######
#...#.#
#.....#
#..OO@#
#..O..#
#.....#
#######

<vv<<^^<<^^

A src/day15/mod.rs => src/day15/mod.rs +11 -0
@@ 0,0 1,11 @@
use std::path::Path;

mod part1;
mod part2;

pub fn solve(input: &Path) -> anyhow::Result<()> {
    println!("part one: {}", part1::part_one(input)?);
    println!("part two: {}", part2::part_two(input)?);

    Ok(())
}

A src/day15/part1.rs => src/day15/part1.rs +187 -0
@@ 0,0 1,187 @@
use std::{
    fs::File,
    io::{BufRead, BufReader},
    path::Path,
};

#[derive(Debug)]
enum Direction {
    North,
    East,
    South,
    West,
}

impl Direction {
    fn from_c(c: char) -> Self {
        match c {
            '^' => Direction::North,
            '>' => Direction::East,
            'v' => Direction::South,
            '<' => Direction::West,
            _ => unreachable!(),
        }
    }
}

#[derive(Clone)]
struct Pos {
    x: usize,
    y: usize,
}

impl Pos {
    fn r#move(&mut self, direction: &Direction) {
        match direction {
            Direction::North => self.y -= 1,
            Direction::East => self.x += 1,
            Direction::South => self.y += 1,
            Direction::West => self.x -= 1,
        }
    }
}

struct Warehouse {
    robot: Pos,
    tiles: Vec<Vec<Tile>>,
}

impl Warehouse {
    fn from_lines(lines: &[String]) -> Self {
        let mut tiles = vec![];
        let mut robot = None;
        for (y, line) in lines.iter().enumerate() {
            let mut row = vec![];
            for (x, c) in line.chars().enumerate() {
                match c {
                    '.' => row.push(Tile::Empty),
                    '#' => row.push(Tile::Wall),
                    'O' => row.push(Tile::Box),
                    '@' => {
                        row.push(Tile::Empty);
                        robot = Some(Pos { x, y });
                    }
                    _ => unreachable!(),
                }
            }
            tiles.push(row);
        }
        Self {
            robot: robot.unwrap(),
            tiles,
        }
    }

    fn get_tile_in_direction(&self, x: usize, y: usize, direction: &Direction) -> (&Tile, Pos) {
        match direction {
            Direction::North => (&self.tiles[y - 1][x], Pos { x, y: y - 1 }),
            Direction::East => (&self.tiles[y][x + 1], Pos { x: x + 1, y }),
            Direction::South => (&self.tiles[y + 1][x], Pos { x, y: y + 1 }),
            Direction::West => (&self.tiles[y][x - 1], Pos { x: x - 1, y }),
        }
    }

    fn move_robot(&mut self, direction: &Direction) {
        let (tile, p) = self.get_tile_in_direction(self.robot.x, self.robot.y, direction);
        match tile {
            Tile::Empty => self.robot.r#move(direction),
            Tile::Wall => {}
            Tile::Box => {
                if self.move_box(p, direction) {
                    self.robot.r#move(direction);
                }
            }
        }
    }

    fn move_box(&mut self, our_p: Pos, direction: &Direction) -> bool {
        let (tile, p) = self.get_tile_in_direction(our_p.x, our_p.y, direction);
        match tile {
            Tile::Empty => {
                self.tiles[our_p.y][our_p.x] = Tile::Empty;
                self.tiles[p.y][p.x] = Tile::Box;
                true
            }
            Tile::Wall => false,
            Tile::Box => {
                if self.move_box(p.clone(), direction) {
                    self.tiles[our_p.y][our_p.x] = Tile::Empty;
                    self.tiles[p.y][p.x] = Tile::Box;
                    true
                } else {
                    false
                }
            }
        }
    }

    fn _print(&self) {
        for (y, row) in self.tiles.iter().enumerate() {
            for (x, tile) in row.iter().enumerate() {
                if y == self.robot.y && x == self.robot.x {
                    print!("@");
                } else {
                    print!("{}", tile);
                }
            }
            println!();
        }
    }
}

enum Tile {
    Empty,
    Wall,
    Box,
}

impl std::fmt::Display for Tile {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Tile::Empty => write!(f, "."),
            Tile::Wall => write!(f, "#"),
            Tile::Box => write!(f, "O"),
        }
    }
}

pub fn part_one(input: &Path) -> anyhow::Result<usize> {
    let reader = BufReader::new(File::open(input)?);

    let mut map_lines: Vec<String> = vec![];
    let mut movements: Vec<Direction> = vec![];
    let mut split = false;
    for line in reader.lines() {
        let line = line?;
        if line.is_empty() {
            split = true;
            continue;
        }
        if split {
            movements.append(
                &mut line
                    .trim()
                    .chars()
                    .map(|c| -> Direction { Direction::from_c(c) })
                    .collect::<Vec<Direction>>(),
            );
        } else {
            map_lines.push(line);
        }
    }
    let mut wh = Warehouse::from_lines(&map_lines);
    for direction in movements {
        wh.move_robot(&direction);
    }

    let mut answer = 0;
    for (y, row) in wh.tiles.iter().enumerate() {
        for (x, tile) in row.iter().enumerate() {
            if let Tile::Box = tile {
                answer += 100 * y + x;
            }
        }
    }

    Ok(answer)
}

A src/day15/part2.rs => src/day15/part2.rs +295 -0
@@ 0,0 1,295 @@
use std::{
    fs::File,
    io::{BufRead, BufReader},
    path::Path,
};

#[derive(Debug)]
enum Direction {
    North,
    East,
    South,
    West,
}

impl Direction {
    fn from_c(c: char) -> Self {
        match c {
            '^' => Direction::North,
            '>' => Direction::East,
            'v' => Direction::South,
            '<' => Direction::West,
            _ => unreachable!(),
        }
    }
}

#[derive(Clone, Debug)]
struct Pos {
    x: usize,
    y: usize,
}

impl Pos {
    fn r#move(&mut self, direction: &Direction) {
        match direction {
            Direction::North => self.y -= 1,
            Direction::East => self.x += 1,
            Direction::South => self.y += 1,
            Direction::West => self.x -= 1,
        }
    }
}

struct Warehouse {
    robot: Pos,
    tiles: Vec<Vec<Tile>>,
}

impl Warehouse {
    fn from_lines(lines: &[String]) -> Self {
        let mut tiles = vec![];
        let mut robot = None;
        for (y, line) in lines.iter().enumerate() {
            let mut row = vec![];
            for (x, c) in line.chars().enumerate() {
                match c {
                    '.' => {
                        row.push(Tile::Empty);
                        row.push(Tile::Empty);
                    }
                    '#' => {
                        row.push(Tile::Wall);
                        row.push(Tile::Wall);
                    }
                    'O' => {
                        row.push(Tile::Box(Side::Left));
                        row.push(Tile::Box(Side::Right));
                    }
                    '@' => {
                        row.push(Tile::Empty);
                        row.push(Tile::Empty);
                        robot = Some(Pos { x: x * 2, y });
                    }
                    _ => unreachable!(),
                }
            }
            tiles.push(row);
        }
        Self {
            robot: robot.unwrap(),
            tiles,
        }
    }

    fn get_tile_in_direction(&self, x: usize, y: usize, direction: &Direction) -> (&Tile, Pos) {
        match direction {
            Direction::North => (&self.tiles[y - 1][x], Pos { x, y: y - 1 }),
            Direction::East => (&self.tiles[y][x + 1], Pos { x: x + 1, y }),
            Direction::South => (&self.tiles[y + 1][x], Pos { x, y: y + 1 }),
            Direction::West => (&self.tiles[y][x - 1], Pos { x: x - 1, y }),
        }
    }

    fn move_robot(&mut self, direction: &Direction) {
        let (tile, p) = self.get_tile_in_direction(self.robot.x, self.robot.y, direction);
        match tile {
            Tile::Empty => self.robot.r#move(direction),
            Tile::Wall => {}
            Tile::Box(_side) => {
                if self.move_box(*tile, p, direction, false) {
                    self.robot.r#move(direction);
                }
            }
        }
    }

    fn move_box(
        &mut self,
        us: Tile,
        our_p: Pos,
        direction: &Direction,
        is_other_side: bool,
    ) -> bool {
        match direction {
            Direction::East | Direction::West => {
                let (tile, p) = self.get_tile_in_direction(our_p.x, our_p.y, direction);
                if match tile {
                    Tile::Empty => true,
                    Tile::Wall => false,
                    Tile::Box(_) => self.move_box(*tile, p.clone(), direction, false),
                } {
                    self.tiles[p.y][p.x] = self.tiles[our_p.y][our_p.x];
                    self.tiles[our_p.y][our_p.x] = Tile::Empty;
                    true
                } else {
                    false
                }
            }
            Direction::North | Direction::South => {
                let (tile, p) = self.get_tile_in_direction(our_p.x, our_p.y, direction);
                if is_other_side {
                    if match tile {
                        Tile::Empty => true,
                        Tile::Wall => false,
                        Tile::Box(_) => self.move_box(*tile, p.clone(), direction, false),
                    } {
                        self.tiles[p.y][p.x] = self.tiles[our_p.y][our_p.x];
                        self.tiles[our_p.y][our_p.x] = Tile::Empty;
                        return true;
                    } else {
                        return false;
                    }
                }
                match tile {
                    Tile::Empty => {
                        if let Tile::Box(side) = us {
                            let other_p = match side {
                                Side::Left => Pos {
                                    x: our_p.x + 1,
                                    y: our_p.y,
                                },
                                Side::Right => Pos {
                                    x: our_p.x - 1,
                                    y: our_p.y,
                                },
                            };
                            if self.move_box(
                                self.tiles[other_p.y][other_p.x],
                                other_p,
                                direction,
                                true,
                            ) {
                                self.tiles[p.y][p.x] = self.tiles[our_p.y][our_p.x];
                                self.tiles[our_p.y][our_p.x] = Tile::Empty;
                                true
                            } else {
                                false
                            }
                        } else {
                            unreachable!();
                        }
                    }
                    Tile::Wall => false,
                    Tile::Box(_) => {
                        if !self.move_box(*tile, p.clone(), direction, false) {
                            return false;
                        }
                        if let Tile::Box(side) = us {
                            let other_p = match side {
                                Side::Left => Pos {
                                    x: our_p.x + 1,
                                    y: our_p.y,
                                },
                                Side::Right => Pos {
                                    x: our_p.x - 1,
                                    y: our_p.y,
                                },
                            };
                            if self.move_box(
                                self.tiles[other_p.y][other_p.x],
                                other_p,
                                direction,
                                true,
                            ) {
                                self.tiles[p.y][p.x] = self.tiles[our_p.y][our_p.x];
                                self.tiles[our_p.y][our_p.x] = Tile::Empty;
                                true
                            } else {
                                false
                            }
                        } else {
                            unreachable!();
                        }
                    }
                }
            }
        }
    }

    fn _print(&self) {
        for (y, row) in self.tiles.iter().enumerate() {
            for (x, tile) in row.iter().enumerate() {
                if y == self.robot.y && x == self.robot.x {
                    print!("@");
                } else {
                    print!("{}", tile);
                }
            }
            println!();
        }
    }
}

#[derive(Clone, Copy, Debug)]
enum Tile {
    Empty,
    Wall,
    Box(Side),
}

#[derive(Clone, Copy, Debug, PartialEq)]
enum Side {
    Left,
    Right,
}

impl std::fmt::Display for Tile {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Tile::Empty => write!(f, "."),
            Tile::Wall => write!(f, "#"),
            Tile::Box(side) => match side {
                Side::Left => write!(f, "["),
                Side::Right => write!(f, "]"),
            },
        }
    }
}

pub fn part_two(input: &Path) -> anyhow::Result<usize> {
    let reader = BufReader::new(File::open(input)?);

    let mut map_lines: Vec<String> = vec![];
    let mut movements: Vec<Direction> = vec![];
    let mut split = false;
    for line in reader.lines() {
        let line = line?;
        if line.is_empty() {
            split = true;
            continue;
        }
        if split {
            movements.append(
                &mut line
                    .trim()
                    .chars()
                    .map(|c| -> Direction { Direction::from_c(c) })
                    .collect::<Vec<Direction>>(),
            );
        } else {
            map_lines.push(line);
        }
    }
    let mut wh = Warehouse::from_lines(&map_lines);
    wh._print();
    for direction in movements {
        dbg!(&direction);
        wh.move_robot(&direction);
        wh._print();
        println!();
    }

    let mut answer = 0;
    for (y, row) in wh.tiles.iter().enumerate() {
        for (x, tile) in row.iter().enumerate() {
            if let Tile::Box(side) = tile {
                if *side == Side::Left {
                    answer += 100 * y + x;
                }
            }
        }
    }

    Ok(answer)
}

M src/main.rs => src/main.rs +8 -0
@@ 8,6 8,7 @@ mod day11;
mod day12;
mod day13;
mod day14;
mod day15;
mod day2;
mod day3;
mod day4;


@@ 81,6 82,10 @@ enum DayArgs {
        #[arg(short)]
        input: PathBuf,
    },
    Day15 {
        #[arg(short)]
        input: PathBuf,
    },
}

fn main() -> anyhow::Result<()> {


@@ 129,6 134,9 @@ fn main() -> anyhow::Result<()> {
        DayArgs::Day14 { input } => {
            day14::solve(&input)?;
        }
        DayArgs::Day15 { input } => {
            day15::solve(&input)?;
        }
    }

    Ok(())