-
Notifications
You must be signed in to change notification settings - Fork 0
/
day19.py
159 lines (119 loc) Β· 5.65 KB
/
day19.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
from collections import deque
from dataclasses import dataclass
from aocd import get_data
from parse import parse
#
# @dataclass(frozen=True)
# class CollectedMaterials:
# Ore: int
# Clay: int
# Obsidian: int
# Geode: int
#
#
# @dataclass(frozen=True)
# class RobotFleet:
# Ore: int
# Clay: int
# Obsidian: int
# Geode: int
@dataclass(frozen=True)
class Robot:
Ore: int
Clay: int
Obsidian: int
@dataclass(frozen=True)
class Blueprint:
Id: int
OreRobot: Robot
ClayRobot: Robot
ObsidianRobot: Robot
GeodeRobot: Robot
def max_ore(self):
return max(self.OreRobot.Ore, self.ClayRobot.Ore, self.ObsidianRobot.Ore, self.GeodeRobot.Ore)
def max_clay(self):
return self.ObsidianRobot.Clay
def max_obsidian(self):
return self.GeodeRobot.Obsidian
def parse_blueprints(input_lines):
blue_prints = []
template = "Blueprint {:d}: Each ore robot costs {:d} ore. Each clay robot costs {:d} ore. Each obsidian robot costs {:d} ore and {:d} clay. Each geode robot costs {:d} ore and {:d} obsidian."
for input_line in input_lines:
parsed = parse(template, input_line).fixed
blue_prints.append(Blueprint(
Id=parsed[0],
OreRobot=Robot(Ore=parsed[1], Clay=0, Obsidian=0),
ClayRobot=Robot(Ore=parsed[2], Clay=0, Obsidian=0),
ObsidianRobot=Robot(Ore=parsed[3], Clay=parsed[4], Obsidian=0),
GeodeRobot=Robot(Ore=parsed[5], Clay=0, Obsidian=parsed[6])
))
return blue_prints
def best_case_scenario(initial_amount, robots, t):
return initial_amount + robots * (t + 1) + t * (t + 1) // 2
def search(start_time, blueprint):
ORE, CLAY, OBS, GEO = range(4)
best = 0
visited = set()
current_search = deque([(start_time, 0, 0, 0, 0, 1, 0, 0, 0, ())])
while current_search:
current_state = current_search.pop()
state = current_state[:-1]
if state in visited:
continue
visited.add(state)
current_time, ore, clay, obsidian, geode, ore_robots, clay_robots, obsidian_robots, geode_robots, did_not_build = current_state
new_ore = ore + ore_robots
new_clay = clay + clay_robots
new_obsidian = obsidian + obsidian_robots
new_geode = geode + geode_robots
current_time -= 1
if current_time == 0:
best = max(best, new_geode)
continue
if best_case_scenario(new_geode, geode_robots, current_time) < best:
continue
if best_case_scenario(new_obsidian, obsidian_robots, current_time) < blueprint.GeodeRobot.Obsidian or best_case_scenario(new_ore, ore_robots, current_time) < blueprint.GeodeRobot.Ore:
best = max(best, new_geode + geode_robots * current_time)
continue
can_build = []
# build geode robot
if obsidian >= blueprint.GeodeRobot.Obsidian and ore >= blueprint.GeodeRobot.Ore and GEO not in did_not_build:
can_build.append(GEO)
current_search.append((current_time, new_ore - blueprint.GeodeRobot.Ore, new_clay, new_obsidian - blueprint.GeodeRobot.Obsidian, new_geode, ore_robots, clay_robots, obsidian_robots, geode_robots + 1, ()))
# build an obsidian robot
if obsidian_robots < blueprint.max_obsidian() and clay >= blueprint.ObsidianRobot.Clay and ore >= blueprint.ObsidianRobot.Ore and OBS not in did_not_build:
can_build.append(OBS)
current_search.append((current_time, new_ore - blueprint.ObsidianRobot.Ore, new_clay - blueprint.ObsidianRobot.Clay, new_obsidian, new_geode, ore_robots, clay_robots, obsidian_robots + 1, geode_robots, ()))
# build a clay robot
if clay_robots < blueprint.max_clay() and ore >= blueprint.ClayRobot.Ore and CLAY not in did_not_build:
can_build.append(CLAY)
current_search.append((current_time, new_ore - blueprint.ClayRobot.Ore, new_clay, new_obsidian, new_geode, ore_robots, clay_robots + 1, obsidian_robots, geode_robots, ()))
# Build an ore robot
if ore_robots < blueprint.max_ore() and ore >= blueprint.OreRobot.Ore and ORE not in did_not_build:
can_build.append(ORE)
current_search.append((current_time, new_ore - blueprint.OreRobot.Ore, new_clay, new_obsidian, new_geode, ore_robots + 1, clay_robots, obsidian_robots, geode_robots, ()))
# Only collect
if (obsidian_robots and obsidian < blueprint.max_obsidian()) or (clay_robots and clay < blueprint.max_clay()) or ore < blueprint.max_ore():
current_search.append((current_time, new_ore, new_clay, new_obsidian, new_geode, ore_robots, clay_robots, obsidian_robots, geode_robots, can_build))
return best
if __name__ == '__main__':
data = [
'Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.',
'Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.'
]
data = get_data(day=19, year=2022).splitlines()
blueprints = parse_blueprints(data)
part1_results = {}
for blueprint in blueprints:
part1_results[blueprint.Id] = search(24, blueprint)
part1_sum = 0
for key, value in part1_results.items():
part1_sum += key * value
print(f'Part 1: {part1_sum}')
part2_results = {}
for blueprint in blueprints[:3]:
part2_results[blueprint.Id] = search(32, blueprint)
part2_multiply = 1
for key, value in part2_results.items():
part2_multiply *= value
print(f'Part 2: {part2_multiply}')