Files
advent-of-code-go/2022/day19/main.go
T
2022-12-25 12:04:59 -05:00

212 lines
5.3 KiB
Go

package main
import (
_ "embed"
"flag"
"fmt"
"strings"
"github.com/alexchao26/advent-of-code-go/mathy"
"github.com/alexchao26/advent-of-code-go/util"
)
//go:embed input.txt
var input string
func init() {
// do this in init (not main) so test file has same input
input = strings.TrimRight(input, "\n")
if len(input) == 0 {
panic("empty input.txt file")
}
}
func main() {
var part int
flag.IntVar(&part, "part", 1, "part 1 or 2")
flag.Parse()
fmt.Println("Running part", part)
if part == 1 {
ans := part1(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
} else {
ans := part2(input)
util.CopyToClipboard(fmt.Sprintf("%v", ans))
fmt.Println("Output:", ans)
}
}
func part1(input string) int {
blueprints := parseInput(input)
// how many geodes can be opened in 24 minutes?
sum := 0
for _, bp := range blueprints {
st := newState(bp)
geodesMade := st.calcMostGeodes(0, map[string]int{}, 24, 24)
// fmt.Println("ID:", bp.id, geodesMade)
sum += st.blueprint.id * geodesMade
}
// total quality of all blueprints, quality = id * (# geodes in 24 min)
return sum
}
func part2(input string) int {
blueprints := parseInput(input)
if len(blueprints) > 3 {
blueprints = blueprints[:3]
}
prod := 1
for _, bp := range blueprints {
st := newState(bp)
geodesMade := st.calcMostGeodes(0, map[string]int{}, 32, 32)
// fmt.Println(bp.id, geodesMade)
prod *= geodesMade
}
// total quality of all blueprints, quality = id * (# geodes in 24 min)
return prod
}
type blueprint struct {
id int
oreForOreRobot int
oreForClayRobot int
oreForObsidianRobot, clayForObsidianRobot int
oreForGeodeRobot, obsidianForGeodeRobot int
}
type state struct {
blueprint
ore, clay, obsidian, geode int
oreRobots, clayRobots, obsidianRobots, geodeRobots int
}
func newState(blueprint blueprint) state {
return state{
blueprint: blueprint,
oreRobots: 1,
}
}
func (s *state) farm() {
s.ore += s.oreRobots
s.clay += s.clayRobots
s.obsidian += s.obsidianRobots
s.geode += s.geodeRobots
}
func (s *state) hash(time int) string {
return fmt.Sprint(time, s.ore, s.clay, s.obsidian,
s.geode, s.oreRobots, s.clayRobots, s.obsidianRobots, s.geodeRobots)
}
// NOT A POINTER METHOD SO A COPY CAN BE MADE
// this is some cheeky Go struct copying, it'd be easier to read if it was just
// directly recreating all the fields
func (s state) copy() state {
return s
}
func (s *state) calcMostGeodes(time int, memo map[string]int, totalTime int, earliestGeode int) int {
if time == totalTime {
return s.geode
}
h := s.hash(time)
if v, ok := memo[h]; ok {
return v
}
if s.geode == 0 && time > earliestGeode {
return 0
}
// factory can try to make any possible robot, will backtrack if necessary
mostGeodes := s.geode
// always make geode robots
if s.ore >= s.oreForGeodeRobot &&
s.obsidian >= s.obsidianForGeodeRobot {
cp := s.copy()
cp.farm()
cp.ore -= cp.oreForGeodeRobot
cp.obsidian -= cp.obsidianForGeodeRobot
cp.geodeRobots++
if cp.geodeRobots == 1 {
earliestGeode = mathy.MinInt(earliestGeode, time+1)
}
mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode))
memo[h] = mostGeodes
return mostGeodes
}
if time <= totalTime-16 &&
s.oreRobots < s.oreForObsidianRobot*2 &&
s.ore >= s.oreForOreRobot {
cp := s.copy()
cp.ore -= cp.oreForOreRobot
cp.farm()
cp.oreRobots++
mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode))
}
if time <= totalTime-8 &&
s.clayRobots < s.clayForObsidianRobot &&
s.ore >= s.oreForClayRobot {
cp := s.copy()
cp.ore -= cp.oreForClayRobot
cp.farm()
cp.clayRobots++
mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode))
}
if time <= totalTime-4 &&
s.obsidianRobots < s.obsidianForGeodeRobot &&
s.ore >= s.oreForObsidianRobot && s.clay >= s.clayForObsidianRobot {
cp := s.copy()
cp.ore -= cp.oreForObsidianRobot
cp.clay -= cp.clayForObsidianRobot
cp.farm()
cp.obsidianRobots++
mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode))
}
// or no factory production this minute
cp := s.copy()
cp.ore += cp.oreRobots
cp.clay += cp.clayRobots
cp.obsidian += cp.obsidianRobots
cp.geode += cp.geodeRobots
mostGeodes = mathy.MaxInt(mostGeodes, cp.calcMostGeodes(time+1, memo, totalTime, earliestGeode))
memo[h] = mostGeodes
return mostGeodes
}
func parseInput(input string) (ans []blueprint) {
// Blueprint 1: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 12 obsidian.
for _, line := range strings.Split(input, "\n") {
bp := blueprint{}
_, err := fmt.Sscanf(line, "Blueprint %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.",
&bp.id, &bp.oreForOreRobot, &bp.oreForClayRobot, &bp.oreForObsidianRobot,
&bp.clayForObsidianRobot, &bp.oreForGeodeRobot, &bp.obsidianForGeodeRobot)
if err != nil {
panic("parsing: " + err.Error())
}
ans = append(ans, bp)
}
return ans
}