| @@ -0,0 +1,77 @@ | |||
| from functools import lru_cache | |||
| @lru_cache(maxsize=None) | |||
| def find_min_dist(valid, a, b, DX=[1, -1, 1j, -1j]): | |||
| dist = 0 | |||
| state = {a} | |||
| seen = state.copy() | |||
| while state: | |||
| dist += 1 | |||
| state = {x + dx for x in state for dx in DX} & valid - seen | |||
| seen.update(state) | |||
| if b in state: | |||
| return dist | |||
| def evolve(old, pinned): | |||
| valid = frozenset({p for p, c in old if c == '.'}) | |||
| for x, k in old: | |||
| if x not in pinned and k.isalpha(): | |||
| xi = 'ABCD'.index(k) * 2 + 3 | |||
| cost = 10 ** 'ABCD'.index(k) | |||
| # rule: if you're in the hall, you can only end up in your room | |||
| if x.imag == 1: | |||
| ys = {complex(xi, y) for y in slots} | |||
| # rule: if you're in a wrong spot, you can only end up in the hall | |||
| else: | |||
| ys = {complex(x, 1) for x in (1, 2, 4, 6, 8, 10, 11)} | |||
| for y in ys & valid: | |||
| # heuristic: assume only A can waste time with alcove | |||
| if y.real in {1, 2} and k != 'A': | |||
| continue | |||
| # rule: you only get one move | |||
| new_pinned = pinned | |||
| if y.imag > 1: | |||
| new_pinned |= {y} | |||
| if dist := find_min_dist(valid, x, y): | |||
| new = old - {(x, k), (y, '.')} | {(x, '.'), (y, k)} | |||
| options = {scores.get(new), scores[old] + cost * dist} | |||
| scores[new] = min(options - {None}) | |||
| yield new, new_pinned | |||
| text = open(0).read() | |||
| expansion = ''' | |||
| #D#C#B#A# | |||
| #D#B#A#C# | |||
| ''' | |||
| expanded_text = text.replace('#\n ', '#' + expansion + ' ', 1) | |||
| for text in [text, expanded_text]: | |||
| slots = list(range(2, len(text.splitlines()) - 1)) | |||
| init = frozenset( | |||
| (complex(x, y), ch) | |||
| for y, ln in enumerate(text.splitlines()) | |||
| for x, ch in enumerate(ln) | |||
| ) | |||
| goal = { | |||
| (complex(i * 2 + 3, y), ch) | |||
| for i, ch in enumerate('ABCD') | |||
| for y in slots | |||
| } | |||
| scores = {init: 0} | |||
| states = {(init, frozenset())} | |||
| wins = set() | |||
| seen = set() | |||
| i = 0 | |||
| while states: | |||
| i += 1; print(i, len(states)) | |||
| states = {new for old in states for new in evolve(*old)} - seen | |||
| seen |= states | |||
| if wins := {old[0] for old in states if goal < old[0]}: | |||
| print('end', scores[min(wins, key=scores.get)]) | |||
| break | |||