|
@@ -0,0 +1,199 @@
|
|
|
|
+from math import sqrt
|
|
|
|
+lines = []
|
|
|
|
+
|
|
|
|
+with open("20.input") as f:
|
|
|
|
+ for line in f.readlines():
|
|
|
|
+ lines.append(line.strip())
|
|
|
|
+
|
|
|
|
+def parse(lines):
|
|
|
|
+ tiles = []
|
|
|
|
+ fragments = []
|
|
|
|
+ id = None
|
|
|
|
+ for line in lines:
|
|
|
|
+ if line.startswith("Tile"):
|
|
|
|
+ id = int(line[5:-1])
|
|
|
|
+ elif line == "":
|
|
|
|
+ if fragments == []:
|
|
|
|
+ continue
|
|
|
|
+ tiles.append((id, fragments))
|
|
|
|
+ fragments = []
|
|
|
|
+ else:
|
|
|
|
+ fragments.append(list(line))
|
|
|
|
+ return tiles
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def column(fragments, i):
|
|
|
|
+ return [row[i] for row in fragments]
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def rotate_90(fragments):
|
|
|
|
+ return [reverse_of(column(fragments, i)) for i in range(0, len(fragments))]
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def all_rotations(grid):
|
|
|
|
+ rotations = []
|
|
|
|
+ for i in range(0, 4):
|
|
|
|
+ grid = rotate_90(grid)
|
|
|
|
+ rotations.append(grid)
|
|
|
|
+ return rotations
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def all_orientations(grid):
|
|
|
|
+ return all_rotations(grid) + all_rotations(reverse_of(grid))
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def all_sides(fragments):
|
|
|
|
+ sides = [fragments[0], fragments[-1], column(fragments, 0), column(fragments, -1)]
|
|
|
|
+ sides += [reverse_of(side) for side in sides]
|
|
|
|
+ return sides
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def reverse_of(l):
|
|
|
|
+ l2 = list(l)
|
|
|
|
+ l2.reverse()
|
|
|
|
+ return l2
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def valid_orientations(tile, left, right, top, bottom):
|
|
|
|
+ result = []
|
|
|
|
+ for orientation in all_orientations(tile[1]):
|
|
|
|
+ if left != None and column(orientation, 0) not in left:
|
|
|
|
+ continue
|
|
|
|
+ elif right != None and column(orientation, -1) not in right:
|
|
|
|
+ continue
|
|
|
|
+ elif top != None and orientation[0] not in top:
|
|
|
|
+ continue
|
|
|
|
+ elif bottom != None and orientation[-1] not in bottom:
|
|
|
|
+ continue
|
|
|
|
+ else:
|
|
|
|
+ result.append(orientation)
|
|
|
|
+ return result
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+def possible_orders(prefix, width, tiles, uniq_sides):
|
|
|
|
+ index = len(prefix)
|
|
|
|
+ if index == width * width:
|
|
|
|
+ return [prefix]
|
|
|
|
+ left = None
|
|
|
|
+ right = None
|
|
|
|
+ top = None
|
|
|
|
+ bottom = None
|
|
|
|
+ result = []
|
|
|
|
+ if index >= width:
|
|
|
|
+ # Must match above tile
|
|
|
|
+ top = [prefix[index - width][-1]]
|
|
|
|
+ if index % width != 0:
|
|
|
|
+ # Must match left tile
|
|
|
|
+ left = [column(prefix[-1], -1)]
|
|
|
|
+ if index in [0, width - 1, width * (width - 1), width * width - 1]:
|
|
|
|
+ key = "corner"
|
|
|
|
+ elif index < width or index > width * (width - 1) or index % width in [0, width - 1]:
|
|
|
|
+ key = "edge"
|
|
|
|
+ else:
|
|
|
|
+ key = "center"
|
|
|
|
+ for tile in tiles[key]:
|
|
|
|
+ if index % width == 0:
|
|
|
|
+ left = uniq_sides[tile[0]]
|
|
|
|
+ if int(index / width) == 0:
|
|
|
|
+ top = uniq_sides[tile[0]]
|
|
|
|
+ if index % width == width - 1:
|
|
|
|
+ right = uniq_sides[tile[0]]
|
|
|
|
+ for valid in valid_orientations(tile, left, right, top, bottom):
|
|
|
|
+ tiles_next = dict(tiles)
|
|
|
|
+ tiles_next[key] = [t for t in tiles[key] if t != tile]
|
|
|
|
+ result += possible_orders(prefix + [valid], width, tiles_next, uniq_sides)
|
|
|
|
+ return result
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+tiles = parse(lines)
|
|
|
|
+
|
|
|
|
+sides = [(side, tile[0]) for tile in tiles for side in all_sides(tile[1])]
|
|
|
|
+uniq_sides = {}
|
|
|
|
+counts = {}
|
|
|
|
+for (side, tile_id) in sides:
|
|
|
|
+ same_side = len([1 for (s, id) in sides if s == side])
|
|
|
|
+ if same_side == 1:
|
|
|
|
+ count = counts.get(tile_id, 0)
|
|
|
|
+ count += 1
|
|
|
|
+ counts[tile_id] = count
|
|
|
|
+ uniq = uniq_sides.get(tile_id, [])
|
|
|
|
+ uniq.append(side)
|
|
|
|
+ uniq_sides[tile_id] = uniq
|
|
|
|
+
|
|
|
|
+corners = []
|
|
|
|
+edges = []
|
|
|
|
+prod = 1
|
|
|
|
+
|
|
|
|
+for (k, v) in counts.items():
|
|
|
|
+ if v == 4:
|
|
|
|
+ prod *= k
|
|
|
|
+ corners += [tile for tile in tiles if tile[0] == k]
|
|
|
|
+ elif v == 2:
|
|
|
|
+ edges += [tile for tile in tiles if tile[0] == k]
|
|
|
|
+
|
|
|
|
+print("Answer 1:", prod)
|
|
|
|
+
|
|
|
|
+center = [tile for tile in tiles if tile not in corners and tile not in edges]
|
|
|
|
+width = int(sqrt(len(tiles)))
|
|
|
|
+first_corner = corners.pop()
|
|
|
|
+valid_corner = valid_orientations(first_corner, uniq_sides[first_corner[0]], None, uniq_sides[first_corner[0]], None)[0]
|
|
|
|
+orders = possible_orders([valid_corner], width, {'center': center, 'edge': edges, 'corner': corners}, uniq_sides)
|
|
|
|
+assert len(orders) == 1
|
|
|
|
+
|
|
|
|
+def strip_borders(frags):
|
|
|
|
+ return [row[1:-1] for row in frags[1:-1]]
|
|
|
|
+
|
|
|
|
+assert strip_borders([[1, 2, 3], [4,5,6], [7,8,9]]) == [[5]]
|
|
|
|
+
|
|
|
|
+map = []
|
|
|
|
+order = orders[0]
|
|
|
|
+while len(order) > 0:
|
|
|
|
+ row = [strip_borders(frags) for frags in order[0:width]]
|
|
|
|
+ for i in range(0, len(row[0])):
|
|
|
|
+ map.append(list(''.join([''.join(frags[i]) for frags in row])))
|
|
|
|
+ del order[0:width]
|
|
|
|
+
|
|
|
|
+sea_monster = [" # ",
|
|
|
|
+ "# ## ## ###",
|
|
|
|
+ " # # # # # # "]
|
|
|
|
+
|
|
|
|
+sea_monster = [list(row) for row in sea_monster]
|
|
|
|
+
|
|
|
|
+def check_monster(map, x, y, monster):
|
|
|
|
+ for local_x in range(0, len(monster[0])):
|
|
|
|
+ for local_y in range(0, len(monster)):
|
|
|
|
+ if monster[local_y][local_x] == '#' and map[y + local_y][x + local_x] != '#':
|
|
|
|
+ return False
|
|
|
|
+ return True
|
|
|
|
+
|
|
|
|
+def paint_monster(map, x, y, monster):
|
|
|
|
+ for local_x in range(0, len(monster[0])):
|
|
|
|
+ for local_y in range(0, len(monster)):
|
|
|
|
+ if monster[local_y][local_x] == '#':
|
|
|
|
+ map[y + local_y][x + local_x] = 'O'
|
|
|
|
+ return map
|
|
|
|
+
|
|
|
|
+def search_map(map):
|
|
|
|
+ map_width = len(map[0])
|
|
|
|
+ map_height = len(map)
|
|
|
|
+ monster = sea_monster
|
|
|
|
+
|
|
|
|
+ found = False
|
|
|
|
+ for x in range(0, map_width - len(monster[0])):
|
|
|
|
+ for y in range(0, map_height - len(monster)):
|
|
|
|
+ if check_monster(map, x, y, monster):
|
|
|
|
+ found = True
|
|
|
|
+ map = paint_monster(map, x, y, monster)
|
|
|
|
+ if found:
|
|
|
|
+ return map
|
|
|
|
+ else:
|
|
|
|
+ return None
|
|
|
|
+
|
|
|
|
+def print_map(map):
|
|
|
|
+ for row in map:
|
|
|
|
+ print(''.join(row))
|
|
|
|
+
|
|
|
|
+for map in all_orientations(map):
|
|
|
|
+ map = search_map(map)
|
|
|
|
+ if map != None:
|
|
|
|
+ print("Answer 2:", len([1 for row in map for c in row if c == '#']))
|