diff --git a/19/input b/19/input new file mode 100644 index 0000000..4e3afbb --- /dev/null +++ b/19/input @@ -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. diff --git a/19/solution.mjs b/19/solution.mjs new file mode 100644 index 0000000..1c73478 --- /dev/null +++ b/19/solution.mjs @@ -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 +);