1use std::ops::{Add, Sub};
45
46use crate::util::iter::*;
47use crate::util::parse::*;
48
49const ZERO: Mineral = Mineral::from(0, 0, 0, 0);
51const ORE_BOT: Mineral = Mineral::from(1, 0, 0, 0);
52const CLAY_BOT: Mineral = Mineral::from(0, 1, 0, 0);
53const OBSIDIAN_BOT: Mineral = Mineral::from(0, 0, 1, 0);
54const GEODE_BOT: Mineral = Mineral::from(0, 0, 0, 1);
55
56#[derive(Clone, Copy)]
57struct Mineral {
58 ore: u32,
59 clay: u32,
60 obsidian: u32,
61 geode: u32,
62}
63
64impl Mineral {
65 const fn from(ore: u32, clay: u32, obsidian: u32, geode: u32) -> Self {
66 Self { ore, clay, obsidian, geode }
67 }
68
69 fn less_than_equal(self, rhs: Self) -> bool {
71 self.ore <= rhs.ore && self.clay <= rhs.clay && self.obsidian <= rhs.obsidian
72 }
73}
74
75impl Add for Mineral {
77 type Output = Self;
78
79 fn add(self, rhs: Self) -> Self {
80 Self {
81 ore: self.ore + rhs.ore,
82 clay: self.clay + rhs.clay,
83 obsidian: self.obsidian + rhs.obsidian,
84 geode: self.geode + rhs.geode,
85 }
86 }
87}
88
89impl Sub for Mineral {
90 type Output = Self;
91
92 fn sub(self, rhs: Self) -> Self {
93 Self {
94 ore: self.ore - rhs.ore,
95 clay: self.clay - rhs.clay,
96 obsidian: self.obsidian - rhs.obsidian,
97 geode: self.geode - rhs.geode,
98 }
99 }
100}
101
102pub struct Blueprint {
103 id: u32,
104 max_ore: u32,
105 max_clay: u32,
106 max_obsidian: u32,
107 ore_cost: Mineral,
108 clay_cost: Mineral,
109 obsidian_cost: Mineral,
110 geode_cost: Mineral,
111}
112
113impl Blueprint {
114 fn from(chunk: [u32; 7]) -> Self {
115 let [id, ore1, ore2, ore3, clay, ore4, obsidian] = chunk;
116 Self {
117 id,
118 max_ore: ore1.max(ore2).max(ore3).max(ore4),
119 max_clay: clay,
120 max_obsidian: obsidian,
121 ore_cost: Mineral::from(ore1, 0, 0, 0),
122 clay_cost: Mineral::from(ore2, 0, 0, 0),
123 obsidian_cost: Mineral::from(ore3, clay, 0, 0),
124 geode_cost: Mineral::from(ore4, 0, obsidian, 0),
125 }
126 }
127}
128
129pub fn parse(input: &str) -> Vec<Blueprint> {
130 input.iter_unsigned().chunk::<7>().map(Blueprint::from).collect()
131}
132
133pub fn part1(input: &[Blueprint]) -> u32 {
134 input.iter().map(|blueprint| blueprint.id * maximize(blueprint, 24)).sum()
135}
136
137pub fn part2(input: &[Blueprint]) -> u32 {
138 input.iter().take(3).map(|blueprint| maximize(blueprint, 32)).product()
139}
140
141fn maximize(blueprint: &Blueprint, time: u32) -> u32 {
142 let mut result = 0;
143 dfs(blueprint, &mut result, time, ORE_BOT, ZERO);
144 result
145}
146
147fn dfs(blueprint: &Blueprint, result: &mut u32, time: u32, bots: Mineral, resources: Mineral) {
149 *result = (*result).max(resources.geode + bots.geode * time);
151
152 if heuristic(blueprint, *result, time, bots, resources) {
154 if bots.obsidian > 0 && time > 1 {
155 next(blueprint, result, time, bots, resources, GEODE_BOT, blueprint.geode_cost);
156 }
157 if bots.obsidian < blueprint.max_obsidian && bots.clay > 0 && time > 3 {
158 next(blueprint, result, time, bots, resources, OBSIDIAN_BOT, blueprint.obsidian_cost);
159 }
160 if bots.ore < blueprint.max_ore && time > 3 {
161 next(blueprint, result, time, bots, resources, ORE_BOT, blueprint.ore_cost);
162 }
163 if bots.clay < blueprint.max_clay && time > 5 {
164 next(blueprint, result, time, bots, resources, CLAY_BOT, blueprint.clay_cost);
165 }
166 }
167}
168
169#[inline]
176fn heuristic(
177 blueprint: &Blueprint,
178 result: u32,
179 time: u32,
180 mut bots: Mineral,
181 mut resources: Mineral,
182) -> bool {
183 for _ in 0..time {
184 resources.ore = blueprint.max_ore;
186
187 if blueprint.geode_cost.less_than_equal(resources) {
189 resources = resources + bots - blueprint.geode_cost;
190 bots = bots + GEODE_BOT;
191 } else if blueprint.obsidian_cost.less_than_equal(resources) {
192 resources = resources + bots - blueprint.obsidian_cost;
193 bots = bots + OBSIDIAN_BOT;
194 } else {
195 resources = resources + bots;
196 }
197
198 bots = bots + CLAY_BOT;
200 }
201
202 resources.geode > result
203}
204
205#[inline]
208fn next(
209 blueprint: &Blueprint,
210 result: &mut u32,
211 time: u32,
212 bots: Mineral,
213 mut resources: Mineral,
214 new_bot: Mineral,
215 cost: Mineral,
216) {
217 for jump in 1..time {
218 if cost.less_than_equal(resources) {
219 dfs(blueprint, result, time - jump, bots + new_bot, resources + bots - cost);
220 break;
221 }
222 resources = resources + bots;
223 }
224}