from tools.aoc import AOCDay from typing import Any def getLegalMoves(positions): movers = [c for c in positions if positions[c]] for start in movers: finalPosition = True if start[1] == 0 or start[0] != positions[start] * 2: finalPosition = False else: for y in range(start[1] + 1, 5): if (start[0], y) in positions and positions[(start[0], y)] != positions[start]: finalPosition = False if finalPosition: continue if start[1] > 0 and positions[(start[0], start[1] - 1)]: continue possible_targets = [] target_x = positions[start] * 2 for y in [4, 3, 2, 1]: target = (target_x, y) if target not in positions: continue if positions[target] and positions[target] != positions[start]: break if positions[target] == positions[start]: continue if positions[(target_x, y - 1)]: continue possible_targets.append(target) break if start[1] != 0: for x in 0, 1, 3, 5, 7, 9, 10: if not positions[(x, 0)]: possible_targets.append((x, 0)) for dest in possible_targets: if start == dest or positions[dest]: continue wayClear = True for x in range(min(start[0], dest[0]) + 1, max(start[0], dest[0])): if positions[(x, 0)]: wayClear = False break if not wayClear: continue yield start, dest, start[1] + abs(dest[0] - start[0]) + dest[1] def isFinalPosition(position): for x in 2, 4, 6, 8: for y in 1, 2, 3, 4: if (x, y) in position and position[(x, y)] != x // 2: return False return True def play(position, DP, depth=0): if str(position) in DP: return DP[str(position)] minCost = 1e9 for move in getLegalMoves(position): move_cost = 10 ** (position[move[0]] - 1) * move[2] new_pos = position.copy() new_pos[move[1]] = new_pos[move[0]] new_pos[move[0]] = 0 if not isFinalPosition(new_pos): move_cost += play(new_pos, DP, depth+1) if move_cost < minCost: minCost = move_cost DP[str(position)] = minCost return minCost class Day(AOCDay): inputs = [ [ (12521, "test_input23"), (12530, "input23") ], [ (44169, "test_input23"), (50492, "input23") ] ] def getStartingPosition(self, part2=False): positions = {} for x in range(11): positions[(x, 0)] = 0 my_input = self.getInput()[2:4] if part2: my_input = [my_input[0], " #D#C#B#A#", ' #D#B#A#C#', my_input[1]] for y, l in enumerate(my_input): for x, c in enumerate(l[2:].split("#")): if not c.strip(): continue positions[(x * 2, y + 1)] = ord(c) - 64 return positions def part1(self) -> Any: initPos = self.getStartingPosition() return play(initPos, {}) def part2(self) -> Any: initPos = self.getStartingPosition(part2=True) return play(initPos, {}) if __name__ == '__main__': day = Day(2021, 23) day.run(verbose=True)