aoc2024/day15.py
2024-12-15 07:19:21 +01:00

129 lines
3.3 KiB
Python

from collections import deque
from tools.aoc import AOCDay
from tools.coordinate import Coordinate
from tools.grid import Grid
from typing import Any
MOVES = {
"^": (0, -1),
">": (1, 0),
"v": (0, 1),
"<": (-1, 0),
}
LARGER = {
"#": "##",
"O": "[]",
".": "..",
"@": "@.",
}
def sokoban(grid: Grid, start: Coordinate, moves: str) -> Grid:
for move in moves:
dir_ = MOVES[move]
next_pos = start + dir_
if grid.get(next_pos) == ".":
start += dir_
continue
elif grid.get(next_pos) == "#":
continue
elif grid.get(next_pos) == "O":
pos_behind_box = next_pos + dir_
while grid.get(pos_behind_box) == "O":
pos_behind_box += dir_
if grid.get(pos_behind_box) == "#":
continue
grid.set(pos_behind_box, "O")
start = next_pos
grid.set(start, ".")
continue
if move in ["<", ">"]:
# easy mode
pos_behind_box = next_pos + dir_
while grid.get(pos_behind_box) in ["[", "]"]:
pos_behind_box += dir_ * 2
if grid.get(pos_behind_box) == "#":
continue
while pos_behind_box != start:
grid.set(pos_behind_box, grid.get(pos_behind_box - dir_))
pos_behind_box -= dir_
else:
# recursive mode
wall_in_the_way = False
Q = deque([next_pos])
boxes = set()
while Q:
pos = Q.popleft()
if grid.get(pos) == "#":
wall_in_the_way = True
break
elif grid.get(pos) == ".":
continue
Q.append(pos + dir_)
if grid.get(pos) == "[":
boxes.add((pos, pos + (1, 0)))
Q.append(pos + dir_ + (1, 0))
else:
boxes.add((pos + (-1, 0), pos))
Q.append(pos + dir_ + (-1, 0))
if wall_in_the_way:
continue
for box_l, box_r in boxes:
grid.set(box_l, ".")
grid.set(box_r, ".")
for box_l, box_r in boxes:
grid.set(box_l + dir_, "[")
grid.set(box_r + dir_, "]")
start = next_pos
return grid
class Day(AOCDay):
inputs = [
[
(2028, "input15_test2"),
(10092, "input15_test"),
(1515788, "input15"),
],
[
(9021, "input15_test"),
(1516544, "input15"),
],
]
def parse_input(self, part2: bool = False) -> tuple[Grid, Coordinate, str]:
map_, moves = self.getMultiLineInputAsArray()
if part2:
map_ = ["".join([LARGER[x] for x in y]) for y in map_]
grid = Grid.from_data(map_, default=".")
start = list(grid.find("@"))[0]
grid.set(start, ".")
return grid, start, "".join(moves)
def part1(self) -> Any:
grid = sokoban(*self.parse_input())
return sum(100 * c.y + c.x for c in grid.find("O"))
def part2(self) -> Any:
grid = sokoban(*self.parse_input(True))
return sum(100 * c.y + c.x for c in grid.find("["))
if __name__ == "__main__":
day = Day(2024, 15)
day.run(verbose=True)