Skip to content

Commit

Permalink
[2024] Day 16 initial solution
Browse files Browse the repository at this point in the history
  • Loading branch information
connorslade committed Dec 16, 2024
1 parent 47bec2e commit 69d3f96
Show file tree
Hide file tree
Showing 3 changed files with 257 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Thank you to [Eric Wastl](http://was.tl) for running this incredible yearly even
- [Day 13: Claw Contraption](aoc_2024/src/day_13.rs)
- [Day 14: Restroom Redoubt](aoc_2024/src/day_14.rs)
- [Day 15: Warehouse Woes](aoc_2024/src/day_15.rs)
- [Day 16: Reindeer Maze](aoc_2024/src/day_16.rs)
<!-- MARKER -->

## [2023](https://adventofcode.com/2023) [![aoc_2023](https://github.com/connorslade/advent-of-code/actions/workflows/aoc_2023.yml/badge.svg)](https://github.com/connorslade/advent-of-code/actions/workflows/aoc_2023.yml)
Expand Down
254 changes: 254 additions & 0 deletions aoc_2024/src/day_16.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
use std::collections::{BinaryHeap, HashMap, HashSet};

use aoc_lib::{direction::cardinal::Direction, matrix::Matrix};
use common::{solution, Answer};
use nd_vec::{vector, Vec2};

solution!("Reindeer Maze", 16);

#[derive(PartialEq, Eq)]
enum Tile {
Empty,
Wall,
Start,
End,
}

fn part_a(input: &str) -> Answer {
let map = Matrix::new_chars(input, |c| match c {
'.' => Tile::Empty,
'#' => Tile::Wall,
'S' => Tile::Start,
'E' => Tile::End,
_ => panic!(),
});
let start = map.find(Tile::Start).unwrap();

// find minimum score needed to path from S to E
// - Move foreword (+1)
// - rotate left or right (+1000)

#[derive(PartialEq, Eq)]
struct Item {
pos: Vec2<usize>,
dir: Direction,
score: u32,
}

impl Ord for Item {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.score.cmp(&self.score)
}
}

impl PartialOrd for Item {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(other.score.cmp(&self.score))
}
}

// todo: try bin heap
let mut queue = BinaryHeap::new();
let mut seen = HashSet::new();

queue.push(Item {
pos: start,
dir: Direction::Right,
score: 0,
});

while let Some(Item { pos, dir, score }) = queue.pop() {
if !seen.insert((pos, dir)) {
continue;
}

if map[pos] == Tile::End {
return score.into();
}

// Move foreword
let next = dir.wrapping_advance(pos);
if map.contains(next) && map[next] != Tile::Wall {
queue.push(Item {
pos: next,
dir,
score: score + 1,
});
}

queue.push(Item {
pos,
dir: dir.turn_left(),
score: score + 1000,
});

queue.push(Item {
pos,
dir: dir.turn_right(),
score: score + 1000,
});
}

unreachable!()
}

fn part_b(input: &str) -> Answer {
let map = Matrix::new_chars(input, |c| match c {
'.' => Tile::Empty,
'#' => Tile::Wall,
'S' => Tile::Start,
'E' => Tile::End,
_ => panic!(),
});
let start = map.find(Tile::Start).unwrap();

// find minimum score needed to path from S to E
// - Move foreword (+1)
// - rotate left or right (+1000)

#[derive(PartialEq, Eq)]
struct Item {
pos: Vec2<usize>,
dir: Direction,
score: u32,

path: Vec<Vec2<usize>>,
}

impl Ord for Item {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
other.score.cmp(&self.score)
}
}

impl PartialOrd for Item {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(other.score.cmp(&self.score))
}
}

// todo: try bin heap
let mut queue = BinaryHeap::new();
let mut seen = HashMap::new();

let mut on_best = HashSet::<Vec2<usize>>::new();

queue.push(Item {
pos: start,
dir: Direction::Right,
score: 0,

path: vec![start],
});

let mut best = None;

while let Some(Item {
pos,
dir,
score,
path,
}) = queue.pop()
{
if let Some(&prev) = seen.get(&(pos, dir)) {
if score > prev {
continue;
}
} else {
seen.insert((pos, dir), score);
}

if map[pos] == Tile::End {
if let Some(real_best) = best {
dbg!(score, real_best);
if score == real_best {
on_best.extend(path.iter());
}
} else {
best = Some(score);
on_best.extend(path.iter());
}

continue;
}

// Move foreword
let next = dir.wrapping_advance(pos);
let mut next_path = path.clone();
next_path.push(next);
if map.contains(next) && map[next] != Tile::Wall {
queue.push(Item {
pos: next,
dir,
score: score + 1,
path: next_path,
});
}

queue.push(Item {
pos,
dir: dir.turn_left(),
score: score + 1000,
path: path.clone(),
});

queue.push(Item {
pos,
dir: dir.turn_right(),
score: score + 1000,
path,
});
}

for y in 0..map.size.y() {
for x in 0..map.size.x() {
let pos = vector!(x, y);
print!(
"{}",
match map[pos] {
Tile::Empty if on_best.contains(&pos) => '@',
Tile::Empty => '.',
Tile::Wall => '#',
Tile::Start => 'S',
Tile::End => 'E',
}
);
}
println!();
}

on_best.len().into()
}

#[cfg(test)]
mod test {
use indoc::indoc;

const CASE: &str = indoc! {"
###############
#.......#....E#
#.#.###.#.###.#
#.....#.#...#.#
#.###.#####.#.#
#.#.#.......#.#
#.#.#####.###.#
#...........#.#
###.#.#####.#.#
#...#.....#.#.#
#.#.#.###.#.#.#
#.....#...#.#.#
#.###.#.#.#.#.#
#S..#.....#...#
###############
"};

#[test]
fn part_a() {
assert_eq!(super::part_a(CASE), 7036.into());
}

#[test]
fn part_b() {
assert_eq!(super::part_b(CASE), 45.into());
}
}
2 changes: 2 additions & 0 deletions aoc_2024/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod day_12;
mod day_13;
mod day_14;
mod day_15;
mod day_16;
// [import_marker]

pub const SOLUTIONS: &[Solution] = &[
Expand All @@ -33,5 +34,6 @@ pub const SOLUTIONS: &[Solution] = &[
day_13::SOLUTION,
day_14::SOLUTION,
day_15::SOLUTION,
day_16::SOLUTION,
// [list_marker]
];

0 comments on commit 69d3f96

Please sign in to comment.