ROBOT_SPEED: int = 1 class Coord: x: int y: int def __init__(self, x: int, y: int): self.x = x self.y = y def __str__(self) -> str: return f'({self.x}, {self.y})' def __eq__(self, other) -> bool: return other.x == self.x and other.y == self.y def __add__(self, other): return Coord(self.x + other.x, self.y + other.y) def __sub__(self, other): return Coord(self.x - other.x, self.y - other.y) def __mul__(self, other): return Coord(self.x * other, self.y * other) def __rmul__(self, other): return Coord(self.x * other, self.y * other) def copy(self): return Coord(self.x, self.y) class Warehouse: robot: Coord walls: list[Coord] = [] boxes: list[Coord] = [] width: int height: int def __init__(self, warehouse_map: list[str]): for y, line in enumerate(warehouse_map): for x, c in enumerate(line): match c: case "#": self.walls.append(Coord(x, y)) case "@": self.robot = Coord(x, y) case "O": self.boxes.append(Coord(x, y)) self.width = len(warehouse_map[0]) self.height = len(warehouse_map) def __str__(self) -> str: w = [['.'] * self.width for _ in range(self.height)] for box in self.boxes: w[box.y][box.x] = 'O' for wall in self.walls: w[wall.y][wall.x] = '#' w[self.robot.y][self.robot.x] = '@' s = '' for row in w: for c in row: s += c s += '\n' return s def move_robot(self, command: str) -> None: dx, dy = 0, 0 match command: case '>': dx += 1 case '<': dx -= 1 case 'v': dy += 1 case '^': dy -= 1 movement = Coord(dx, dy) new_pos = self.robot + movement if new_pos in self.walls: return None if new_pos in self.boxes: if self.push_box(new_pos, movement): self.robot = new_pos return None self.robot = new_pos def push_box(self, box: Coord, movement: Coord) -> bool: new_pos: Coord = box + movement if new_pos in self.walls: return False if new_pos in self.boxes: if not self.push_box(new_pos, movement): return False self.boxes.remove(box) self.boxes.append(new_pos) return True def score(self) -> int: total = 0 for box in self.boxes: total += 100 * box.y + box.x return total class BigBox: right: Coord left: Coord def __init__(self, left: Coord): self.left = left self.right = Coord(left.x + 1, left.y) def __add__(self, other: Coord): return BigBox(self.left + other) def __eq__(self, other) -> bool: return self.left == other.left and self.right == other.right def __str__(self) -> str: return '[]' class SecondWarehouse: robot: Coord walls: list[Coord] = [] boxes: list[BigBox] = [] width: int height: int def __init__(self, warehouse_map: list[str]): for y, line in enumerate(warehouse_map): offset = 0 for x, c in enumerate(line): match c: case "#": self.walls.append(Coord(x + offset, y)) self.walls.append(Coord(x + 1 + offset, y)) case "@": self.robot = Coord(x + offset, y) case "O": self.boxes.append(BigBox(Coord(x + offset, y))) offset += 1 self.width = len(warehouse_map[0]) * 2 self.height = len(warehouse_map) def __str__(self) -> str: w = [['.'] * self.width for _ in range(self.height)] for box in self.boxes: w[box.left.y][box.left.x] = '[' w[box.right.y][box.right.x] = ']' for wall in self.walls: w[wall.y][wall.x] = '#' w[self.robot.y][self.robot.x] = '@' s = '' for row in w: for c in row: s += c s += '\n' return s def move_robot(self, command: str) -> None: dx, dy = 0, 0 match command: case '>': dx += 1 case '<': dx -= 1 case 'v': dy += 1 case '^': dy -= 1 movement = Coord(dx, dy) new_pos = self.robot + movement if new_pos in self.walls: return if box := self.is_box(new_pos): if self.push_box(box, movement): self.robot = new_pos return self.robot = new_pos def is_box(self, pos: Coord) -> BigBox | None: for box in self.boxes: if pos == box.left or pos == box.right: return box return None def push_box(self, box: BigBox, movement: Coord) -> bool: new_box: BigBox = box + movement # def push_box(self, box: Coord, movement: Coord) -> bool: # if movement.y != 0: # return self.vertical_push(box, movement) # new_pos: Coord = box + movement # new_pos2 = new_pos.copy() # if movement.x > 0: # new_pos2.x += 1 # else: # new_pos.x -= 1 # if new_pos in self.walls: # return False # if new_pos2 in self.boxes: # if not self.push_box(new_pos2, movement): # return False # self.boxes.remove(box) # self.boxes.append(new_pos) # return True def vertical_push(self, box: Coord, movement: Coord) -> bool: pass new_pos: Coord = box + movement # other_new_pos: Coord = new_pos # if box in self.left_boxes: # other_new_pos.x += 1 # else: # other_new_pos.x -= 1 # if new_pos in self.walls or other_new_pos in self.walls: # return False # if new_pos in self.left_boxes or new_pos in self.right_boxes: # if not self.vertical_push(new_pos, movement): # return False # if other_new_pos in self.left_boxes or other_new_pos in self.right_boxes: # if not self.vertical_push(other_new_pos, movement): # return False # if box in self.left_boxes: # self.left_boxes.remove(box) # self.left_boxes.append(new_pos) # self.right_boxes.remove(Coord(box.x + 1, box.y)) # self.right_boxes.append(other_new_pos) # else: # self.right_boxes.remove(box) # self.right_boxes.append(new_pos) # self.left_boxes.remove(Coord(box.x - 1, box.y)) # self.left_boxes.append(other_new_pos) # return True # def can_push(self, box: Coord, movement: Coord) -> bool: # new_pos: Coord = box + movement # if new_pos in self.right_boxes or new_pos in self.left_boxes: # return self.can_push(new_pos, movement) # return new_pos in self.walls # def score(self) -> int: # total = 0 # for box in self.left_boxes: # total += 100 * box.y + box.x # return total def main() -> None: with open('test.txt', 'r') as file: data: str = file.read() warehouse_map, movements = [s.split("\n") for s in data.split("\n\n")] movements = ''.join(movements) # Part 1 print('<', '=' * 25, ' PART 1 ', '=' * 25, '>') warehouse = Warehouse(warehouse_map) print(warehouse) for command in movements: warehouse.move_robot(command) print(warehouse) print('Score: ', warehouse.score()) # Part 2 print('<', '=' * 25, ' PART 2 ', '=' * 25, '>') warehouse = SecondWarehouse(warehouse_map) print(warehouse) # for command in movements: # warehouse.move_robot(command) # print('Movement: ', command) # print(warehouse) # input() if __name__ == '__main__': main()