1 Setup
1.1 Libraries
library(httr)
library(xml2)
library(magrittr)
library(dplyr)
library(purrr)
library(stringr)
1.2 Retrieve Data from AoC
session_cookie <- set_cookies(session = keyring::key_get("AoC-GitHub-Cookie"))
base_url <- paste0("https://adventofcode.com/", params$year, "/day/", params$task_nr)
puzzle <- GET(base_url,
session_cookie) %>%
content(encoding = "UTF-8") %>%
xml_find_all("///article") %>%
lapply(as.character)
parse_puzzle_data <- function(text_block = readClipboard()) {
if (length(text_block) == 1L) {
text_block <- text_block %>%
str_split("\n") %>%
extract2(1L) %>%
keep(nzchar)
}
text_block %>%
str_extract_all("\\d+") %>%
do.call(rbind, .) %>%
set_colnames(c("id", "ore_cost_ore", "clay_cost_ore", "obsidian_cost_ore",
"obsidian_cost_clay", "geode_cost_ore", "geode_cost_obsidian")) %>%
as_tibble() %>%
mutate(across(everything(), as.integer)) %>%
mutate(costs = list(
list(ore = c(ore = ore_cost_ore, clay = 0L, obsidian = 0L),
clay = c(ore = clay_cost_ore, clay = 0L, obsidian = 0L),
obsidian = c(ore = obsidian_cost_ore, clay = obsidian_cost_clay,
obsidian = 0L),
geode = c(ore = geode_cost_ore, clay = 0L,
obsidian = geode_cost_obsidian))
),
.by = id) %>%
pull(costs)
}
puzzle_data <- local({
GET(paste0(base_url, "/input"),
session_cookie) %>%
content(encoding = "UTF-8") %>%
parse_puzzle_data()
})
2 Puzzle Day 19
2.1 Part 1
2.1.1 Description
— Day 19: Not Enough Minerals —
Your scans show that the lava did indeed form obsidian!
The wind has changed direction enough to stop sending lava droplets toward you, so you and the elephants exit the cave. As you do, you notice a collection of geodes around the pond. Perhaps you could use the obsidian to create some geode-cracking robots and break them open?
To collect the obsidian from the bottom of the pond, you’ll need waterproof obsidian-collecting robots. Fortunately, there is an abundant amount of clay nearby that you can use to make them waterproof.
In order to harvest the clay, you’ll need special-purpose clay-collecting robots. To make any type of robot, you’ll need ore, which is also plentiful but in the opposite direction from the clay.
Collecting ore requires ore-collecting robots with big drills. Fortunately, you have exactly one ore-collecting robot in your pack that you can use to kickstart the whole operation.
Each robot can collect 1 of its resource type per minute. It also takes one minute for the robot factory (also conveniently from your pack) to construct any type of robot, although it consumes the necessary resources available when construction begins.
The robot factory has many blueprints (your puzzle input) you can choose from, but once you’ve configured it with a blueprint, you can’t change it. You’ll need to work out which blueprint is best.
For example:
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.
(Blueprints have been line-wrapped here for legibility. The robot factory’s actual assortment of blueprints are provided one blueprint per line.)
The elephants are starting to look hungry, so you shouldn’t take too long; you need to figure out which blueprint would maximize the number of opened geodes after 24 minutes by figuring out which robots to build and when to build them.
Using blueprint 1 in the example above, the largest number of geodes you could open in 24 minutes is 9. One way to achieve that is:
== Minute 1 ==
1 ore-collecting robot collects 1 ore; you now have 1 ore.
== Minute 2 ==
1 ore-collecting robot collects 1 ore; you now have 2 ore.
== Minute 3 ==
Spend 2 ore to start building a clay-collecting robot.
1 ore-collecting robot collects 1 ore; you now have 1 ore.
The new clay-collecting robot is ready; you now have 1 of them.
== Minute 4 ==
1 ore-collecting robot collects 1 ore; you now have 2 ore.
1 clay-collecting robot collects 1 clay; you now have 1 clay.
== Minute 5 ==
Spend 2 ore to start building a clay-collecting robot.
1 ore-collecting robot collects 1 ore; you now have 1 ore.
1 clay-collecting robot collects 1 clay; you now have 2 clay.
The new clay-collecting robot is ready; you now have 2 of them.
== Minute 6 ==
1 ore-collecting robot collects 1 ore; you now have 2 ore.
2 clay-collecting robots collect 2 clay; you now have 4 clay.
== Minute 7 ==
Spend 2 ore to start building a clay-collecting robot.
1 ore-collecting robot collects 1 ore; you now have 1 ore.
2 clay-collecting robots collect 2 clay; you now have 6 clay.
The new clay-collecting robot is ready; you now have 3 of them.
== Minute 8 ==
1 ore-collecting robot collects 1 ore; you now have 2 ore.
3 clay-collecting robots collect 3 clay; you now have 9 clay.
== Minute 9 ==
1 ore-collecting robot collects 1 ore; you now have 3 ore.
3 clay-collecting robots collect 3 clay; you now have 12 clay.
== Minute 10 ==
1 ore-collecting robot collects 1 ore; you now have 4 ore.
3 clay-collecting robots collect 3 clay; you now have 15 clay.
== Minute 11 ==
Spend 3 ore and 14 clay to start building an obsidian-collecting robot.
1 ore-collecting robot collects 1 ore; you now have 2 ore.
3 clay-collecting robots collect 3 clay; you now have 4 clay.
The new obsidian-collecting robot is ready; you now have 1 of them.
== Minute 12 ==
Spend 2 ore to start building a clay-collecting robot.
1 ore-collecting robot collects 1 ore; you now have 1 ore.
3 clay-collecting robots collect 3 clay; you now have 7 clay.
1 obsidian-collecting robot collects 1 obsidian; you now have 1 obsidian.
The new clay-collecting robot is ready; you now have 4 of them.
== Minute 13 ==
1 ore-collecting robot collects 1 ore; you now have 2 ore.
4 clay-collecting robots collect 4 clay; you now have 11 clay.
1 obsidian-collecting robot collects 1 obsidian; you now have 2 obsidian.
== Minute 14 ==
1 ore-collecting robot collects 1 ore; you now have 3 ore.
4 clay-collecting robots collect 4 clay; you now have 15 clay.
1 obsidian-collecting robot collects 1 obsidian; you now have 3 obsidian.
== Minute 15 ==
Spend 3 ore and 14 clay to start building an obsidian-collecting robot.
1 ore-collecting robot collects 1 ore; you now have 1 ore.
4 clay-collecting robots collect 4 clay; you now have 5 clay.
1 obsidian-collecting robot collects 1 obsidian; you now have 4 obsidian.
The new obsidian-collecting robot is ready; you now have 2 of them.
== Minute 16 ==
1 ore-collecting robot collects 1 ore; you now have 2 ore.
4 clay-collecting robots collect 4 clay; you now have 9 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 6 obsidian.
== Minute 17 ==
1 ore-collecting robot collects 1 ore; you now have 3 ore.
4 clay-collecting robots collect 4 clay; you now have 13 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 8 obsidian.
== Minute 18 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
1 ore-collecting robot collects 1 ore; you now have 2 ore.
4 clay-collecting robots collect 4 clay; you now have 17 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 3 obsidian.
The new geode-cracking robot is ready; you now have 1 of them.
== Minute 19 ==
1 ore-collecting robot collects 1 ore; you now have 3 ore.
4 clay-collecting robots collect 4 clay; you now have 21 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 5 obsidian.
1 geode-cracking robot cracks 1 geode; you now have 1 open geode.
== Minute 20 ==
1 ore-collecting robot collects 1 ore; you now have 4 ore.
4 clay-collecting robots collect 4 clay; you now have 25 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 7 obsidian.
1 geode-cracking robot cracks 1 geode; you now have 2 open geodes.
== Minute 21 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
1 ore-collecting robot collects 1 ore; you now have 3 ore.
4 clay-collecting robots collect 4 clay; you now have 29 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 2 obsidian.
1 geode-cracking robot cracks 1 geode; you now have 3 open geodes.
The new geode-cracking robot is ready; you now have 2 of them.
== Minute 22 ==
1 ore-collecting robot collects 1 ore; you now have 4 ore.
4 clay-collecting robots collect 4 clay; you now have 33 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 4 obsidian.
2 geode-cracking robots crack 2 geodes; you now have 5 open geodes.
== Minute 23 ==
1 ore-collecting robot collects 1 ore; you now have 5 ore.
4 clay-collecting robots collect 4 clay; you now have 37 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 6 obsidian.
2 geode-cracking robots crack 2 geodes; you now have 7 open geodes.
== Minute 24 ==
1 ore-collecting robot collects 1 ore; you now have 6 ore.
4 clay-collecting robots collect 4 clay; you now have 41 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 8 obsidian.
2 geode-cracking robots crack 2 geodes; you now have 9 open geodes.
However, by using blueprint 2 in the example above, you could do even better: the largest number of geodes you could open in 24 minutes is 12.
Determine the quality level of each blueprint by multiplying that blueprint’s ID number with the largest number of geodes that can be opened in 24 minutes using that blueprint. In this example, the first blueprint has ID 1 and can open 9 geodes, so its quality level is 9. The second blueprint has ID 2 and can open 12 geodes, so its quality level is 24. Finally, if you add up the quality levels of all of the blueprints in the list, you get 33.
Determine the quality level of each blueprint using the largest number of geodes it could produce in 24 minutes. What do you get if you add up the quality level of all of the blueprints in your list?
2.1.2 Solution
We solve this puzzle by using a DFS. However, since the search space will grow exponentially we have to use some smart pruning ideas:
- An upper bound for the number of geodes can be found by theoretically building a geode
robot for each of the remaining rounds. The we would produce the number of remaining
rounds times the number of geode robots plus
round * (round - 1) / 2as we would produce one robot each round, that is1 + 2 + ... + roundnew geodes, for which we can use the formula Gauss already found. - We use Memoization to remember seen states. A state for which we have more of resources than maximum cost times the rounds is useless (no need to produce more resources than we can eventually spend), thus we regard states equivalent if any reosurce is larger than the amount needed.
- If we can produce a geode robot we do it right away as it will always be better.
- There is no need to build any robot other than geode cracking if we have already as many as the highest expenses are per mineral as this would lead to a unnecessary stock of minerals.
We built first an R algorithm, which is however too slow (it can be found in the
appendix). To speed up we used the following C++ implementation for a massive speed
gain:
#ifndef STANDALONE
#include <Rcpp.h>
using namespace Rcpp;
#else
#include <iostream>
#endif
#include <array>
#include <cstdint>
#include <unordered_map>
struct State {
int round;
std::array<int, 4> robots;
std::array<int, 4> resources;
};
namespace keying {
constexpr unsigned B_robots = 5; // robots[i] <= 31 -> 5 Bits
constexpr unsigned B_resources = 9; // resources[i] <= 496 -> 9 Bits (0..511)
constexpr unsigned B_rounds = 6;
inline uint64_t maskN(unsigned bits) {
return (bits == 64) ? ~0ull : ((1ull << bits) - 1);
}
inline uint64_t encode64(const State& st) {
uint64_t key = 0;
unsigned pos = 0;
auto push = [&](uint64_t v, unsigned bits) {
key |= (v & maskN(bits)) << pos;
pos += bits;
};
for (int i = 0; i < 4; ++i) {
push(static_cast<uint64_t>(st.robots[i]), B_robots);
push(static_cast<uint64_t>(st.resources[i]), B_resources);
}
push(static_cast<uint64_t>(st.round), B_rounds);
return key;
}
inline State decode64(uint64_t key) {
State st {};
unsigned pos = 0;
auto pop = [&](unsigned bits) -> uint64_t {
uint64_t v = (key >> pos) & maskN(bits);
pos += bits;
return v;
};
for (int i = 0; i < 4; ++i) {
st.robots[i] = static_cast<int>(pop(B_robots));
st.resources[i] = static_cast<int>(pop(B_resources));
}
st.round = static_cast<int>(pop(B_rounds));
return st;
}
} // namespace keying
class Blueprint {
private:
int nr_rounds_;
std::array<std::array<int, 3>, 4> costs_;
std::array<int, 3> max_costs_;
int solve(int, std::array<int, 4>, std::array<int, 4>, int);
int upper_bound(int, std::array<int, 4>, std::array<int, 4>);
std::array<int, 4> cap_resources(int, std::array<int, 4>);
std::unordered_map<uint64_t, int> memo_;
public:
Blueprint(int, const std::array<std::array<int, 3>, 4>&);
int solve();
};
Blueprint::Blueprint(int nr_rounds, const std::array<std::array<int, 3>, 4>& costs)
: nr_rounds_(nr_rounds)
, costs_(costs) {
for (int i = 0; i < 3; ++i) {
int max_cost = 0;
for (int j = 0; j < 4; ++j) {
max_cost = std::max(max_cost, costs_[j][i]);
}
max_costs_[i] = max_cost;
}
}
int Blueprint::solve(int round,
std::array<int, 4> robots,
std::array<int, 4> resources,
int best_so_far) {
if (round == 0) {
return std::max(best_so_far, resources[3]);
}
if (upper_bound(round, robots, resources) <= best_so_far) {
return best_so_far;
}
State st {round, robots, cap_resources(round, resources)};
uint64_t key = keying::encode64(st);
auto it = memo_.find(key);
if (it != memo_.end()) {
return std::max(best_so_far, it->second);
}
int best_local = 0;
std::array<int, 4> produced = resources;
for (int i = 0; i < 4; ++i) {
produced[i] += robots[i];
}
for (int robot_type = 3; robot_type >= 0; --robot_type) {
if (robot_type != 3 && robots[robot_type] >= max_costs_[robot_type]) {
// skip building more of this robots as we produce already the maximum needed
continue;
}
bool can_build = true;
for (int resource_type = 0; resource_type < 3; ++resource_type) {
if (costs_[robot_type][resource_type] > resources[resource_type]) {
can_build = false;
break;
}
}
if (can_build) {
std::array<int, 4> new_resources = produced;
for (int resource_type = 0; resource_type < 3; ++resource_type) {
new_resources[resource_type] -= costs_[robot_type][resource_type];
}
std::array<int, 4> new_robots = robots;
new_robots[robot_type] += 1;
if (robot_type == 3) {
// always build geode robot if possible
best_local = std::max(best_local, solve(round - 1, new_robots, new_resources, best_so_far));
memo_[key] = best_local;
return best_local;
}
best_local = std::max(best_local, solve(round - 1, new_robots, new_resources, best_so_far));
}
}
// produce nothing
best_local = std::max(best_local, solve(round - 1, robots, produced, best_so_far));
memo_[key] = best_local;
return best_local;
}
int Blueprint::upper_bound(int round, std::array<int, 4> robots, std::array<int, 4> resources) {
return resources[3] + robots[3] * round + (round * (round - 1)) / 2;
}
std::array<int, 4> Blueprint::cap_resources(int round, std::array<int, 4> resources) {
std::array<int, 4> capped;
for (int i = 0; i < 3; ++i) {
capped[i] = std::min(resources[i], max_costs_[i] * round);
}
capped[3] = resources[3];
return capped;
}
int Blueprint::solve() {
int res = solve(nr_rounds_, {1, 0, 0, 0}, {0, 0, 0, 0}, 0);
return res;
}
#ifndef STANDALONE
// [[Rcpp::export]]
int solve_blueprint(int nr_rounds, const List& blueprint) {
std::array<std::array<int, 3>, 4> costs;
for (int i = 0; i < blueprint.size(); ++i) {
IntegerVector cost_vec = blueprint[i];
for (int j = 0; j < 3; ++j) {
costs[i][j] = cost_vec[j];
}
}
Blueprint bp(nr_rounds, costs);
return bp.solve();
}
#else
int main() {
Blueprint bp(32, {{{2, 0, 0}, {3, 0, 0}, {3, 8, 0}, {3, 0, 12}}});
int res = bp.solve();
std::cout << "# geodes produced: " << res << std::endl;
return 0;
}
#endif
sum_quality_levels <- function(bps) {
imap_int(bps, ~ .y * solve_blueprint(24L, .x)) %>%
sum()
}
sum_quality_levels(puzzle_data)
## [1] 1150
2.2 Part 2
2.2.1 Description
— Part Two —
While you were choosing the best blueprint, the elephants found some food on their own, so you’re not in as much of a hurry; you figure you probably have 32 minutes before the wind changes direction again and you’ll need to get out of range of the erupting volcano.
Unfortunately, one of the elephants ate most of your blueprint list! Now, only the first three blueprints in your list are intact.
In 32 minutes, the largest number of geodes blueprint 1 (from the example above) can open is 56. One way to achieve that is:
== Minute 1 ==
1 ore-collecting robot collects 1 ore; you now have 1 ore.
== Minute 2 ==
1 ore-collecting robot collects 1 ore; you now have 2 ore.
== Minute 3 ==
1 ore-collecting robot collects 1 ore; you now have 3 ore.
== Minute 4 ==
1 ore-collecting robot collects 1 ore; you now have 4 ore.
== Minute 5 ==
Spend 4 ore to start building an ore-collecting robot.
1 ore-collecting robot collects 1 ore; you now have 1 ore.
The new ore-collecting robot is ready; you now have 2 of them.
== Minute 6 ==
2 ore-collecting robots collect 2 ore; you now have 3 ore.
== Minute 7 ==
Spend 2 ore to start building a clay-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
The new clay-collecting robot is ready; you now have 1 of them.
== Minute 8 ==
Spend 2 ore to start building a clay-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
1 clay-collecting robot collects 1 clay; you now have 1 clay.
The new clay-collecting robot is ready; you now have 2 of them.
== Minute 9 ==
Spend 2 ore to start building a clay-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
2 clay-collecting robots collect 2 clay; you now have 3 clay.
The new clay-collecting robot is ready; you now have 3 of them.
== Minute 10 ==
Spend 2 ore to start building a clay-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
3 clay-collecting robots collect 3 clay; you now have 6 clay.
The new clay-collecting robot is ready; you now have 4 of them.
== Minute 11 ==
Spend 2 ore to start building a clay-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
4 clay-collecting robots collect 4 clay; you now have 10 clay.
The new clay-collecting robot is ready; you now have 5 of them.
== Minute 12 ==
Spend 2 ore to start building a clay-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
5 clay-collecting robots collect 5 clay; you now have 15 clay.
The new clay-collecting robot is ready; you now have 6 of them.
== Minute 13 ==
Spend 2 ore to start building a clay-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
6 clay-collecting robots collect 6 clay; you now have 21 clay.
The new clay-collecting robot is ready; you now have 7 of them.
== Minute 14 ==
Spend 3 ore and 14 clay to start building an obsidian-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 2 ore.
7 clay-collecting robots collect 7 clay; you now have 14 clay.
The new obsidian-collecting robot is ready; you now have 1 of them.
== Minute 15 ==
2 ore-collecting robots collect 2 ore; you now have 4 ore.
7 clay-collecting robots collect 7 clay; you now have 21 clay.
1 obsidian-collecting robot collects 1 obsidian; you now have 1 obsidian.
== Minute 16 ==
Spend 3 ore and 14 clay to start building an obsidian-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
7 clay-collecting robots collect 7 clay; you now have 14 clay.
1 obsidian-collecting robot collects 1 obsidian; you now have 2 obsidian.
The new obsidian-collecting robot is ready; you now have 2 of them.
== Minute 17 ==
Spend 3 ore and 14 clay to start building an obsidian-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 2 ore.
7 clay-collecting robots collect 7 clay; you now have 7 clay.
2 obsidian-collecting robots collect 2 obsidian; you now have 4 obsidian.
The new obsidian-collecting robot is ready; you now have 3 of them.
== Minute 18 ==
2 ore-collecting robots collect 2 ore; you now have 4 ore.
7 clay-collecting robots collect 7 clay; you now have 14 clay.
3 obsidian-collecting robots collect 3 obsidian; you now have 7 obsidian.
== Minute 19 ==
Spend 3 ore and 14 clay to start building an obsidian-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
7 clay-collecting robots collect 7 clay; you now have 7 clay.
3 obsidian-collecting robots collect 3 obsidian; you now have 10 obsidian.
The new obsidian-collecting robot is ready; you now have 4 of them.
== Minute 20 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
2 ore-collecting robots collect 2 ore; you now have 3 ore.
7 clay-collecting robots collect 7 clay; you now have 14 clay.
4 obsidian-collecting robots collect 4 obsidian; you now have 7 obsidian.
The new geode-cracking robot is ready; you now have 1 of them.
== Minute 21 ==
Spend 3 ore and 14 clay to start building an obsidian-collecting robot.
2 ore-collecting robots collect 2 ore; you now have 2 ore.
7 clay-collecting robots collect 7 clay; you now have 7 clay.
4 obsidian-collecting robots collect 4 obsidian; you now have 11 obsidian.
1 geode-cracking robot cracks 1 geode; you now have 1 open geode.
The new obsidian-collecting robot is ready; you now have 5 of them.
== Minute 22 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
2 ore-collecting robots collect 2 ore; you now have 2 ore.
7 clay-collecting robots collect 7 clay; you now have 14 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 9 obsidian.
1 geode-cracking robot cracks 1 geode; you now have 2 open geodes.
The new geode-cracking robot is ready; you now have 2 of them.
== Minute 23 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
2 ore-collecting robots collect 2 ore; you now have 2 ore.
7 clay-collecting robots collect 7 clay; you now have 21 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 7 obsidian.
2 geode-cracking robots crack 2 geodes; you now have 4 open geodes.
The new geode-cracking robot is ready; you now have 3 of them.
== Minute 24 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
2 ore-collecting robots collect 2 ore; you now have 2 ore.
7 clay-collecting robots collect 7 clay; you now have 28 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 5 obsidian.
3 geode-cracking robots crack 3 geodes; you now have 7 open geodes.
The new geode-cracking robot is ready; you now have 4 of them.
== Minute 25 ==
2 ore-collecting robots collect 2 ore; you now have 4 ore.
7 clay-collecting robots collect 7 clay; you now have 35 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 10 obsidian.
4 geode-cracking robots crack 4 geodes; you now have 11 open geodes.
== Minute 26 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
2 ore-collecting robots collect 2 ore; you now have 4 ore.
7 clay-collecting robots collect 7 clay; you now have 42 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 8 obsidian.
4 geode-cracking robots crack 4 geodes; you now have 15 open geodes.
The new geode-cracking robot is ready; you now have 5 of them.
== Minute 27 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
2 ore-collecting robots collect 2 ore; you now have 4 ore.
7 clay-collecting robots collect 7 clay; you now have 49 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 6 obsidian.
5 geode-cracking robots crack 5 geodes; you now have 20 open geodes.
The new geode-cracking robot is ready; you now have 6 of them.
== Minute 28 ==
2 ore-collecting robots collect 2 ore; you now have 6 ore.
7 clay-collecting robots collect 7 clay; you now have 56 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 11 obsidian.
6 geode-cracking robots crack 6 geodes; you now have 26 open geodes.
== Minute 29 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
2 ore-collecting robots collect 2 ore; you now have 6 ore.
7 clay-collecting robots collect 7 clay; you now have 63 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 9 obsidian.
6 geode-cracking robots crack 6 geodes; you now have 32 open geodes.
The new geode-cracking robot is ready; you now have 7 of them.
== Minute 30 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
2 ore-collecting robots collect 2 ore; you now have 6 ore.
7 clay-collecting robots collect 7 clay; you now have 70 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 7 obsidian.
7 geode-cracking robots crack 7 geodes; you now have 39 open geodes.
The new geode-cracking robot is ready; you now have 8 of them.
== Minute 31 ==
Spend 2 ore and 7 obsidian to start building a geode-cracking robot.
2 ore-collecting robots collect 2 ore; you now have 6 ore.
7 clay-collecting robots collect 7 clay; you now have 77 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 5 obsidian.
8 geode-cracking robots crack 8 geodes; you now have 47 open geodes.
The new geode-cracking robot is ready; you now have 9 of them.
== Minute 32 ==
2 ore-collecting robots collect 2 ore; you now have 8 ore.
7 clay-collecting robots collect 7 clay; you now have 84 clay.
5 obsidian-collecting robots collect 5 obsidian; you now have 10 obsidian.
9 geode-cracking robots crack 9 geodes; you now have 56 open geodes.
However, blueprint 2 from the example above is still better; using it, the largest number of geodes you could open in 32 minutes is 62.
You no longer have enough blueprints to worry about quality levels. Instead, for each of the first three blueprints, determine the largest number of geodes you could open; then, multiply these three values together.
Don’t worry about quality levels; instead, just determine the largest number of geodes you could open using each of the first three blueprints. What do you get if you multiply these numbers together?
2.2.2 Solution
For the second part we can use the same fast C++ algorithm, with a larger round number.
multiply_geode_amounts <- function(bps) {
imap_int(bps[1:3], ~ solve_blueprint(32L, .x)) %>%
prod()
}
multiply_geode_amounts(puzzle_data)
## [1] 37367
3 R Legacy Solution
solve_blueprint <- function(bp, time_limit) {
robots <- c(ore = 1L, clay = 0L, obsidian = 0L, geode = 0L)
resources <- c(ore = 0L, clay = 0L, obsidian = 0L, geode = 0L)
max_geode <- 0L
memo <- new.env(parent = emptyenv())
max_cost <- do.call(rbind, bp) %>%
apply(2L, max)
max_cost["geode"] <- Inf
get_upper_bound <- function(time, robots, resources) {
resources["geode"] + # amount of geode we have so far
robots["geode"] * time + # amount of geode the existing robots will produce
time * (time - 1L) / 2L # max output if we could build a geode robot eahc turn
}
cap_resources <- function(resources, time, max_cost) {
capped <- pmin(resources[names(max_cost)], max_cost * time)
c(capped, resources["geode"])
}
rec <- 0L
dfs <- function(time, robots, resources) {
if (time == 0L) {
max_geode <<- max(max_geode, resources["geode"])
return(resources["geode"])
}
if (get_upper_bound(time, robots, resources) <= max_geode) {
return(0L)
}
capped_resources <- cap_resources(resources, time, max_cost)
key <- paste(time, paste(robots, collapse = ","),
paste(capped_resources, collapse = ","),
sep = "|")
if (exists(key, memo, inherits = FALSE)) {
return(memo[[key]])
}
max_local <- 0L
produced <- resources + robots
robot_types <- rev(names(robots))
for (robot_type in robot_types) {
if (robot_type != "geode" && robots[robot_type] >= max_cost[robot_type]) {
next
}
robot_cost <- bp[[robot_type]]
if (all(resources[-4L] >= robot_cost)) {
new_resources <- produced - c(robot_cost, 0L)
new_robots <- robots
new_robots[robot_type] <- new_robots[robot_type] + 1L
if (robot_type == "geode") {
max_local <- max(max_local, Recall(time - 1L, new_robots, new_resources))
memo[[key]] <<- max_local
return(max_local)
}
max_local <- max(
max_local,
Recall(time - 1L, new_robots, new_resources)
)
}
}
max_local <- max(
max_local,
Recall(time - 1L, robots, produced)
)
memo[[key]] <<- max_local
return(max_local)
}
res <- dfs(time_limit, robots, resources)
res
}