123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- 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 == '#']))
|