wat heeft deze guy met plattegrondjes, aoc dag 15
This commit is contained in:
392
advent_of_code/2024/15/src/main.rs
Normal file
392
advent_of_code/2024/15/src/main.rs
Normal file
@@ -0,0 +1,392 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use nom::{
|
||||
branch::alt,
|
||||
character::complete::{char, multispace1},
|
||||
multi::{many1, separated_list1},
|
||||
sequence::separated_pair,
|
||||
IResult, Parser,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum Tile {
|
||||
Empty,
|
||||
Wall,
|
||||
Box,
|
||||
Player,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
enum UpgradedTile {
|
||||
Empty,
|
||||
Wall,
|
||||
Player,
|
||||
LeftBox,
|
||||
RightBox,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum Move {
|
||||
Up,
|
||||
Right,
|
||||
Down,
|
||||
Left,
|
||||
}
|
||||
|
||||
fn parse(input: &str) -> IResult<&str, (Vec<Vec<Tile>>, Vec<Move>)> {
|
||||
let (input, result) = separated_pair(
|
||||
separated_list1(
|
||||
multispace1,
|
||||
many1(alt((char('#'), char('.'), char('@'), char('O'))).map(|c| {
|
||||
use Tile::*;
|
||||
match c {
|
||||
'#' => Wall,
|
||||
'.' => Empty,
|
||||
'@' => Player,
|
||||
'O' => Box,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})),
|
||||
),
|
||||
multispace1,
|
||||
separated_list1(
|
||||
multispace1,
|
||||
many1(alt((char('^'), char('>'), char('<'), char('v'))).map(|c| {
|
||||
use Move::*;
|
||||
match c {
|
||||
'^' => Up,
|
||||
'>' => Right,
|
||||
'<' => Left,
|
||||
'v' => Down,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
})),
|
||||
)
|
||||
.map(|x| x.into_iter().flatten().collect()),
|
||||
)(input)?;
|
||||
|
||||
Ok((input, result))
|
||||
}
|
||||
|
||||
fn calc_score(map: &Vec<Vec<Tile>>) -> u64 {
|
||||
let mut score = 0;
|
||||
|
||||
for (j, row) in map.iter().enumerate() {
|
||||
for (i, t) in row.iter().enumerate() {
|
||||
if *t == Tile::Box {
|
||||
score += 100 * j + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
score as u64
|
||||
}
|
||||
|
||||
fn calc_upgraded_score(map: &Vec<Vec<UpgradedTile>>) -> u64 {
|
||||
let mut score = 0;
|
||||
|
||||
for (j, row) in map.iter().enumerate() {
|
||||
for (i, t) in row.iter().enumerate() {
|
||||
if *t == UpgradedTile::LeftBox {
|
||||
score += 100 * j + i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
score as u64
|
||||
}
|
||||
|
||||
fn move_one(coord: &(usize, usize), m: &Move) -> (usize, usize) {
|
||||
use Move::*;
|
||||
let (x, y) = *coord;
|
||||
match m {
|
||||
Up => (x, y - 1),
|
||||
Right => (x + 1, y),
|
||||
Down => (x, y + 1),
|
||||
Left => (x - 1, y),
|
||||
}
|
||||
}
|
||||
|
||||
fn move_player(map: &mut Vec<Vec<Tile>>, pos: &mut (usize, usize), m: Move) {
|
||||
use Tile::*;
|
||||
|
||||
let (x, y) = *pos;
|
||||
assert_eq!(map[y][x], Player);
|
||||
|
||||
let (tx, ty) = move_one(&pos, &m);
|
||||
|
||||
match map[ty][tx] {
|
||||
Empty => {
|
||||
map[y][x] = Empty;
|
||||
map[ty][tx] = Player;
|
||||
*pos = (tx, ty);
|
||||
}
|
||||
Wall => {}
|
||||
Box => {
|
||||
let (mut bx, mut by) = (tx, ty);
|
||||
while map[by][bx] == Box {
|
||||
(bx, by) = move_one(&(bx, by), &m);
|
||||
}
|
||||
|
||||
match map[by][bx] {
|
||||
Empty => {
|
||||
map[by][bx] = Box;
|
||||
map[ty][tx] = Player;
|
||||
map[y][x] = Empty;
|
||||
*pos = (tx, ty);
|
||||
}
|
||||
Wall => {}
|
||||
Box => unreachable!(),
|
||||
Player => unreachable!(),
|
||||
}
|
||||
}
|
||||
Player => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn solve_1(input: &str) -> u64 {
|
||||
use Tile::Player;
|
||||
|
||||
let (_, (mut map, moves)) = parse(input).unwrap();
|
||||
|
||||
let (mut x, mut y) = (0, 0);
|
||||
for (j, row) in map.iter().enumerate() {
|
||||
let mut found = false;
|
||||
for (i, t) in row.iter().enumerate() {
|
||||
if *t == Player {
|
||||
x = i;
|
||||
y = j;
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if found {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut pos = (x, y);
|
||||
for m in moves {
|
||||
move_player(&mut map, &mut pos, m);
|
||||
}
|
||||
|
||||
calc_score(&map)
|
||||
}
|
||||
|
||||
fn upgrade_map(map: &Vec<Vec<Tile>>) -> Vec<Vec<UpgradedTile>> {
|
||||
let mut new_map = vec![];
|
||||
|
||||
for row in map {
|
||||
let mut new_row = vec![];
|
||||
|
||||
for t in row {
|
||||
match t {
|
||||
Tile::Empty => {
|
||||
new_row.push(UpgradedTile::Empty);
|
||||
new_row.push(UpgradedTile::Empty);
|
||||
}
|
||||
Tile::Wall => {
|
||||
new_row.push(UpgradedTile::Wall);
|
||||
new_row.push(UpgradedTile::Wall);
|
||||
}
|
||||
Tile::Box => {
|
||||
new_row.push(UpgradedTile::LeftBox);
|
||||
new_row.push(UpgradedTile::RightBox);
|
||||
}
|
||||
Tile::Player => {
|
||||
new_row.push(UpgradedTile::Player);
|
||||
new_row.push(UpgradedTile::Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new_map.push(new_row);
|
||||
}
|
||||
|
||||
new_map
|
||||
}
|
||||
|
||||
fn rev_move(m: &Move) -> Move {
|
||||
use Move::*;
|
||||
match m {
|
||||
Up => Down,
|
||||
Right => Left,
|
||||
Down => Up,
|
||||
Left => Right,
|
||||
}
|
||||
}
|
||||
|
||||
fn upgraded_move_player(map: &mut Vec<Vec<UpgradedTile>>, pos: &mut (usize, usize), m: Move) {
|
||||
use Move::*;
|
||||
use UpgradedTile::*;
|
||||
|
||||
let (x, y) = *pos;
|
||||
assert_eq!(map[y][x], Player);
|
||||
|
||||
let (tx, ty) = move_one(&pos, &m);
|
||||
|
||||
// These are all the tiles that need to be checked yet
|
||||
let mut stack = vec![];
|
||||
// These are all the tiles we have checked, in order to shift them if needed
|
||||
let mut visited = HashSet::new();
|
||||
|
||||
match map[ty][tx] {
|
||||
Empty => {}
|
||||
Wall => return,
|
||||
Player => unreachable!(),
|
||||
LeftBox => {
|
||||
if m == Up || m == Down {
|
||||
stack.push((tx, ty));
|
||||
stack.push((tx + 1, ty));
|
||||
|
||||
visited.insert(((tx, ty), LeftBox));
|
||||
visited.insert(((tx + 1, ty), RightBox));
|
||||
} else if m == Right {
|
||||
let (mut bx, mut by) = (tx, ty);
|
||||
|
||||
while map[by][bx] == LeftBox || map[by][bx] == RightBox {
|
||||
visited.insert(((bx, by), map[by][bx].clone()));
|
||||
(bx, by) = move_one(&(bx, by), &m);
|
||||
}
|
||||
|
||||
match map[by][bx] {
|
||||
Empty => {}
|
||||
Wall => return,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
RightBox => {
|
||||
if m == Up || m == Down {
|
||||
stack.push((tx, ty));
|
||||
stack.push((tx - 1, ty));
|
||||
|
||||
visited.insert(((tx, ty), RightBox));
|
||||
visited.insert(((tx - 1, ty), LeftBox));
|
||||
} else if m == Left {
|
||||
let (mut bx, mut by) = (tx, ty);
|
||||
|
||||
while map[by][bx] == LeftBox || map[by][bx] == RightBox {
|
||||
visited.insert(((bx, by), map[by][bx].clone()));
|
||||
(bx, by) = move_one(&(bx, by), &m);
|
||||
}
|
||||
|
||||
match map[by][bx] {
|
||||
Empty => {}
|
||||
Wall => return,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
} else {
|
||||
unreachable!();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while !stack.is_empty() {
|
||||
let (sx, sy) = stack.pop().unwrap_or_default();
|
||||
let (bx, by) = move_one(&(sx, sy), &m);
|
||||
|
||||
match map[by][bx] {
|
||||
Empty => {}
|
||||
Wall => return,
|
||||
Player => unreachable!(),
|
||||
LeftBox => {
|
||||
if visited.insert(((bx, by), LeftBox)) {
|
||||
stack.push((bx, by));
|
||||
}
|
||||
if visited.insert(((bx + 1, by), RightBox)) {
|
||||
stack.push((bx + 1, by));
|
||||
}
|
||||
}
|
||||
RightBox => {
|
||||
if visited.insert(((bx, by), RightBox)) {
|
||||
stack.push((bx, by));
|
||||
}
|
||||
if visited.insert(((bx - 1, by), LeftBox)) {
|
||||
stack.push((bx - 1, by));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let old_map = map.clone();
|
||||
|
||||
// Because of early returns in case of wall and empty and the empty stack in the case of a
|
||||
// horizontal move, we can always assume we hit a box somewhere along the way and we have
|
||||
// to move all those boxes here
|
||||
for ((vx, vy), vt) in visited.iter() {
|
||||
let (px, py) = move_one(&(*vx, *vy), &rev_move(&m));
|
||||
if !visited.contains(&((px, py), old_map[py][px].clone())) {
|
||||
map[*vy][*vx] = Empty;
|
||||
}
|
||||
|
||||
let (nx, ny) = move_one(&(*vx, *vy), &m);
|
||||
map[ny][nx] = vt.clone();
|
||||
}
|
||||
|
||||
map[y][x] = Empty;
|
||||
map[ty][tx] = Player;
|
||||
*pos = (tx, ty);
|
||||
}
|
||||
|
||||
fn solve_2(input: &str) -> u64 {
|
||||
use UpgradedTile::Player;
|
||||
|
||||
let (_, (map, moves)) = parse(input).unwrap();
|
||||
let mut map = upgrade_map(&map);
|
||||
|
||||
let (mut x, mut y) = (0, 0);
|
||||
for (j, row) in map.iter().enumerate() {
|
||||
let mut found = false;
|
||||
for (i, t) in row.iter().enumerate() {
|
||||
if *t == Player {
|
||||
x = i;
|
||||
y = j;
|
||||
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if found {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut pos = (x, y);
|
||||
for m in moves {
|
||||
upgraded_move_player(&mut map, &mut pos, m);
|
||||
}
|
||||
|
||||
calc_upgraded_score(&map)
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Hello, this is Patrick!");
|
||||
|
||||
let input = include_str!("../input.txt");
|
||||
|
||||
let result_1 = solve_1(input);
|
||||
println!("The sum of all boxes' GPS coordinates is {}", result_1);
|
||||
|
||||
let result_2 = solve_2(input);
|
||||
println!("The sum of all big boxes' GPS coordinates is {}", result_2);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_1() {
|
||||
let test_input = include_str!("../test.txt");
|
||||
assert_eq!(solve_1(test_input), 10092);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_2() {
|
||||
let test_input = include_str!("../test.txt");
|
||||
assert_eq!(solve_2(test_input), 9021);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user