diff --git a/.gitignore b/.gitignore index 274cede..35cb943 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ c64/tests/ inputs/ .vim/ +.vscode diff --git a/python/day20.py b/python/day20.py new file mode 100644 index 0000000..80b5a1e --- /dev/null +++ b/python/day20.py @@ -0,0 +1,244 @@ +import fileinput +from pprint import pprint +import itertools +from copy import deepcopy + +N = 10 + +# +# Parse the input +# +current_tile = None +tiles = [] +for line in fileinput.input(): + line = line.strip() + if not line: + continue + + if line.startswith("Tile "): + if current_tile: + assert len(current_tile) == N + tiles.append((current_id, current_tile)) + current_id = int(line[5:-1]) + current_tile = [] + else: + assert len(line) == N + current_tile.append(list(line)) + +assert len(current_tile) == N +tiles.append((current_id, current_tile)) + + +def aligns_right(left, right): + return all(left[y][-1] == right[y][0] for y in range(len(left))) + + +def aligns_bottom(top, bottom): + return all(top[-1][x] == bottom[0][x] for x in range(len(top))) + + +def aligns(apos, b): + ax, ay = apos + _, a = kek[(ax, ay)] + if aligns_right(a, b): + return 1, 0 + if aligns_right(b, a): + return -1, 0 + if aligns_bottom(a, b): + return 0, 1 + if aligns_bottom(b, a): + return 0, -1 + return None + + +def rotate(a): + output = deepcopy(a) + for y in range(len(a)): + for x in range(len(a)): + output[-1 - x][y] = a[y][x] + return output + + +def flip(a): + output = [None] * len(a) + for y in range(len(a)): + output[y] = list(reversed(a[y])) + return output + + +def rotate_align(a_pos, b): + """ + Rotates and flips a and checks if it aligns for every possible orientation. + """ + if d := aligns(a_pos, b): + return b, d + bf = flip(b) + if d := aligns(a_pos, bf): + return bf, d + + b = rotate(b) + if d := aligns(a_pos, b): + return b, d + bf = flip(b) + if d := aligns(a_pos, bf): + return bf, d + + b = rotate(b) + if d := aligns(a_pos, b): + return b, d + bf = flip(b) + if d := aligns(a_pos, bf): + return bf, d + + b = rotate(b) + if d := aligns(a_pos, b): + return b, d + bf = flip(b) + if d := aligns(a_pos, bf): + return bf, d + + return None, None + + +positions = {tiles[0][0]: ((0, 0), tiles[0][1])} +kek = {(0, 0): tiles[0]} + +while len(positions) != len(tiles): + for a_id, _ in tiles: + ((a_x, a_y), a_tile) = positions.get(a_id, ((None, None), None)) + if not a_tile: + continue + + for b_id, b_tile in tiles: + if b_id in positions or a_id == b_id: + continue + + transformed, dpos = rotate_align((a_x, a_y), b_tile) + if dpos is not None: + dx, dy = dpos + b_x = a_x + dx + b_y = a_y + dy + positions[b_id] = (b_x, b_y), transformed + kek[(b_x, b_y)] = b_id, transformed + +min_y = min(y for ((_, y), _) in positions.values()) +max_y = max(y for ((_, y), _) in positions.values()) +min_x = min(x for ((x, _), _) in positions.values()) +max_x = max(x for ((x, _), _) in positions.values()) + +tilemap = {pos: (id, tile) for id, (pos, tile) in positions.items()} +bl, _ = tilemap[(min_x, min_y)] +br, _ = tilemap[(max_x, min_y)] +tl, _ = tilemap[(min_x, max_y)] +tr, _ = tilemap[(max_x, max_y)] +print("Part 1:", tl * tr * bl * br) + + +def is_snek(b, x, y): + for (x_, y_) in ( + (0, 0), + (1, 1), + (4, 1), + (5, 0), + (6, 0), + (7, 1), + (10, 1), + (11, 0), + (12, 0), + (13, 1), + (16, 1), + (17, 0), + (18, 0), + (18, -1), + (19, 0), + ): + try: + if b[y_ + y][x_ + x] == ".": + return False + except: + return False + return True + + +def find_sneks(b): + sneks = [] + for (x, y) in itertools.product(range(8 * 12), repeat=2): + if is_snek(b, x, y): + print(x, y, "is snek") + sneks.append((x, y)) + return sneks + + +def rotate_find_sneks(b): + if d := find_sneks(b): + return b, d + bf = flip(b) + if d := find_sneks(bf): + return bf, d + + b = rotate(b) + if d := find_sneks(b): + return b, d + bf = flip(b) + if d := find_sneks(bf): + return bf, d + + b = rotate(b) + if d := find_sneks(b): + return b, d + bf = flip(b) + if d := find_sneks(bf): + return bf, d + + b = rotate(b) + if d := find_sneks(b): + return b, d + bf = flip(b) + if d := find_sneks(bf): + return bf, d + + +def remove_snek(snek): + # :( + (x, y) = snek + for (x_, y_) in ( + (0, 0), + (1, 1), + (4, 1), + (5, 0), + (6, 0), + (7, 1), + (10, 1), + (11, 0), + (12, 0), + (13, 1), + (16, 1), + (17, 0), + (18, 0), + (18, -1), + (19, 0), + ): + big_chungus[y + y_][x + x_] = '.' + + +chungus = [["."] * 8 * (max_x - min_x + 1) for _ in range(8 * (max_y - min_y + 1))] +for (x, y), (id, tile) in tilemap.items(): + x_ = (x - min_x) * 8 + y_ = (y - min_y) * 8 + for i, row in enumerate(tile[1:-1]): + for j, c in enumerate(row[1:-1]): + chungus[i + y_][j + x_] = c + +# for y, row in enumerate(chungus): +# print("".join(row)) + +big_chungus, sneks = rotate_find_sneks(chungus) +for snek in sneks: + remove_snek(snek) + +part2 = 0 +for row in big_chungus: + for c in row: + if c == "#": + part2 += 1 +print("Part 2:", part2)