132 lines
3.4 KiB
Python
132 lines
3.4 KiB
Python
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(23)
|
|
day.run(verbose=True)
|