Files
advent-of-code-2024/day15/code.py
Dmitry Fedotov 2d7d20ee59 add day 15
2025-01-05 19:11:24 +03:00

283 lines
6.8 KiB
Python

import sys
sys.path.append('../aoclib')
from aoclib import Input
L = '<'
U = '^'
R = '>'
D = 'v'
STR_ROBOT = '@'
STR_EMPTY = '.'
STR_WALL = '#'
STR_BOX = 'O'
STR_BOX_L = '['
STR_BOX_R = ']'
class Map:
def __init__(self, grid: list[list]):
self.grid = grid
def __str__(self):
return '\n'.join(''.join(str(p) for p in line) for line in self.grid)
def swap(self, x1, y1, x2, y2):
self.grid[y1][x1], self.grid[y2][x2] = self.grid[y2][x2], self.grid[y1][x1]
def type_is(self, x, y: int, obj):
return type(self.grid[y][x]) == type(obj)
def robot_pos(self):
for y in range(len(self.grid)):
for x in range(len(self.grid[0])):
if self.type_is(x, y, Robot()):
return x, y
class Instruction:
def __init__(self, s: str):
self.dir = s
def __str__(self):
return self.dir
def right(self) -> bool:
return self.dir == R
def left(self) -> bool:
return self.dir == L
def up(self) -> bool:
return self.dir == U
def down(self) -> bool:
return self.dir == D
class Point:
def __init__(self, s=STR_EMPTY):
self.s = s
def __str__(self):
return self.s
class Robot(Point):
def __init__(self, s=STR_ROBOT):
super().__init__(s)
class Box(Point):
def __init__(self, s=STR_BOX, left=False):
super().__init__(s)
self.l = left
def is_left(self) -> bool:
return self.l
class Wall(Point):
def __init__(self, s=STR_WALL):
super().__init__(s)
class Solution:
def __init__(self, m: Map, inst: list[Instruction]):
self.m = m
self.inst = inst
def apply(self, cx, cy, ins: Instruction) -> bool:
x, y = cx, cy
if ins.up():
y -= 1
elif ins.down():
y += 1
elif ins.left():
x -= 1
elif ins.right():
x += 1
if self.m.type_is(x, y, Wall()):
return False
if self.m.type_is(x, y, Point()):
self.m.swap(cx, cy, x, y)
return True
# we have a box
if not self.apply(x, y, ins):
return False
self.m.swap(cx, cy, x, y)
return True
def apply_vert(self, cx, cy, ins: Instruction):
x, y = cx, cy
if ins.up():
y -= 1
elif ins.down():
y += 1
if self.m.type_is(x, y, Wall()):
return
if self.m.type_is(x, y, Point()):
self.m.swap(cx, cy, x, y)
return
box = self.m.grid[y][x]
if not self.move_box(x, y, box, ins):
return
self.move_box(x, y, box, ins, act=True)
self.m.swap(cx, cy, x, y)
def move_box(self, cx, cy, b: Box, ins: Instruction, act=False) -> bool:
'''here comes the ugly part :) '''
x1, y = cx, cy
x2 = None
if ins.up():
y -= 1
elif ins.down():
y += 1
if b.is_left():
x2 = x1+1
else:
x2 = x1-1
if self.m.type_is(x1, y, Point()) and self.m.type_is(x2, y, Point()):
if act:
self.m.swap(x1, cy, x1, y)
self.m.swap(x2, cy, x2, y)
return True
if self.m.type_is(x1, y, Wall()) or self.m.type_is(x2, y, Wall()):
return False
m1, m2 = True, True
if self.m.type_is(x1, y, Box()):
box = self.m.grid[y][x1]
m1 = self.move_box(x1, y, box, ins, act)
if self.m.type_is(x2, y, Box()):
box = self.m.grid[y][x2]
m2 = self.move_box(x2, y, box, ins, act)
if m1 and m2 and act:
self.m.swap(x1, cy, x1, y)
self.m.swap(x2, cy, x2, y)
return m1 and m2
def solve1(self):
for ins in self.inst:
x, y = self.m.robot_pos()
self.apply(x, y, ins)
s = 0
for y in range(len(self.m.grid)):
for x in range(len(self.m.grid[0])):
if self.m.type_is(x, y, Box()):
s += y*100 + x
return s
def solve2(self):
for ins in self.inst:
x, y = self.m.robot_pos()
if ins.left() or ins.right():
self.apply(x, y, ins)
else:
self.apply_vert(x, y, ins)
s = 0
for y in range(len(self.m.grid)):
for x in range(len(self.m.grid[0])):
if self.m.type_is(x, y, Box()) and self.m.grid[y][x].is_left():
s += y*100 + x
return s
def parse_input(lines: list[str]):
grid = []
instructions = []
for l in lines:
if l.startswith(STR_WALL):
grid.append(list(l))
elif not l:
continue
else:
instructions.extend(Instruction(s) for s in list(l))
for y in range(len(grid)):
for x in range(len(grid[0])):
s = grid[y][x]
obj = None
if s == STR_WALL:
obj = Wall()
elif s == STR_BOX:
obj = Box()
elif s == STR_ROBOT:
obj = Robot()
else:
obj = Point()
grid[y][x] = obj
m = Map(grid)
return Solution(m, instructions)
def parse_input2(lines: list[str]):
grid = []
instructions = []
for l in lines:
if l.startswith(STR_WALL):
row = []
for s in l:
if s == STR_WALL:
row.extend([s, s])
elif s == STR_BOX:
row.extend([STR_BOX_L, STR_BOX_R])
elif s == STR_ROBOT:
row.extend([s, STR_EMPTY])
elif s == STR_EMPTY:
row.extend([s, s])
grid.append(row)
elif not l:
continue
else:
instructions.extend(Instruction(s) for s in list(l))
for y in range(len(grid)):
for x in range(len(grid[0])):
s = grid[y][x]
obj = None
if s == STR_WALL:
obj = Wall()
elif s == STR_BOX_L:
obj = Box(s, True)
elif s == STR_BOX_R:
obj = Box(s, False)
elif s == STR_ROBOT:
obj = Robot()
else:
obj = Point()
grid[y][x] = obj
m = Map(grid)
return Solution(m, instructions)
def solve1(lines: list[str]):
s = parse_input(lines)
return s.solve1()
def solve2(lines: list[str]):
s = parse_input2(lines)
return s.solve2()
if __name__ == '__main__':
#lines = Input('input_test.txt').lines()
lines = Input('input.txt').lines()
#part 1
print('part 1:', solve1(lines)) # 1456590
#part 2
print('part 2:', solve2(lines)) # 1489116