Ben Ashton
1 year ago
2 changed files with 207 additions and 0 deletions
@ -0,0 +1,30 @@ |
|||||||
|
Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 5 clay. Each geode robot costs 3 ore and 7 obsidian. |
||||||
|
Blueprint 2: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 2 ore and 20 obsidian. |
||||||
|
Blueprint 3: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 2 ore and 9 obsidian. |
||||||
|
Blueprint 4: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 3 ore and 9 obsidian. |
||||||
|
Blueprint 5: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 2 ore and 18 obsidian. |
||||||
|
Blueprint 6: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 4 ore and 12 obsidian. |
||||||
|
Blueprint 7: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 2 ore and 8 obsidian. |
||||||
|
Blueprint 8: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 3 ore and 10 obsidian. |
||||||
|
Blueprint 9: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 18 clay. Each geode robot costs 4 ore and 19 obsidian. |
||||||
|
Blueprint 10: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian. |
||||||
|
Blueprint 11: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 5 clay. Each geode robot costs 4 ore and 11 obsidian. |
||||||
|
Blueprint 12: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 19 clay. Each geode robot costs 2 ore and 9 obsidian. |
||||||
|
Blueprint 13: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 2 ore and 11 obsidian. |
||||||
|
Blueprint 14: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 2 ore and 12 obsidian. |
||||||
|
Blueprint 15: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 4 ore and 11 obsidian. |
||||||
|
Blueprint 16: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 18 clay. Each geode robot costs 4 ore and 11 obsidian. |
||||||
|
Blueprint 17: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 2 ore and 18 obsidian. |
||||||
|
Blueprint 18: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 7 clay. Each geode robot costs 2 ore and 7 obsidian. |
||||||
|
Blueprint 19: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 11 obsidian. |
||||||
|
Blueprint 20: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 9 clay. Each geode robot costs 2 ore and 10 obsidian. |
||||||
|
Blueprint 21: 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 20 obsidian. |
||||||
|
Blueprint 22: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 19 clay. Each geode robot costs 4 ore and 12 obsidian. |
||||||
|
Blueprint 23: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 5 clay. Each geode robot costs 2 ore and 10 obsidian. |
||||||
|
Blueprint 24: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 2 ore and 13 obsidian. |
||||||
|
Blueprint 25: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 17 clay. Each geode robot costs 3 ore and 11 obsidian. |
||||||
|
Blueprint 26: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 17 clay. Each geode robot costs 4 ore and 20 obsidian. |
||||||
|
Blueprint 27: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 8 clay. Each geode robot costs 3 ore and 9 obsidian. |
||||||
|
Blueprint 28: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 8 clay. Each geode robot costs 2 ore and 14 obsidian. |
||||||
|
Blueprint 29: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 17 clay. Each geode robot costs 3 ore and 10 obsidian. |
||||||
|
Blueprint 30: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 8 obsidian. |
@ -0,0 +1,177 @@ |
|||||||
|
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 |
||||||
|
); |
Loading…
Reference in new issue