Files
advent-of-code-go/2024/day15/day15.py
T
2024-12-27 16:10:36 -05:00

215 lines
7.8 KiB
Python

diff_map: dict[str, tuple[int, int]] = {
"^": (-1, 0),
"v": (1, 0),
"<": (0, -1),
">": (0, 1),
}
def part1(input: str) -> int:
input_parts = input.split("\n\n")
grid = [list(line) for line in input_parts[0].splitlines()]
row: int = 0
col: int = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "@":
row = r
col = c
instructions = "".join(input_parts[1].splitlines())
for inst in instructions:
diff = diff_map[inst]
next_row, next_col = row + diff[0], col + diff[1]
match grid[next_row][next_col]:
case "#":
# blocked
continue
case ".":
grid[row][col] = "."
grid[next_row][next_col] = "@"
row, col = next_row, next_col
case "O":
# attempt push, keep moving in direction of diff until a . or # is hit...
not_obstacle_row, not_obstacle_col = next_row, next_col
while grid[not_obstacle_row][not_obstacle_col] == "O":
not_obstacle_row += diff[0]
not_obstacle_col += diff[1]
# if it's a wall "#", nothing moves, so only check for empty spaces "."
if grid[not_obstacle_row][not_obstacle_col] == ".":
grid[not_obstacle_row][not_obstacle_col] = "O"
grid[next_row][next_col] = "@"
grid[row][col] = "."
row, col = next_row, next_col
case _:
raise Exception("unhandled grid type: ", grid[next_row][next_col])
# print("\n".join(["".join(row) for row in grid]))
# 100 times its distance from the top edge of the map plus its distance from the left edge of the map
# 0-indexed so same as indices in 2D array
gps_sum: int = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "O":
gps_sum += r * 100 + c
return gps_sum
def part2(input: str) -> int:
input_parts = input.split("\n\n")
input_parts[0] = input_parts[0].replace("O", "[]")
input_parts[0] = input_parts[0].replace(".", "..")
input_parts[0] = input_parts[0].replace("#", "##")
input_parts[0] = input_parts[0].replace("@", "@.")
grid = [list(line) for line in input_parts[0].splitlines()]
row: int = 0
col: int = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "@":
row = r
col = c
# print("\n".join(["".join(row) for row in grid]))
instructions = "".join(input_parts[1].splitlines())
for inst in instructions:
# first see what's in the next space we want to move into...
diff = diff_map[inst]
next_row, next_col = row + diff[0], col + diff[1]
match grid[next_row][next_col]:
case "#":
continue
case ".":
grid[row][col] = "."
row += diff[0]
col += diff[1]
grid[row][col] = "@"
case "[" | "]":
# handle left and right separately because they only push one row of boxes
# at this point row == next_row
if inst in "<>":
# copy-pasta from part 1
not_obstacle_col = next_col
while grid[next_row][not_obstacle_col] in "[]":
not_obstacle_col += diff[1]
# if it's a wall "#", nothing moves, so only check for empty spaces "."
# move everything over towards the diff direction
if grid[next_row][not_obstacle_col] == ".":
for c in range(not_obstacle_col, col, -1 * diff[1]):
grid[row][c] = grid[row][c - diff[1]]
# move robot..
grid[next_row][next_col] = "@"
grid[row][col] = "."
row, col = next_row, next_col
else:
# push boxes up or down, use a stack to maintain the reverse order of boxes that
# MAY get moved
coords_to_check: list[tuple[int, int]] = [
(row, col),
]
to_check_index: int = 0
is_blocked: bool = False
while to_check_index < len(coords_to_check):
front = coords_to_check[to_check_index]
to_check_index += 1
# variable masking is no bueno, but i guess it's fine here
next_row, next_col = front[0] + diff[0], front[1] + diff[1]
next_value = grid[next_row][next_col]
if next_value == "#":
is_blocked = True
break
if next_value == "[":
coords_to_check.append((next_row, next_col))
coords_to_check.append((next_row, next_col + 1))
if next_value == "]":
coords_to_check.append((next_row, next_col))
coords_to_check.append((next_row, next_col - 1))
# if "." then do nothing... no need to check
# move everything towards diff if nothing was blocked
# in reverse order to avoid overwrites
if not is_blocked:
# didn't prevent duplicate adds in the "checking" stage, so just no-op them here
moved_set: set[tuple[int, int]] = set()
for coord in reversed(coords_to_check):
if coord in moved_set:
continue
moved_set.add(coord)
next_row, next_col = coord[0] + diff[0], coord[1] + diff[1]
grid[next_row][next_col], grid[coord[0]][coord[1]] = (
grid[coord[0]][coord[1]],
grid[next_row][next_col],
)
row, col = next_row, next_col
case _:
raise Exception("unexpected grid value: ", grid[next_row][next_col])
# print("\n".join(["".join(row) for row in grid]))
gps_sum: int = 0
for r in range(len(grid)):
for c in range(len(grid[0])):
if grid[r][c] == "[":
gps_sum += r * 100 + c
return gps_sum
small_example = """########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########
<^^>>>vv<v>>v<<"""
example = """##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########
<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^"""
input = open("input.txt").read().strip()
print(f"part1 small_example: {part1(small_example)} want 2028")
print(f"part1 example: {part1(example)} want 10092")
print(f"part1: {part1(input)} want 1413675")
print(f"part2 example: {part2(example)} want 9021")
print(f"part2: {part2(input)} want 1399772")