generated from public/aoc_template
116 lines
2.8 KiB
Python
116 lines
2.8 KiB
Python
from collections import deque
|
|
from enum import Enum
|
|
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": "[]",
|
|
".": "..",
|
|
"@": "@.",
|
|
}
|
|
|
|
|
|
class Tile(str, Enum):
|
|
EMPTY = "."
|
|
WALL = "#"
|
|
BOX = "O"
|
|
BOX_LEFT = "["
|
|
BOX_RIGHT = "]"
|
|
|
|
|
|
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) == Tile.WALL:
|
|
continue
|
|
elif grid.get(next_pos) == Tile.EMPTY:
|
|
start = next_pos
|
|
continue
|
|
|
|
# neither a wall, nor empty space? There must be a box in the way
|
|
to_move = set()
|
|
wall_in_the_way = False
|
|
queue = deque([next_pos])
|
|
while queue:
|
|
pos = queue.popleft()
|
|
pos_tile = grid.get(pos)
|
|
if pos_tile == Tile.EMPTY:
|
|
continue
|
|
|
|
if pos_tile == Tile.WALL:
|
|
wall_in_the_way = True
|
|
break
|
|
|
|
if (pos, pos_tile) in to_move:
|
|
continue
|
|
|
|
to_move.add((pos, pos_tile))
|
|
queue.append(pos + dir_)
|
|
if pos_tile in [Tile.BOX_LEFT, Tile.BOX_RIGHT]:
|
|
if pos_tile == Tile.BOX_LEFT:
|
|
queue.append(pos + (1, 0))
|
|
else:
|
|
queue.append(pos + (-1, 0))
|
|
|
|
if wall_in_the_way:
|
|
continue
|
|
|
|
for box, _ in to_move:
|
|
grid.set(box, Tile.EMPTY)
|
|
|
|
for box, box_tile in to_move:
|
|
grid.set(box + dir_, box_tile)
|
|
|
|
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=Tile.EMPTY, translate={r"\.#O\[\]": Tile})
|
|
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)
|