Advent-of-Code-2022/19/solution.mjs

178 lines
5.3 KiB
JavaScript
Raw Normal View History

2022-12-27 15:27:14 -08:00
import { readFileSync } from 'node:fs';
const input = readFileSync('input', 'utf-8');
// Materials
const M = {
ORE: 0,
CLAY: 1,
OBSIDIAN: 2,
GEODE: 3
};
const blueprints = input
.split('\n')
.filter(line => !!line.trim())
.map(line => {
const [details, robotsData] = line.split(/:\s+/);
const blueprintId = parseInt(details.match(/\d+/)[0]);
const robots = robotsData
.split(/\.\s*(?!$)/)
.map(robotData => {
const robotType = robotData.match(/(\w+)\s+robot/)[1];
const index = Object.entries(M).find(([name]) =>
name === robotType.toUpperCase()
)[1];
const costs = [
parseInt(robotData.match(/\d+(?=\s+ore)/)?.[0] ?? 0),
parseInt(robotData.match(/\d+(?=\s+clay)/)?.[0] ?? 0),
parseInt(robotData.match(/\d+(?=\s+obsidian)/)?.[0] ?? 0),
0
];
return [index, costs];
})
.reduce((arr, [index, costs]) => {
arr[index] = costs;
return arr;
}, []);
return {
id: blueprintId,
robots
};
});
// Attempt to build the given robot and apply changes to the inventory. Return
// new robots and inventory arrays, or undefined if unable to build
function build(blueprint, robotType, inventory, robots) {
const newInventory = [];
for (const [material, cost] of blueprint.robots[robotType].entries()) {
const newQuantity = inventory[material] - cost;
if (newQuantity < 0) return;
newInventory.push(newQuantity);
}
const newRobots = [...robots];
newRobots[robotType] += 1;
return [newInventory, newRobots];
}
// Calculate the maximum possible number of geodes that could be cracked within
// the remaining time assuming an infinite inventory
function getMaxPossibleGeodes(state, timeRemaining) {
const geodeRobotCount = state[1][M.GEODE];
const geodeCount = state[0][M.GEODE];
return (
geodeCount +
(timeRemaining * geodeRobotCount) +
((timeRemaining * Math.max(0, timeRemaining - 1)) / 2)
);
}
function getMaximumGeodes(blueprint, totalTime) {
let states = [[[0, 0, 0, 0], [1, 0, 0, 0]]];
// Determine the worst case scenario per-turn requirements for each material
const requirements = Object.keys(M).map(material =>
blueprint.robots.reduce(
(max, r) => Math.max(max, r[M[material]]),
-Infinity
)
);
for (let time = 0; time < totalTime; time++) {
const timeRemaining = totalTime - time;
const newStates = [];
// Prune
states = states.filter((stateA, ia, states) => {
// Remove duplicates
return states.findLastIndex(stateB =>
stateB[0].every((c, i) => c === stateA[0][i]) &&
stateB[1].every((c, i) => c === stateA[1][i])
) === ia;
}).filter((stateA, ia) => {
// Maximum possible number of geodes that could be created from this
// state assuming an infinite inventory
const fantasyA = getMaxPossibleGeodes(stateA, timeRemaining);
// Filter out states where there exists a better state
for (let ib = 0; ib < states.length; ib++) {
const stateB = states[ib];
// If stateB has more geodes than stateA could ever possibly hope to
// gather then discard stateA (minor optimization)
if (stateB[0][M.GEODE] > fantasyA) {
return false;
}
// Discard stateA if stateB is objectively better (all values larger or
// the same, at least one larger)
if (
ib !== ia &&
stateB[0].every((c, i) => c >= stateA[0][i]) &&
stateB[1].every((c, i) => c >= stateA[1][i]) && (
stateB[0].some((c, i) => c > stateA[0][i]) ||
stateB[1].some((c, i) => c > stateA[1][i])
)
) {
return false;
}
}
return true;
});
// Process states
for (const [inventory, robots] of states) {
// Calculate inventory thresholds after which there is no longer any
// incentive to build more of that robot
const thresholds = Object.keys(M).map(material =>
(requirements[M[material]] - robots[M[material]]) * (timeRemaining - 1)
);
const buildOre = inventory[M.ORE] < thresholds[M.ORE];
const buildClay = inventory[M.CLAY] < thresholds[M.CLAY];
const buildObsidian = inventory[M.OBSIDIAN] < thresholds[M.OBSIDIAN];
let choices = [
// do nothing
[[...inventory], [...robots]],
buildOre && build(blueprint, M.ORE, inventory, robots),
buildClay && build(blueprint, M.CLAY, inventory, robots),
buildObsidian && build(blueprint, M.OBSIDIAN, inventory, robots),
build(blueprint, M.GEODE, inventory, robots)
].filter(choice => !!choice);
choices = choices.map(([cInv, cRob]) => [
cInv.map((c, i) => c + robots[i]),
cRob
]);
newStates.push(...choices);
}
states = newStates;
}
return states.reduce((max, state) => Math.max(max, state[0][M.GEODE]), 0);
}
let totalQuality = 0;
for (const blueprint of blueprints) {
const geodes = getMaximumGeodes(blueprint, 24);
const quality = blueprint.id * geodes;
totalQuality += quality
}
console.log('The sum of all blueprint quality levels is:', totalQuality);
let totalMultiplied = 1;
for (const blueprint of blueprints.slice(0, 3)) {
totalMultiplied *= getMaximumGeodes(blueprint, 32);
}
console.log(
'Multiplication of max geodes for first three blueprints:',
totalMultiplied
);