20.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. from math import sqrt
  2. lines = []
  3. with open("20.input") as f:
  4. for line in f.readlines():
  5. lines.append(line.strip())
  6. def parse(lines):
  7. tiles = []
  8. fragments = []
  9. id = None
  10. for line in lines:
  11. if line.startswith("Tile"):
  12. id = int(line[5:-1])
  13. elif line == "":
  14. if fragments == []:
  15. continue
  16. tiles.append((id, fragments))
  17. fragments = []
  18. else:
  19. fragments.append(list(line))
  20. return tiles
  21. def column(fragments, i):
  22. return [row[i] for row in fragments]
  23. def rotate_90(fragments):
  24. return [reverse_of(column(fragments, i)) for i in range(0, len(fragments))]
  25. def all_rotations(grid):
  26. rotations = []
  27. for i in range(0, 4):
  28. grid = rotate_90(grid)
  29. rotations.append(grid)
  30. return rotations
  31. def all_orientations(grid):
  32. return all_rotations(grid) + all_rotations(reverse_of(grid))
  33. def all_sides(fragments):
  34. sides = [fragments[0], fragments[-1], column(fragments, 0), column(fragments, -1)]
  35. sides += [reverse_of(side) for side in sides]
  36. return sides
  37. def reverse_of(l):
  38. l2 = list(l)
  39. l2.reverse()
  40. return l2
  41. def valid_orientations(tile, left, right, top, bottom):
  42. result = []
  43. for orientation in all_orientations(tile[1]):
  44. if left != None and column(orientation, 0) not in left:
  45. continue
  46. elif right != None and column(orientation, -1) not in right:
  47. continue
  48. elif top != None and orientation[0] not in top:
  49. continue
  50. elif bottom != None and orientation[-1] not in bottom:
  51. continue
  52. else:
  53. result.append(orientation)
  54. return result
  55. def possible_orders(prefix, width, tiles, uniq_sides):
  56. index = len(prefix)
  57. if index == width * width:
  58. return [prefix]
  59. left = None
  60. right = None
  61. top = None
  62. bottom = None
  63. result = []
  64. if index >= width:
  65. # Must match above tile
  66. top = [prefix[index - width][-1]]
  67. if index % width != 0:
  68. # Must match left tile
  69. left = [column(prefix[-1], -1)]
  70. if index in [0, width - 1, width * (width - 1), width * width - 1]:
  71. key = "corner"
  72. elif index < width or index > width * (width - 1) or index % width in [0, width - 1]:
  73. key = "edge"
  74. else:
  75. key = "center"
  76. for tile in tiles[key]:
  77. if index % width == 0:
  78. left = uniq_sides[tile[0]]
  79. if int(index / width) == 0:
  80. top = uniq_sides[tile[0]]
  81. if index % width == width - 1:
  82. right = uniq_sides[tile[0]]
  83. for valid in valid_orientations(tile, left, right, top, bottom):
  84. tiles_next = dict(tiles)
  85. tiles_next[key] = [t for t in tiles[key] if t != tile]
  86. result += possible_orders(prefix + [valid], width, tiles_next, uniq_sides)
  87. return result
  88. tiles = parse(lines)
  89. sides = [(side, tile[0]) for tile in tiles for side in all_sides(tile[1])]
  90. uniq_sides = {}
  91. counts = {}
  92. for (side, tile_id) in sides:
  93. same_side = len([1 for (s, id) in sides if s == side])
  94. if same_side == 1:
  95. count = counts.get(tile_id, 0)
  96. count += 1
  97. counts[tile_id] = count
  98. uniq = uniq_sides.get(tile_id, [])
  99. uniq.append(side)
  100. uniq_sides[tile_id] = uniq
  101. corners = []
  102. edges = []
  103. prod = 1
  104. for (k, v) in counts.items():
  105. if v == 4:
  106. prod *= k
  107. corners += [tile for tile in tiles if tile[0] == k]
  108. elif v == 2:
  109. edges += [tile for tile in tiles if tile[0] == k]
  110. print("Answer 1:", prod)
  111. center = [tile for tile in tiles if tile not in corners and tile not in edges]
  112. width = int(sqrt(len(tiles)))
  113. first_corner = corners.pop()
  114. valid_corner = valid_orientations(first_corner, uniq_sides[first_corner[0]], None, uniq_sides[first_corner[0]], None)[0]
  115. orders = possible_orders([valid_corner], width, {'center': center, 'edge': edges, 'corner': corners}, uniq_sides)
  116. assert len(orders) == 1
  117. def strip_borders(frags):
  118. return [row[1:-1] for row in frags[1:-1]]
  119. assert strip_borders([[1, 2, 3], [4,5,6], [7,8,9]]) == [[5]]
  120. map = []
  121. order = orders[0]
  122. while len(order) > 0:
  123. row = [strip_borders(frags) for frags in order[0:width]]
  124. for i in range(0, len(row[0])):
  125. map.append(list(''.join([''.join(frags[i]) for frags in row])))
  126. del order[0:width]
  127. sea_monster = [" # ",
  128. "# ## ## ###",
  129. " # # # # # # "]
  130. sea_monster = [list(row) for row in sea_monster]
  131. def check_monster(map, x, y, monster):
  132. for local_x in range(0, len(monster[0])):
  133. for local_y in range(0, len(monster)):
  134. if monster[local_y][local_x] == '#' and map[y + local_y][x + local_x] != '#':
  135. return False
  136. return True
  137. def paint_monster(map, x, y, monster):
  138. for local_x in range(0, len(monster[0])):
  139. for local_y in range(0, len(monster)):
  140. if monster[local_y][local_x] == '#':
  141. map[y + local_y][x + local_x] = 'O'
  142. return map
  143. def search_map(map):
  144. map_width = len(map[0])
  145. map_height = len(map)
  146. monster = sea_monster
  147. found = False
  148. for x in range(0, map_width - len(monster[0])):
  149. for y in range(0, map_height - len(monster)):
  150. if check_monster(map, x, y, monster):
  151. found = True
  152. map = paint_monster(map, x, y, monster)
  153. if found:
  154. return map
  155. else:
  156. return None
  157. def print_map(map):
  158. for row in map:
  159. print(''.join(row))
  160. for map in all_orientations(map):
  161. map = search_map(map)
  162. if map != None:
  163. print("Answer 2:", len([1 for row in map for c in row if c == '#']))