|
|
|
|
|
|
|
|
# flake8: noqa |
|
|
|
|
|
import re |
|
|
|
|
|
import collections |
|
|
import collections |
|
|
|
|
|
import functools |
|
|
import itertools |
|
|
import itertools |
|
|
import sys |
|
|
|
|
|
import math |
|
|
import math |
|
|
from functools import reduce |
|
|
|
|
|
|
|
|
import re |
|
|
|
|
|
import sys |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def render(grid): |
|
|
def render(grid): |
|
|
return '\n'.join( |
|
|
|
|
|
'\n'.join(''.join([n[1:-1] for n in ln]) |
|
|
|
|
|
for ln in zip(*[grid[y, x].splitlines()[1:-1] for x in range(size)])) |
|
|
|
|
|
for y in range(size) |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
lines = [] |
|
|
|
|
|
for y in range(size): |
|
|
|
|
|
groups = [tiles[grid[y, x]].splitlines()[1:-1] for x in range(size)] |
|
|
|
|
|
for lns in zip(*groups): |
|
|
|
|
|
lines.append(''.join(ln[1:-1] for ln in lns)) |
|
|
|
|
|
return '\n'.join(lines) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def get_borders(content): |
|
|
def get_borders(content): |
|
|
a, *_, b = content.splitlines() |
|
|
a, *_, b = content.splitlines() |
|
|
|
|
|
|
|
|
def variants(string): |
|
|
def variants(string): |
|
|
alts = [noop, flipH], [noop, flipV], [noop, transpose] |
|
|
alts = [noop, flipH], [noop, flipV], [noop, transpose] |
|
|
for ops in itertools.product(*alts): |
|
|
for ops in itertools.product(*alts): |
|
|
yield reduce(lambda s, f: f(s), ops, string) |
|
|
|
|
|
|
|
|
yield functools.reduce(lambda s, f: f(s), ops, string) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def UD(A, B): |
|
|
def UD(A, B): |
|
|
|
|
|
|
|
|
return out |
|
|
return out |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reconcile(A, B, condition): |
|
|
|
|
|
tiles[A], tiles[B] = next( |
|
|
|
|
|
(X, Y) |
|
|
|
|
|
for X, Y in itertools.product(variants(tiles[A]), variants(tiles[B])) |
|
|
|
|
|
if condition(X, Y) |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
text = sys.stdin.read().strip() |
|
|
text = sys.stdin.read().strip() |
|
|
tiles = {} |
|
|
tiles = {} |
|
|
borders = {} |
|
|
borders = {} |
|
|
|
|
|
|
|
|
print(math.prod(corners)) |
|
|
print(math.prod(corners)) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gids = {} |
|
|
|
|
|
for y in range(size): |
|
|
|
|
|
for x in range(size): |
|
|
|
|
|
if (y, x) == (0, 0): |
|
|
|
|
|
gids[y, x] = corners[0] |
|
|
|
|
|
|
|
|
|
|
|
gids[1, 0], gids[0, 1] = adj[gids[0, 0]] |
|
|
|
|
|
gids[1, 1], = adj[gids[1, 0]] & adj[gids[0, 1]] - {gids[0, 0]} |
|
|
|
|
|
|
|
|
|
|
|
for x in range(2, size): |
|
|
|
|
|
gids[0, x], = adj[gids[0, x - 1]] - {gids[0, x - 2], gids[1, x - 1]} |
|
|
|
|
|
gids[1, x], = adj[gids[0, x]] & adj[gids[1, x - 1]] - {gids[0, x - 1]} |
|
|
|
|
|
|
|
|
|
|
|
for y in range(2, size): |
|
|
|
|
|
gids[y, 0], = adj[gids[y - 1, 0]] - {gids[y - 2, 0], gids[y - 1, 1]} |
|
|
|
|
|
for x in range(1, size): |
|
|
|
|
|
gids[y, x], = adj[gids[y, x - 1]] & adj[gids[y - 1, x]] - {gids[y - 1, x - 1]} |
|
|
|
|
|
|
|
|
|
|
|
grid = {(y, x): tiles[gids[y, x]] for x in range(size) for y in range(size)} |
|
|
|
|
|
grid[0, 0] = flipV(grid[0, 0]) |
|
|
|
|
|
for y in range(size): |
|
|
|
|
|
for x in range(size): |
|
|
|
|
|
if (x, y) != (0, 0): |
|
|
|
|
|
grid[y, x], = ( |
|
|
|
|
|
pic for pic in variants(grid[y, x]) |
|
|
|
|
|
if ((y - 1, x) not in grid or UD(grid[y - 1, x], pic)) |
|
|
|
|
|
and ((y, x - 1) not in grid or LR(grid[y, x - 1], pic)) |
|
|
|
|
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
A = min(corners) |
|
|
|
|
|
B, C = adj.pop(A) |
|
|
|
|
|
|
|
|
|
|
|
grid = {(y, x): None for x in range(size) for y in range(size)} |
|
|
|
|
|
grid[0, 0] = A |
|
|
|
|
|
grid[0, 1] = B |
|
|
|
|
|
grid[1, 0] = C |
|
|
|
|
|
reconcile(A, B, condition=LR) |
|
|
|
|
|
reconcile(A, C, condition=UD) |
|
|
|
|
|
|
|
|
|
|
|
seen = {A, B, C} |
|
|
|
|
|
for z in range(2, 2 * size - 1): |
|
|
|
|
|
# determine new pieces from intersection between 2 neighbors |
|
|
|
|
|
for y, x in {(y, z - y) for y in range(1, z)}.intersection(grid): |
|
|
|
|
|
U, L = (y - 1, x), (y, x - 1) |
|
|
|
|
|
grid[y, x], = adj[grid[U]] & adj[grid[L]] - seen |
|
|
|
|
|
reconcile(grid[L], grid[y, x], condition=LR) |
|
|
|
|
|
seen.add(grid[y, x]) |
|
|
|
|
|
|
|
|
|
|
|
# determine new piece from only one alternative left |
|
|
|
|
|
for y, x in {(0, z), (z, 0)}.intersection(grid): |
|
|
|
|
|
opts = {(y - 1, x): UD, (y, x - 1): LR} |
|
|
|
|
|
P, fn = next(p for p in opts.items() if p[0] in grid) |
|
|
|
|
|
grid[y, x], = adj[grid[P]] - seen |
|
|
|
|
|
reconcile(grid[P], grid[y, x], condition=fn) |
|
|
|
|
|
seen.add(grid[y, x]) |
|
|
|
|
|
|
|
|
monster = ''' |
|
|
monster = ''' |
|
|
..................#. |
|
|
..................#. |