221 lines
7.7 KiB
Python
221 lines
7.7 KiB
Python
import aoclib
|
|
import math
|
|
import re
|
|
|
|
DAY = 20
|
|
TEST_SOLUTION_PART1 = 20899048083289
|
|
TEST_SOLUTION_PART2 = 273
|
|
|
|
|
|
class Tile:
|
|
def __init__(self, raw_input):
|
|
self.image_id = int(raw_input[0][5:-1])
|
|
self.image = raw_input[1:]
|
|
|
|
def getPossibleBorderList(self):
|
|
return [
|
|
self.image[0],
|
|
"".join([x[0] for x in self.image]),
|
|
"".join([x[-1] for x in self.image]),
|
|
self.image[-1],
|
|
"".join(reversed(self.image[0])),
|
|
"".join(reversed([x[0] for x in self.image])),
|
|
"".join(reversed([x[-1] for x in self.image])),
|
|
"".join(reversed(self.image[-1])),
|
|
]
|
|
|
|
def getBorderlessImage(self):
|
|
return ["".join(x for x in y[1:-1]) for y in self.image[1:-1]]
|
|
|
|
def getCurrentBorders(self):
|
|
# top right bottom left
|
|
return [
|
|
self.image[0],
|
|
"".join([x[-1] for x in self.image]),
|
|
self.image[-1],
|
|
"".join([x[0] for x in self.image])
|
|
]
|
|
|
|
def rotateRight(self):
|
|
self.image = rotateRight(self.image)
|
|
|
|
def rotateLeft(self):
|
|
self.image = rotateLeft(self.image)
|
|
|
|
def flipHorizontally(self):
|
|
self.image = flipHorizontally(self.image)
|
|
|
|
def flipVertically(self):
|
|
self.image = flipVertically(self.image)
|
|
|
|
def printImage(self):
|
|
for x in self.image:
|
|
print(x)
|
|
|
|
|
|
def rotateRight(image):
|
|
return ["".join([x[line_no] for x in reversed(image)]) for line_no in range(len(image))]
|
|
|
|
|
|
def rotateLeft(image):
|
|
return ["".join([x[line_no] for x in image]) for line_no in reversed(range(len(image)))]
|
|
|
|
|
|
def flipHorizontally(image):
|
|
return list(reversed(image))
|
|
|
|
|
|
def flipVertically(image):
|
|
return ["".join(x for x in reversed(y)) for y in image]
|
|
|
|
|
|
def getAllPossibleBorders(tile_dict):
|
|
all_possible_borders = []
|
|
for tile_id, tile in tile_dict.items():
|
|
all_possible_borders.extend(tile.getPossibleBorderList())
|
|
|
|
return all_possible_borders
|
|
|
|
|
|
def getCornerPieces(tile_dict, only_get_first_piece=False):
|
|
corner_pieces = []
|
|
all_borders = getAllPossibleBorders(tile_dict)
|
|
for tile_id, tile in tile_dict.items():
|
|
single_borders = 0
|
|
for border in tile.getPossibleBorderList():
|
|
single_borders += all_borders.count(border) - 1
|
|
|
|
if single_borders == 4: # every border shows up twice (1x straight, 1x reversed)
|
|
if only_get_first_piece:
|
|
return tile_id
|
|
corner_pieces.append(tile_id)
|
|
if len(corner_pieces) == 4:
|
|
break
|
|
|
|
return corner_pieces
|
|
|
|
|
|
def part1(test_mode=False):
|
|
my_input = aoclib.getMultiLineInputAsArray(day=DAY, test=test_mode)
|
|
tile_dict = {}
|
|
for tile_input in my_input:
|
|
tile = Tile(tile_input)
|
|
tile_dict[tile.image_id] = tile
|
|
|
|
return math.prod(getCornerPieces(tile_dict))
|
|
|
|
|
|
def part2(test_mode=False):
|
|
my_input = aoclib.getMultiLineInputAsArray(day=DAY, test=test_mode)
|
|
tile_dict = {}
|
|
for tile_input in my_input:
|
|
tile = Tile(tile_input)
|
|
tile_dict[tile.image_id] = tile
|
|
|
|
borderlen = int(math.sqrt(len(tile_dict)))
|
|
all_pieces = list(tile_dict.keys())
|
|
corner_piece = getCornerPieces(tile_dict, only_get_first_piece=True)
|
|
all_pieces.remove(corner_piece)
|
|
|
|
# first - find correct orientation of corner piece
|
|
possible_borders = getAllPossibleBorders(tile_dict)
|
|
transformations = [
|
|
tile_dict[corner_piece].flipHorizontally,
|
|
tile_dict[corner_piece].flipVertically,
|
|
tile_dict[corner_piece].flipHorizontally,
|
|
tile_dict[corner_piece].rotateRight,
|
|
tile_dict[corner_piece].flipHorizontally,
|
|
tile_dict[corner_piece].flipVertically,
|
|
tile_dict[corner_piece].flipHorizontally,
|
|
]
|
|
transformation_counter = 0
|
|
found = False
|
|
while not found:
|
|
if possible_borders.count(tile_dict[corner_piece].getCurrentBorders()[0]) == 1 \
|
|
and possible_borders.count(tile_dict[corner_piece].getCurrentBorders()[3]) == 1:
|
|
found = True
|
|
else:
|
|
transformations[transformation_counter]()
|
|
transformation_counter += 1
|
|
|
|
assert found is True
|
|
|
|
full_image = []
|
|
for y in range(borderlen):
|
|
full_image.append([])
|
|
for x in range(borderlen):
|
|
if x == 0 and y == 0:
|
|
full_image[y].append(corner_piece)
|
|
continue
|
|
|
|
# find matching piece with correct orientation for the piece to the left (or the top if y > 0 and x == 0)
|
|
if x == 0: # check against bottom border of upper piece
|
|
check_border = tile_dict[full_image[y - 1][x]].getCurrentBorders()[2]
|
|
else:
|
|
check_border = tile_dict[full_image[y][x - 1]].getCurrentBorders()[1]
|
|
|
|
for possible_tile in all_pieces:
|
|
if check_border in tile_dict[possible_tile].getPossibleBorderList():
|
|
# found my piece ... now orientate it correctly
|
|
transformations = [
|
|
tile_dict[possible_tile].flipHorizontally,
|
|
tile_dict[possible_tile].flipVertically,
|
|
tile_dict[possible_tile].flipHorizontally,
|
|
tile_dict[possible_tile].rotateRight,
|
|
tile_dict[possible_tile].flipHorizontally,
|
|
tile_dict[possible_tile].flipVertically,
|
|
tile_dict[possible_tile].flipHorizontally,
|
|
]
|
|
transformation_counter = 0
|
|
oriented = False
|
|
while not oriented:
|
|
if (x == 0 and tile_dict[possible_tile].getCurrentBorders()[0] == check_border) \
|
|
or (x != 0 and tile_dict[possible_tile].getCurrentBorders()[3] == check_border):
|
|
oriented = True
|
|
full_image[y].append(possible_tile)
|
|
all_pieces.remove(possible_tile)
|
|
else:
|
|
transformations[transformation_counter]()
|
|
transformation_counter += 1
|
|
|
|
# now that we have the full picture, assemble the borderless full image
|
|
borderless_image = []
|
|
full_hash_count = 0
|
|
for y in full_image:
|
|
for image_line in range(8):
|
|
borderless_image.append("".join([tile_dict[x].getBorderlessImage()[image_line] for x in y]))
|
|
full_hash_count += borderless_image[-1].count('#')
|
|
|
|
# and finally scan for our sea monster
|
|
# seamonster_line_1 = re.compile(r"..................#.")
|
|
seamonster_line_2 = re.compile(r"#....##....##....###")
|
|
seamonster_line_3 = re.compile(r".#..#..#..#..#..#...")
|
|
seamonster_hashcount = 15 # amount of '#' in the sea monster
|
|
seamonster_count = 0
|
|
for _ in range(2):
|
|
for _ in range(2):
|
|
for _ in range(4):
|
|
for x in range(len(borderless_image) - 2):
|
|
if s_2 := re.split(seamonster_line_2, borderless_image[x+1]):
|
|
if s_3 := re.split(seamonster_line_3, borderless_image[x+2]):
|
|
# yes, this is somewhat optimistic, but it results in the correct answer
|
|
if len(s_2) > 1 and len(s_2) == len(s_3):
|
|
seamonster_count += len(s_2) - 1
|
|
|
|
if seamonster_count == 0:
|
|
borderless_image = rotateRight(borderless_image)
|
|
else:
|
|
break
|
|
|
|
if seamonster_count == 0:
|
|
borderless_image = flipHorizontally(borderless_image)
|
|
else:
|
|
break
|
|
|
|
if seamonster_count == 0:
|
|
borderless_image = flipVertically(borderless_image)
|
|
else:
|
|
break
|
|
|
|
return full_hash_count - seamonster_count * seamonster_hashcount
|