from enum import Enum from typing import Union class Side(Enum): UP = 1 DOWN = 2 LEFT = 4 RIGHT = 8 class Tile: def __init__(self, uid: str, image: list[list[str]]): self.uid = uid self.image = image self.up: Union[Tile, None] = None self.down: Union[Tile, None] = None self.left: Union[Tile, None] = None self.right: Union[Tile, None] = None def flip_ud(self) -> 'Tile': cache = self.up self.up = self.down self.up = cache self.image = list(reversed(self.image)) return self def flip_lr(self) -> 'Tile': cache = self.left self.left = self.right self.right = cache for i in range(len(self.image)): self.image[i] = list(reversed(self.image[i])) return self def rotate_l(self) -> 'Tile': new_image = [] for i in range(len(self.image[0])): new_image.append([]) for line in self.image: for i in range(len(line)): new_image[i].append(list(reversed(line))[i]) self.image = new_image return self def rotate_r(self) -> 'Tile': new_image = [] for i in range(len(self.image[0])): new_image.append([]) for line in reversed(self.image): for i in range(len(line)): new_image[i].append(line[i]) self.image = new_image return self def align_to(self, edge: str, side: Side): if edge not in self.get_all_edges(): return rev = "".join(reversed(edge)) if side == Side.UP: while "".join(self.image[0]) not in [edge, rev]: self.rotate_r() if "".join(self.image[0]) == rev: self.flip_lr() elif side == side.DOWN: while "".join(self.image[len(self.image)-1]) not in [edge, rev]: self.rotate_r() if "".join(self.image[len(self.image)-1]) == rev: self.flip_lr() elif side == side.LEFT: while "".join([line[0] for line in self.image]) not in [edge, rev]: self.rotate_r() if "".join([line[0] for line in self.image]) == rev: self.flip_ud() elif side == side.RIGHT: while "".join([line[len(self.image[0])-1] for line in self.image]) not in [edge, rev]: self.rotate_r() if "".join([line[len(self.image[0])-1] for line in self.image]) == rev: self.flip_ud() def get_all_edges(self) -> list[str]: all_edges = [] up = "".join(self.image[0]) down = "".join(self.image[len(self.image)-1]) left = "".join([line[0] for line in self.image]) right = "".join([line[len(self.image[0])-1] for line in self.image]) all_edges += [up, down, left, right, "".join(reversed(up)), "".join(reversed(down)), "".join(reversed(left)), "".join(reversed(right))] return all_edges def count_connections(self) -> int: return sum([1 if x else 0 for x in [self.up, self.down, self.left, self.right]]) def print(self): print(f"Tile {self.uid}:") for line in self.image: print("".join(line)) def __str__(self): return f"Tile {self.uid}" def parse_data() -> list[Tile]: output = [] current_img = [['.']] current_title = "" with open("input.txt") as file: for line in file: line = line.rstrip() if not line: continue if line[0] == 'T': output.append(Tile(current_title, current_img)) current_title = line.split(" ")[1][:-1] current_img = [] elif line[0] in ['#', '.']: current_img.append(list(line)) output.append(Tile(current_title, current_img)) return output[1:] def connect_tiles(data: list[Tile], already_done: set[str], tile: Tile): if tile.uid in already_done: return already_done.add(tile.uid) up = "".join(tile.image[0]) down = "".join(tile.image[len(tile.image) - 1]) left = "".join([line[0] for line in tile.image]) right = "".join([line[len(tile.image[0]) - 1] for line in tile.image]) for other_tile in data: if other_tile.uid in already_done or tile.uid == other_tile.uid: continue other_edges = other_tile.get_all_edges() if up in other_edges: other_tile.align_to(up, Side.DOWN) tile.up = other_tile other_tile.down = tile elif down in other_edges: other_tile.align_to(down, Side.UP) tile.down = other_tile other_tile.up = tile elif left in other_edges: other_tile.align_to(left, Side.RIGHT) tile.left = other_tile other_tile.right = tile elif right in other_edges: other_tile.align_to(right, Side.LEFT) tile.right = other_tile other_tile.left = tile if tile.up: connect_tiles(data, already_done, tile.up) if tile.down: connect_tiles(data, already_done, tile.down) if tile.left: connect_tiles(data, already_done, tile.left) if tile.right: connect_tiles(data, already_done, tile.right) def solve_p1(data: list[Tile]) -> int: result = 1 for tile in data: if tile.count_connections() == 2: result *= int(tile.uid) return result DATA = parse_data() connect_tiles(DATA, set(), DATA[0].flip_ud()) print(solve_p1(DATA))