github.com/mnlphlp/aoc22@v0.0.0-20230330151331-c1dc4bff1b9b/day19/day19.go (about) 1 package day19 2 3 import ( 4 "fmt" 5 "strings" 6 "sync" 7 8 "github.com/mnlphlp/aoc22/util" 9 ) 10 11 type ressources struct { 12 clay int 13 ore int 14 obsidian int 15 } 16 17 type blueprint struct { 18 id int 19 oreRobot ressources 20 clayRobot ressources 21 obsidianRobot ressources 22 geodeRobot ressources 23 maxOreCost int 24 maxClayCost int 25 maxObsidianCost int 26 } 27 28 type state struct { 29 clay, ore, obsidian int 30 clayBots, oreBots, obsidianBots, geodeBots int 31 geodes int 32 timePassed int 33 } 34 35 func parseInput(input string) []blueprint { 36 input = strings.Replace(input, "\n", "", -1) 37 blueprints := []blueprint{} 38 for _, bpStr := range strings.Split(input, "Blueprint") { 39 if bpStr == "" { 40 continue 41 } 42 bp := blueprint{} 43 fmt.Sscanf(bpStr, 44 " %d: Each ore robot costs %d ore. Each clay robot costs %d ore. Each obsidian robot costs %d ore and %d clay. Each geode robot costs %d ore and %d obsidian.", 45 &bp.id, &bp.oreRobot.ore, &bp.clayRobot.ore, &bp.obsidianRobot.ore, &bp.obsidianRobot.clay, &bp.geodeRobot.ore, &bp.geodeRobot.obsidian, 46 ) 47 bp.maxClayCost = util.Max(bp.clayRobot.clay, bp.oreRobot.clay, bp.obsidianRobot.clay, bp.geodeRobot.clay) 48 bp.maxOreCost = util.Max(bp.clayRobot.ore, bp.oreRobot.ore, bp.obsidianRobot.ore, bp.geodeRobot.ore) 49 bp.maxObsidianCost = util.Max(bp.clayRobot.obsidian, bp.oreRobot.obsidian, bp.obsidianRobot.obsidian, bp.geodeRobot.obsidian) 50 blueprints = append(blueprints, bp) 51 } 52 return blueprints 53 } 54 55 func getQualityNumbers(blueprints []blueprint, timeSteps int, parallel bool) []int { 56 qualityNumbers := make([]int, len(blueprints)) 57 wg := sync.WaitGroup{} 58 for i, bp := range blueprints { 59 if parallel { 60 wg.Add(1) 61 go func(i int, bp blueprint) { 62 defer wg.Done() 63 qualityNumbers[i] = getMaxGeode(bp, timeSteps) * bp.id 64 }(i, bp) 65 } else { 66 qualityNumbers[i] = getMaxGeode(bp, timeSteps) * bp.id 67 } 68 } 69 wg.Wait() 70 return qualityNumbers 71 } 72 73 func hashState(s state) int { 74 hash := s.clay 75 hash |= s.ore << 8 76 hash |= s.obsidian << 16 77 hash |= s.timePassed << 24 78 return hash 79 } 80 81 func canBuild(s state, r ressources) bool { 82 return s.clay >= r.clay && s.obsidian >= r.obsidian && s.ore >= r.ore 83 } 84 85 func consumeRessources(s state, r ressources) state { 86 s.clay -= r.clay 87 s.ore -= r.ore 88 s.obsidian -= r.obsidian 89 return s 90 } 91 92 func getNeighbors(s state, bp blueprint, time int) []state { 93 neighbors := make([]state, 0) 94 if s.timePassed == time-1 { 95 return neighbors 96 } 97 // create doNothing state 98 doNothing := s 99 doNothing.timePassed++ 100 doNothing.clay += doNothing.clayBots 101 doNothing.ore += doNothing.oreBots 102 doNothing.obsidian += doNothing.obsidianBots 103 // geodes are computed directly 104 // if geode bot is possible only build this 105 if canBuild(s, bp.geodeRobot) { 106 new := consumeRessources(doNothing, bp.geodeRobot) 107 new.geodeBots++ 108 new.geodes += time - new.timePassed 109 // if max count for all other robots is reached skip next steps 110 if s.clayBots == bp.maxClayCost && s.oreBots == bp.maxOreCost && s.obsidianBots == bp.maxObsidianCost { 111 // one geode bot can be build per time step 112 remTime := time - new.timePassed 113 new.geodeBots += remTime 114 new.geodes += int(float64(remTime) * float64(remTime) / 2) 115 new.timePassed = time 116 } 117 neighbors = append(neighbors, new) 118 return neighbors 119 } 120 // append doNothing 121 neighbors = append(neighbors, doNothing) 122 // append other possible bots 123 if s.clayBots < bp.maxClayCost && canBuild(s, bp.clayRobot) { 124 new := consumeRessources(doNothing, bp.clayRobot) 125 new.clayBots++ 126 neighbors = append(neighbors, new) 127 } 128 if s.oreBots < bp.maxOreCost && canBuild(s, bp.oreRobot) { 129 new := consumeRessources(doNothing, bp.oreRobot) 130 new.oreBots++ 131 neighbors = append(neighbors, new) 132 } 133 if s.obsidianBots < bp.maxObsidianCost && canBuild(s, bp.obsidianRobot) { 134 new := consumeRessources(doNothing, bp.obsidianRobot) 135 new.obsidianBots++ 136 neighbors = append(neighbors, new) 137 } 138 return neighbors 139 } 140 141 func getMaxGeode(bp blueprint, timeSteps int) int { 142 // calculate max number of geodes cracked 143 current := state{oreBots: 1} 144 open := []state{current} 145 best := current 146 closed := map[int]struct{}{} 147 for len(open) > 0 { 148 // get next state 149 current = open[len(open)-1] 150 open = open[:len(open)-1] 151 // check if new max can be reached 152 remTime := timeSteps - current.timePassed 153 if current.geodes+(remTime*(remTime/2)) <= best.geodes { 154 continue 155 } 156 closed[hashState(current)] = struct{}{} 157 if current.geodes > best.geodes { 158 best = current 159 } 160 for _, n := range getNeighbors(current, bp, timeSteps) { 161 if _, ok := closed[hashState(n)]; ok { 162 continue 163 } 164 open = append(open, n) 165 } 166 } 167 if debug { 168 fmt.Printf("Result blueprint %d: can open %d geodes\n", bp.id, best.geodes) 169 } 170 return best.geodes 171 } 172 173 func getMaxGeodes(blueprints []blueprint, timeSteps int, parallel bool) []int { 174 maxGeodes := make([]int, len(blueprints)) 175 wg := sync.WaitGroup{} 176 for i, bp := range blueprints { 177 if parallel { 178 wg.Add(1) 179 go func(i int, bp blueprint) { 180 defer wg.Done() 181 maxGeodes[i] = getMaxGeode(bp, timeSteps) 182 }(i, bp) 183 } else { 184 maxGeodes[i] = getMaxGeode(bp, timeSteps) 185 } 186 } 187 wg.Wait() 188 return maxGeodes 189 } 190 191 var debug = false 192 193 func Solve(input string, debugFlag bool, task int) (string, string) { 194 res1, res2 := 0, 0 195 debug = debugFlag 196 blueprints := parseInput(input) 197 if debug { 198 fmt.Println(blueprints) 199 200 } 201 if task != 2 { 202 qualities := getQualityNumbers(blueprints, 24, !debug) 203 fmt.Printf("Quality numbers: %v\n", qualities) 204 res1 = util.Sum(qualities...) 205 } 206 if task != 1 { 207 maxGeodes := getMaxGeodes(blueprints[:util.Min(3, len(blueprints))], 32, !debug) 208 fmt.Printf("Max geodes: %v\n", maxGeodes) 209 res2 = util.Mul(maxGeodes) 210 } 211 return fmt.Sprint(res1), fmt.Sprint(res2) 212 }