Still catching up, slowly but surely, but I am closing the gap. Here's hoping I can get this one in relatively quickly too!
This is rediculous. Utter madness. Almost stupid. Just a bunch of robots, and possible blueprints.
This seems... tedious. It seems like the only real possible choice is, each iteration, which bot to produce (or not produce). Another breadth first search of the space. So I'm going to need to figure out some sort of... state for this, and probably cache and compare later on.
int maxGeodes = 0;
while (!q.empty())
{
BotState s = q.front(); q.pop();
// Check end state
if (s.minutes == MINUTES)
{
maxGeodes = max(maxGeodes, s.geo);
continue;
}
// Build robot
switch (s.nextBot)
{
case BOT_TYPE::ORE:
if (s.ore < oreCost) continue;
s.ore -= oreCost;
break;
case BOT_TYPE::CLAY:
if (s.ore < clayCost) continue;
s.ore -= clayCost;
break;
case BOT_TYPE::OBSIDIAN:
if (s.ore < obsCostO) continue;
if (s.clay < obsCostC) continue;
s.ore -= obsCostO;
s.clay -= obsCostC;
break;
case BOT_TYPE::GEODE:
if (s.ore < geoCostO) continue;
if (s.obs < geoCostOb) continue;
s.ore -= geoCostO;
s.obs -= geoCostOb;
break;
}
// Collect resources
s.ore += s.oreBots;
s.clay += s.clayBots;
s.obs += s.obsBots;
s.geo += s.geoBots;
// Finish building robot
switch (s.nextBot)
{
case BOT_TYPE::ORE: s.oreBots++; break;
case BOT_TYPE::CLAY: s.clayBots++; break;
case BOT_TYPE::OBSIDIAN: s.obsBots++; break;
case BOT_TYPE::GEODE: s.geoBots++; break;
}
// Prepare next iteration
s.nextBot = BOT_TYPE::NONE;
s.minutes++;
for (int type = BOT_TYPE::NONE; type != BOT_TYPE::GEODE; type++)
{
BotState nextState = s;
nextState.nextBot = (BOT_TYPE) type;
q.push(nextState);
}
}
It crawls to a halt when it start simulating about minute 18. Adding some caching to the bot states made it a bit faster, but it still crawls to a slow at about minute 20 instead. I need to be a bit more clever.
I can only build one bot per minute, regardless of type. So if I have enough bots to collect, say, the maximum amount of ore needed to construct any bot, there's no point in making more. So I can just prune the branches where I make more, since it'll just be wasting construction time. I'll replace the loop with a bunch of check instead.
// None - default
BotState noneState = s;
noneState.nextBot = BOT_TYPE::NONE;
q.push(noneState);
// Ore
if (s.oreBots < maxOreBotsNeeded && s.ore >= oreCost)
{
BotState oreState = s;
oreState.nextBot = BOT_TYPE::ORE;
q.push(oreState);
}
/// etc....
That, plus moving the ore checks into that step really made this a lot quicker. Enough to actually finish a blueprint with the right answer! Unfortunately, it still takes several minutes per blueprint, and will be unreasonable for the full solution. I need more....
If there's enough stock of a resource that I wouldn't need to get more, at least in the time that's left, then there's also no point at that point in getting more. Like if I have 20 ore, 2 bots, with 10 minutes left, there's no point to make more bots.
// Ore
if (s.oreBots < maxOreNeeded // If we can gather enough to build a bot each minute
&& ((s.oreBots * dt) + s.ore) < (maxOreNeeded * dt) // If we have enough stores
&& s.ore >= oreCost) // If we have the resources currently
{
BotState oreState = s;
oreState.nextBot = BOT_TYPE::ORE;
q.push(oreState);
}
That's running much faster yet, but it still takes a bit. I've also reordered it so Geode bots are considered first, and if one can be built, the next action is to build one, no other considerations. That also sped it up a bit. I don't know if it's correct but it makes sense, and prunes a bunch of other paths.
Unfortunately I'm running into a memory problem... between each iteration, I think so much cleanup or reallocation might be happenign that it's just slowing things down. I do pretty much just have everything on the stack. I just need to find another way to prune states.
Another option: Just ignore all states that produce less than the current optimally known state. Since Geode bots are essentially the end goal, once those start getting produced, it should promote branches that go down there, and prune those that don't.
maxGeodes = max(maxGeodes, s.geo);
if (s.geo < maxGeodes) continue;
That worked shockingly well! It was still a little slow, up until the last few minutes. I imagine it's searching for the most efficient way to get up to the geode bot production, then all those that didn't cut it just got purned all at once!
... Except it runs extremely slow after the first few iterations. Regardless of the run I'm doing.
After a bit of fiddling around, it was memory management issues. All I did was convert my BotState
s into pointers, so it isn't absolutely destroying the stack. Now it's super quick - about 3-4s per blueprint.
Took nearly 4 minutes (it was closer to about 8s/blueprint) but it worked!
Interesting... part 2 is more iterations, as these sorts of problems typically are, but it's only about ten minutes deeper... it the person making the puzzles thought that just adding a few more iterations was more than enough, so only the first 3 of the inputs had to be created?
I had to make a few small tweaks, mostly to bookkeeping. But oddly enough, the first time I ran the code it gave the the wrong answer. But a small change gave the the right answer. I guess this was more of a heuristic than a hard rule?
maxGeodes = max(maxGeodes, s->geo);
if (s->geo < maxGeodes-1) continue;
Either way, that ran quick and gave me an answer.
I managed to finish two tonight. Now I'm only 3 behind, far better than the week behind I was before. There's some hope!