Guest User

AoC 2022 - Day 19

a guest
Dec 27th, 2022
1,336
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 5.04 KB | None | 0 0
  1. """
  2. Advent of Code 2022 Day 19
  3. """
  4. import re
  5. import sys
  6.  
  7. from advent_tools import get_daily_input
  8.  
  9. DAY = 19
  10.  
  11. TEST = sys.argv[1] == "test" if len(sys.argv) > 1 else False
  12.  
  13. TEST_DATA = """
  14. 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.
  15. 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.
  16. """
  17.  
  18. if TEST:
  19.     def get_daily_input(_):
  20.         for line in TEST_DATA.strip().split("\n"):
  21.             yield line.strip("\n")
  22.  
  23.  
  24. class Blueprint:
  25.     __slots__ = ("id", "cost", "useful")
  26.  
  27.     def __init__(self, input_string: str) -> None:
  28.         vals = [int(i) for i in re.findall(r"\d+", input_string)]
  29.         self.id = vals[0]
  30.         self.cost = {
  31.             "ore": {"ore": vals[1]},
  32.             "clay": {"ore": vals[2]},
  33.             "obsidian": {"ore": vals[3], "clay": vals[4]},
  34.             "geode": {"ore": vals[5], "obsidian": vals[6]}
  35.         }
  36.         self.useful = {
  37.             "ore": max(self.cost["clay"]["ore"],
  38.                        self.cost["obsidian"]["ore"],
  39.                        self.cost["geode"]["ore"]),
  40.             "clay": self.cost["obsidian"]["clay"],
  41.             "obsidian": self.cost["geode"]["obsidian"],
  42.             "geode": float("inf")
  43.         }
  44.  
  45.  
  46. class State:
  47.     __slots__ = ("robots", "resources", "ignored")
  48.  
  49.     def __init__(self, robots: dict = None, resources: dict = None,
  50.                  ignored: list = None):
  51.         self.robots = robots.copy() if robots else {
  52.             "ore": 1, "clay": 0, "obsidian": 0, "geode": 0
  53.         }
  54.         self.resources = resources.copy() if resources else {
  55.             "ore": 0, "clay": 0, "obsidian": 0, "geode": 0
  56.         }
  57.         self.ignored = ignored.copy() if ignored else []
  58.  
  59.     def copy(self) -> "State":
  60.         return State(self.robots, self.resources, self.ignored)
  61.  
  62.     def __gt__(self, other):
  63.         return self.resources["geode"] > other.resources["geode"]
  64.  
  65.     def __repr__(self):
  66.         return f"{{robots: {self.robots}, resources: {self.resources}}}"
  67.  
  68.  
  69. def evaluate_options(
  70.         blueprint: Blueprint,
  71.         prior_states: list[State],
  72.         timelimit: int = 26
  73. ) -> [tuple[int, list]]:
  74.     time_remaining = timelimit - len(prior_states)
  75.     curr_state = prior_states[-1]
  76.  
  77.     # determine options for what to build in the next state
  78.     options: list[str] = []
  79.     if time_remaining >= 0:
  80.         # look for something affordable and useful and not ignored last time
  81.         for robot, cost in blueprint.cost.items():
  82.             if (curr_state.robots[robot] < blueprint.useful[robot]
  83.                     and all(curr_state.resources[k] >= v for k, v in cost.items())
  84.                     and robot not in curr_state.ignored):
  85.                 options.append(robot)
  86.  
  87.         # geodes before anything else, don't bother with other types at the end
  88.         if "geode" in options:
  89.             options = ["geode"]
  90.         elif time_remaining < 1:
  91.             options = []
  92.         else:
  93.             # cutting off plans that build resources more than 2 phases back
  94.             if ((curr_state.robots["clay"] > 3 or curr_state.robots["obsidian"]
  95.                  or "obsidian" in options) and "ore" in options):
  96.                 options.remove("ore")
  97.             if ((curr_state.robots["obsidian"] > 3 or curr_state.robots["geode"]
  98.                  or "geode" in options) and "clay" in options):
  99.                 options.remove("clay")
  100.  
  101.         # add new resources
  102.         next_state = curr_state.copy()
  103.         for r, n in next_state.robots.items():
  104.             next_state.resources[r] += n
  105.  
  106.         # the 'do nothing' option
  107.         next_state.ignored += options
  108.         results = [evaluate_options(blueprint, prior_states + [next_state], timelimit)]
  109.  
  110.         # the rest of the options
  111.         for opt in options:
  112.             next_state_opt = next_state.copy()
  113.             next_state_opt.ignored = []
  114.             next_state_opt.robots[opt] += 1
  115.             for r, n in blueprint.cost[opt].items():
  116.                 next_state_opt.resources[r] -= n
  117.             results.append(
  118.                 evaluate_options(blueprint, prior_states + [next_state_opt], timelimit)
  119.             )
  120.  
  121.         return max(results)
  122.  
  123.     return prior_states[-1].resources["geode"], prior_states
  124.  
  125.  
  126. def part_1() -> int:
  127.     blueprints = [Blueprint(bp) for bp in get_daily_input(DAY)]
  128.     result = 0
  129.     for bp in blueprints:
  130.         r = evaluate_options(bp, [State()], 24)
  131.         result += r[0] * bp.id
  132.     return result
  133.  
  134.  
  135. def part_2() -> int:
  136.     blueprints = [Blueprint(bp) for bp in get_daily_input(DAY)]
  137.     if len(blueprints) > 3:
  138.         blueprints = blueprints[:3]
  139.     result = 1
  140.     for bp in blueprints:
  141.         r = evaluate_options(bp, [State()], 32)
  142.         result *= r[0]
  143.     return result
  144.  
  145.  
  146. def main():
  147.     print(f"Part 1: {part_1()}")
  148.     print(f"Part 2: {part_2()}")
  149.  
  150.  
  151. if __name__ == "__main__":
  152.     main()
  153.  
Advertisement
Add Comment
Please, Sign In to add comment