diff --git a/day19.py b/day19.py index da02c43..65805e3 100644 --- a/day19.py +++ b/day19.py @@ -1,7 +1,10 @@ from enum import Enum +from heapq import heappush, heappop from tools.aoc import AOCDay from typing import Any +from tools.int_seq import triangular + class Material(Enum): ORE = 1 @@ -17,12 +20,6 @@ class Robot: self.min_viable = min_viable -class Blueprint: - def __init__(self, bp_id: int, robots: dict): - self.bp_id = bp_id - self.robots = robots - - class Inventory: def __init__(self): self.robots = { @@ -43,19 +40,10 @@ class Inventory: for mat, amount in self.robots.items(): self.materials[mat] += amount - def queue_build_robot(self, robot: Robot) -> None: + def build_robot(self, robot: Robot) -> None: for cost_mat, cost_amount in robot.costs.items(): self.materials[cost_mat] -= cost_amount - self.__build_queue.append(robot) - - def build_queued_robots(self) -> None: - for robot in self.__build_queue: - self.robots[robot.produces] += 1 - self.__build_queue = [] - - def build_robot(self, robot: Robot) -> None: - self.queue_build_robot(robot) - self.build_queued_robots() + self.robots[robot.produces] += 1 def copy(self) -> 'Inventory': new_inv = Inventory() @@ -63,10 +51,27 @@ class Inventory: new_inv.materials = self.materials.copy() return new_inv + def __lt__(self, other: 'Inventory') -> bool: + return self.materials[Material.GEODE] < other.materials[Material.GEODE] + + +class Blueprint: + def __init__(self, bp_id: int, robots: dict): + self.bp_id = bp_id + self.robots = robots + self.max_cost = { + Material.ORE: max(robot.costs[Material.ORE] for _, robot in self.robots.items()), + Material.CLAY: self.robots[Material.OBSIDIAN].costs[Material.CLAY], + Material.OBSIDIAN: self.robots[Material.GEODE].costs[Material.OBSIDIAN], + Material.GEODE: 1e9 + } + def pass_minute(bp: Blueprint, inv: Inventory, ignore: list) -> (Inventory, list): could_build = [] for mat in Material: + if inv.robots[mat] >= bp.max_cost[mat]: + continue for robot_mat, robot_cost in bp.robots[mat].costs.items(): if inv.materials[robot_mat] < robot_cost: break @@ -79,31 +84,32 @@ def pass_minute(bp: Blueprint, inv: Inventory, ignore: list) -> (Inventory, list return inv, could_build -def get_quality(bp: Blueprint, inv: Inventory, ignore: list, time_left: int = 24) -> int: - for m in range(time_left): +def get_bfs_quality(bp: Blueprint, max_time: int = 24) -> int: + q = [] + max_geode = 0 + heappush(q, (max_time, Inventory(), [], 0)) + while q: + time_left, inv, ignore, no_build = heappop(q) + inv, could_build = pass_minute(bp, inv, ignore) + time_left -= 1 - if could_build and time_left - m > 1: - max_quality = 0 - build_a_bot = False - for robot in could_build: - if robot.min_viable > time_left - m: - continue - sub_inv = inv.copy() - sub_inv.build_robot(robot) - sub_quality = get_quality(bp, sub_inv, [], time_left - m - 1) - if sub_quality > max_quality: - max_quality = sub_quality - build_a_bot = True + if time_left == 0: + if inv.materials[Material.GEODE] > max_geode: + max_geode = inv.materials[Material.GEODE] + continue - if build_a_bot: - sub_quality = get_quality(bp, inv, could_build, time_left - m - 1) - if sub_quality > max_quality: - max_quality = sub_quality + for robot in could_build: + if robot.min_viable > time_left: + continue + sub_inv = inv.copy() + sub_inv.build_robot(robot) + heappush(q, (time_left, sub_inv, [], 0)) - return max_quality + if no_build < bp.max_cost[Material.ORE]: + heappush(q, (time_left, inv, could_build, no_build + 1)) - return inv.materials[Material.GEODE] + return max_geode class Day(AOCDay): @@ -113,8 +119,8 @@ class Day(AOCDay): (1616, "input19"), ], [ - (56*62, "input19_test"), - (None, "input19"), + #(56*62, "input19_test"), + (8990, "input19"), ] ] @@ -143,18 +149,18 @@ class Day(AOCDay): blueprints = self.get_blueprints() score = 0 for b in blueprints: - quality = get_quality(b, Inventory(), []) - print("BP", b.bp_id, "Q", quality) + quality = get_bfs_quality(b) score += quality * b.bp_id + print("BP", b.bp_id, "Q", quality, "S", score) return score def part2(self) -> Any: blueprints = self.get_blueprints() score = 1 for b in blueprints[:3]: - quality = get_quality(b, Inventory(), [], 32) - print("BP", b.bp_id, "Q", quality) + quality = get_bfs_quality(b, 32) score *= quality + print("BP", b.bp_id, "Q", quality, "S", score) return score