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(())