Day 24, part 1

This commit is contained in:
jazzpi 2022-12-24 18:40:37 +01:00
parent 98fcdfeaef
commit a7d32abb93
4 changed files with 210 additions and 0 deletions

13
src/bin/d24p1.rs Normal file
View File

@ -0,0 +1,13 @@
use aoc22::{
day24::{self},
util,
};
pub fn main() {
let (map, start, target) = day24::parse_map(&util::parse_input());
day24::print_map(&map, &start);
let rounds = day24::find_path(&map, &start, &target);
println!("Goal is reachable in {} min", rounds);
}

177
src/day24.rs Normal file
View File

@ -0,0 +1,177 @@
use std::collections::HashSet;
use crate::util::{Coord, Dir};
#[derive(Debug, Clone)]
pub enum Tile {
Empty,
Wall,
Blizzards(Vec<Dir>),
}
/// Returns (map, start, end)
pub fn parse_map(input: &str) -> (Vec<Vec<Tile>>, Coord, Coord) {
let mut map = Vec::new();
for line in input.lines() {
let mut row = Vec::new();
for c in line.chars() {
row.push(match c {
'.' => Tile::Empty,
'#' => Tile::Wall,
_ => Tile::Blizzards(vec![Dir::from_char(c).unwrap()]),
});
}
map.push(row);
}
assert!(map.len() >= 2);
let last_row = map.len() - 1;
let start = map[0]
.iter()
.position(|t| matches!(t, Tile::Empty))
.unwrap();
let end = map[last_row]
.iter()
.position(|t| matches!(t, Tile::Empty))
.unwrap();
(map, (0, start), (last_row, end))
}
pub fn print_map(map: &Vec<Vec<Tile>>, expedition: &Coord) {
for (y, row) in map.iter().enumerate() {
for (x, tile) in row.iter().enumerate() {
if *expedition == (y, x) {
print!("E");
} else {
match tile {
Tile::Empty => print!("."),
Tile::Wall => print!("#"),
Tile::Blizzards(blizzards) => {
if blizzards.len() > 1 {
print!("{}", blizzards.len());
} else {
print!("{}", blizzards[0].to_str());
}
}
}
}
}
print!("\n");
}
}
pub fn next_map(map: &Vec<Vec<Tile>>) -> Vec<Vec<Tile>> {
assert!(map.len() > 0);
// Outermost rows/columns are walls
let min_y = 1;
let max_y = map.len() - 2;
let min_x = 1;
let max_x = map[0].len() - 2;
// Start with a map without blizzards
let mut result: Vec<Vec<Tile>> = map
.iter()
.map(|r| {
r.iter()
.map(|t| {
if matches!(t, Tile::Blizzards(_)) {
Tile::Empty
} else {
t.clone()
}
})
.collect()
})
.collect();
for y in 0..map.len() {
for x in 0..map[0].len() {
if let Tile::Blizzards(blizzards) = &map[y][x] {
for blizzard_dir in blizzards {
let new_tile = match blizzard_dir {
Dir::Right => {
if x == max_x {
(y, min_x)
} else {
(y, x + 1)
}
}
Dir::Down => {
if y == max_y {
(min_y, x)
} else {
(y + 1, x)
}
}
Dir::Left => {
if x == min_x {
(y, max_x)
} else {
(y, x - 1)
}
}
Dir::Up => {
if y == min_y {
(max_y, x)
} else {
(y - 1, x)
}
}
};
let new_tile = &mut result[new_tile.0][new_tile.1];
if let Tile::Blizzards(blizzards) = new_tile {
blizzards.push(*blizzard_dir);
} else {
assert!(matches!(new_tile, Tile::Empty));
*new_tile = Tile::Blizzards(vec![*blizzard_dir]);
}
}
}
}
}
result
}
/// Returns length of the shortest path
pub fn find_path(initial: &Vec<Vec<Tile>>, start: &Coord, target: &Coord) -> usize {
let mut map = initial.clone();
let mut to_check = HashSet::new();
to_check.insert(*start);
let mut rounds = 0;
loop {
map = next_map(&map);
rounds += 1;
let mut check_next = HashSet::new();
for pos in to_check {
let mut reachable = vec![
pos,
(pos.0 + 1, pos.1),
(pos.0, pos.1 + 1),
(pos.0, pos.1 - 1),
];
// This would underflow in the first round
if pos.0 != 0 {
reachable.push((pos.0 - 1, pos.1));
}
for p in reachable {
if p == *target {
return rounds;
}
if matches!(map[p.0][p.1], Tile::Empty) {
check_next.insert(p);
}
}
}
to_check = check_next;
}
}

View File

@ -14,6 +14,7 @@ pub mod day20;
pub mod day21;
pub mod day22;
pub mod day23;
pub mod day24;
pub mod day3;
pub mod day4;
pub mod day5;

View File

@ -144,6 +144,25 @@ impl Dir {
}
}
pub fn from_char(c: char) -> Option<Dir> {
match c {
'^' => Some(Dir::Up),
'>' => Some(Dir::Right),
'v' => Some(Dir::Down),
'<' => Some(Dir::Left),
_ => None,
}
}
pub fn to_str(&self) -> &'static str {
match self {
Dir::Right => ">",
Dir::Down => "v",
Dir::Left => "<",
Dir::Up => "^",
}
}
pub fn cw(&self) -> Dir {
let d = *self as usize;
Self::from_usize((d + 1) % 4).unwrap()