283 lines
6.8 KiB
Python
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
|