20_jigsaw.py 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102
  1. #!/usr/bin/env python3
  2. import sys
  3. from functools import lru_cache
  4. def parse(f):
  5. tile = ''
  6. for line in f:
  7. if line.startswith('Tile'):
  8. ident = int(line.split()[1][:-1])
  9. elif line == '\n':
  10. yield ident, tile
  11. tile = ''
  12. else:
  13. tile += line.rstrip()
  14. yield ident, tile
  15. def vflip(tile, sz):
  16. return ''.join(tile[i:i + sz] for i in range((sz - 1) * sz, -1, -sz))
  17. def lrotate(tile, sz):
  18. return ''.join(tile[sz - 1 - i::sz] for i in range(sz))
  19. def fliprot(tile, sz):
  20. for variant in (tile, vflip(tile, sz)):
  21. yield variant
  22. for i in range(3):
  23. variant = lrotate(variant, sz)
  24. yield variant
  25. def make_grid(tiles, sz=10):
  26. tiles = dict(tiles)
  27. placed = {}
  28. unavail = set()
  29. @lru_cache(maxsize=None)
  30. def variants(ident):
  31. return list(fliprot(tiles[ident], sz))
  32. def place(ident, tile, x, y):
  33. tile_t = placed.get((x, y - 1), (None, None))[1]
  34. tile_b = placed.get((x, y + 1), (None, None))[1]
  35. tile_r = placed.get((x + 1, y), (None, None))[1]
  36. tile_l = placed.get((x - 1, y), (None, None))[1]
  37. if tile_t and tile_t[-sz:] != tile[:sz] or \
  38. tile_b and tile_b[:sz] != tile[-sz:] or \
  39. tile_r and tile_r[::sz] != tile[sz - 1::sz] or \
  40. tile_l and tile_l[sz - 1::sz] != tile[::sz]:
  41. return False
  42. placed[(x, y)] = ident, tile
  43. unavail.add(ident)
  44. for nb in ((x, y + 1), (x + 1, y), (x, y - 1), (x - 1, y)):
  45. if nb not in placed:
  46. for ident in tiles:
  47. if ident not in unavail:
  48. for variant in variants(ident):
  49. if place(ident, variant, *nb):
  50. break
  51. return True
  52. start = next(iter(tiles))
  53. place(start, tiles[start], 0, 0)
  54. minx = min(x for x, y in placed)
  55. miny = min(y for x, y in placed)
  56. maxx = max(x for x, y in placed) + 1
  57. maxy = max(y for x, y in placed) + 1
  58. return [[placed[(x, y)] for x in range(minx, maxx)]
  59. for y in range(miny, maxy)]
  60. def stitch(grid, sz=10):
  61. return ''.join(tile[y + 1:y + sz - 1]
  62. for row in grid
  63. for y in range(sz, sz * sz - sz, sz)
  64. for ident, tile in row)
  65. def findpat(tile, pattern, sz):
  66. diffs = [y * sz + x
  67. for y, line in enumerate(pattern)
  68. for x, char in enumerate(line)
  69. if char == '#']
  70. found = set()
  71. for variant in fliprot(tile, sz):
  72. for y in range(sz - len(pattern) + 1):
  73. for x in range(sz - len(pattern[0]) + 1):
  74. base = y * sz + x
  75. if all(variant[base + i] == '#' for i in diffs):
  76. found |= {base + i for i in diffs}
  77. if found:
  78. return variant, found
  79. def roughness(habitat, sz):
  80. monster = (' # ',
  81. '# ## ## ###',
  82. ' # # # # # # ')
  83. variant, monsters = findpat(habitat, monster, sz)
  84. return variant.count('#') - len(monsters)
  85. grid = make_grid(parse(sys.stdin))
  86. print(grid[0][0][0] * grid[0][-1][0] * grid[-1][0][0] * grid[-1][-1][0])
  87. print(roughness(stitch(grid), len(grid) * 8))