You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

129 line
3.3KB

  1. # flake8: noqa
  2. import re
  3. import collections
  4. import itertools
  5. import sys
  6. import math
  7. from functools import reduce
  8. def render(grid):
  9. return '\n'.join(
  10. '\n'.join(''.join([n[1:-1]
  11. for n in ln])
  12. for ln in zip(*[grid[y, x].splitlines()[1:-1] for x in range(size)]))
  13. for y in range(size)
  14. )
  15. def get_borders(content):
  16. a, *_, b = content.splitlines()
  17. c, *_, d = [''.join(ln) for ln in zip(*content.splitlines())]
  18. return [a, b, c, d, a[::-1], b[::-1], c[::-1], d[::-1]]
  19. def transpose(string):
  20. return '\n'.join(''.join(ln) for ln in zip(*string.splitlines()))
  21. def flipV(string):
  22. return '\n'.join(ln for ln in string.splitlines()[::-1])
  23. def flipH(string):
  24. return '\n'.join(ln[::-1] for ln in string.splitlines())
  25. def rot90(n):
  26. def inner(string):
  27. return flipH(transpose(string))
  28. return inner
  29. def noop(string):
  30. return string
  31. def variants(string):
  32. alts = [noop, flipH], [noop, flipV], [noop, rot90(1), rot90(2), rot90(3)]
  33. for ops in itertools.product(*alts):
  34. yield reduce(lambda s, f: f(s), ops, string)
  35. def search_in(source, target):
  36. source_lns = source.splitlines()
  37. target_lns = target.splitlines()
  38. h, w = len(target_lns), len(target_lns[0])
  39. out = []
  40. for y in range(len(source_lns)):
  41. for x in range(len(source_lns[0])):
  42. window = '\n'.join(ln[x:x + w] for ln in source_lns[y:y + h])
  43. if re.fullmatch(target, window):
  44. out.append((x, y))
  45. return out
  46. text = sys.stdin.read().strip()
  47. tiles = {}
  48. borders = {}
  49. for line in text.split('\n\n'):
  50. title, content = line.split(':\n')
  51. tid = int(title.split()[1])
  52. tiles[tid] = content
  53. borders[tid] = get_borders(content)
  54. size = int(len(tiles) ** 0.5)
  55. adj = collections.defaultdict(set)
  56. for (aid, A), (bid, B) in itertools.combinations(borders.items(), 2):
  57. if set(A) & set(B):
  58. adj[aid].add(bid)
  59. adj[bid].add(aid)
  60. corn = [v for v, ks in adj.items() if len(ks) == 2]
  61. print(math.prod(corn))
  62. gids = [[None for _ in range(size)] for _ in range(size)]
  63. gids[0][0] = corn[0]
  64. gids[1][0], gids[0][1] = adj[gids[0][0]]
  65. gids[1][1], = adj[gids[1][0]] & adj[gids[0][1]] - {gids[0][0]}
  66. for x in range(2, size):
  67. gids[0][x], = adj[gids[0][x - 1]] - {gids[0][x - 2], gids[1][x - 1]}
  68. gids[1][x], = adj[gids[0][x]] & adj[gids[1][x - 1]] - {gids[0][x - 1]}
  69. for y in range(2, size):
  70. gids[y][0], = adj[gids[y - 1][0]] - {gids[y - 2][0], gids[y - 1][1]}
  71. for x in range(1, size):
  72. gids[y][x], = adj[gids[y][x - 1]] & adj[gids[y - 1][x]] - {gids[y - 1][x - 1]}
  73. grid = {(y, x): tiles[gids[y][x]] for x in range(size) for y in range(size)}
  74. def UD(A, B):
  75. return A.splitlines()[-1] == B.splitlines()[0]
  76. def LR(A, B):
  77. return UD(rot90(1)(A), rot90(1)(B))
  78. for y in range(size):
  79. for x in range(size):
  80. if (y, x) == (0, 0):
  81. grid[y, x] = flipV(grid[y, x])
  82. elif y == 0:
  83. grid[y, x] = next(pic for pic in variants(grid[0, x]) if LR(grid[y, x - 1], pic))
  84. else:
  85. grid[y, x] = next(pic for pic in variants(grid[y, x]) if UD(grid[y - 1, x], pic))
  86. monster = '''
  87. ..................#.
  88. #....##....##....###
  89. .#..#..#..#..#..#...
  90. '''[1:-1]
  91. src = render(grid)
  92. found = max([search_in(pic, monster) for pic in variants(src)], key=len)
  93. print(src.count('#') - len(found) * monster.count('#'))