#!/usr/bin/env python3 from sys import argv from math import sqrt tiles = {} for tile in map(lambda x: x.splitlines(), open(argv[1]).read().strip().split("\n\n")): tiles[int(tile[0][5:-1])] = "".join(tile[1:]) size = int(sqrt(len(tiles))) tsize = int(sqrt(len(next(iter(tiles.values()))))) sides = {} sides_rev = {} for tile in tiles: data = tiles[tile] up = data[0:tsize] down = data[tsize ** 2 - tsize:tsize ** 2] left = "" right = "" for x in range(tsize): left += data[tsize * x] right += data[tsize * x + tsize - 1] up = up[::-1] right = right[::-1] sides[tile] = [ up, left, down, right, up[::-1], left[::-1], down[::-1], right[::-1] ] for i, side in enumerate(sides[tile]): if side not in sides_rev: sides_rev[side] = [] sides_rev[side].append((tile, i)) tiles_noconn = {} for side in sides_rev: if len(sides_rev[side]) == 1: tile, orien = sides_rev[side][0] if tile not in tiles_noconn: tiles_noconn[tile] = [] tiles_noconn[tile].append(orien) tiles_corner = [i for i, noconn in tiles_noconn.items() if len(noconn) == 4] print(tiles_corner[0] * tiles_corner[1] * tiles_corner[2] * tiles_corner[3]) # Figure out the orientation of the top-left corner tile we pick orien = tiles_noconn[tiles_corner[0]] if 3 in orien and 0 in orien: orien = 3 else: orien = min(orien) corner = (tiles_corner[0], orien) def get_orien(base, add): if base & 4: add = 4 - add return ((base + add) & 3) | (base & 4) # Create a connection graph conn = {} for side in sides_rev.values(): if len(side) != 2: continue conn[side[0]] = side[1] conn[side[1]] = side[0] # Put the grid together, starting from the top-left corner, going right then down grid = [] for y in range(size): if y: tile_top, orien_top = grid[y-1][0] orien_top = get_orien(orien_top, 2) tile, orien = conn[(tile_top, orien_top)] orien ^= 4 row = [(tile, orien)] else: row = [corner] for x in range(1, size): tile_left, orien_left = row[x-1] orien_left = get_orien(orien_left, 3) tile, orien = conn[(tile_left, orien_left)] orien = get_orien(orien, -1) orien ^= 6 row.append((tile, orien)) grid.append(row) def rotate(tile, orien): # Flip the tile if necessary if orien >= 4: tile = [row[::-1] for row in tile] orien = (4 - orien) % 4 # Rotate the tile for x in range(orien): tile = [[tile[len(iy) - x - 1][y] for x, ix in enumerate(iy)] for y, iy in enumerate(tile)] return tile # Create image ssize = tsize - 2 image = [[0] * size * ssize for x in range(size * ssize)] for y, row in enumerate(grid): y *= ssize for x, (tile, orien) in enumerate(row): x *= ssize # Convert tile data into grid, strip the edges tile = [tiles[tile][x + 1:x + tsize - 1] for x in range(tsize, tsize ** 2 - tsize, tsize)] tile = rotate(tile, orien) # Store into image for iy, irow in enumerate(tile): for ix, c in enumerate(irow): image[y + iy][x + ix] = c # Find monsters monster = """ # # ## ## ### # # # # # # """ monster = [(y,x) for y, line in enumerate(monster[1:-1].split("\n")) for x, char in enumerate(line) if char == "#"] monster_h = 3 monster_w = 20 for orien in range(8): cur_image = rotate(image, orien) count = 0 for y in range(size * ssize - monster_h): for x in range(size * ssize - monster_w): for m in monster: if cur_image[m[0] + y][m[1] + x] != "#": break else: for m in monster: cur_image[m[0] + y][m[1] + x] = "O" count += 1 if count: break # Tally roughness print(sum(1 for row in cur_image for char in row if char == "#")) # for row in cur_image: # print("".join(row))