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