-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay19.kt
123 lines (102 loc) · 4.37 KB
/
Day19.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package aoc2022.day19
import util.readInputLineByLine
fun readInputToBlueprint(path: String): List<Blueprint> =
readInputLineByLine(path).map { Blueprint.fromInputString(it) }
fun part1(blueprints: List<Blueprint>): Int {
return blueprints.fold(0) { acc, blueprint ->
val currResult = MiningWork().maxMinedGeodesIn(24, blueprint)
blueprint.id * currResult + acc
}
}
fun part2(blueprints: List<Blueprint>): Long {
return blueprints.take(3).fold(1L) { acc, blueprint ->
acc * MiningWork().maxMinedGeodesIn(32, blueprint)
}
}
data class MiningWork(
val ore: Int = 0,
val clay: Int = 0,
val obsidian: Int = 0,
val geode: Int = 0,
val oreRobots: Int = 1,
val clayRobots: Int = 0,
val obsidianRobots: Int = 0,
val geodeRobots: Int = 0
) {
private fun possibleNextStates(blueprint: Blueprint, canBuyNext: Int): List<Pair<MiningWork, Int>> {
val oreRobotCost = blueprint.oreRobotCost
val obsidianRobotCost = blueprint.obsidianRobotCost
val geodeRobotCost = blueprint.geodeRobotCost
val clayRobotCost = blueprint.clayRobotCost
val maxOreCost = listOf(oreRobotCost, clayRobotCost, obsidianRobotCost.oreQnt, geodeRobotCost.oreQnt).max()
val maxClayCost = obsidianRobotCost.clayQnt
val maxObsidianCost = geodeRobotCost.obsidianQnt
if (ore / geodeRobotCost.oreQnt > 0 && obsidian / geodeRobotCost.obsidianQnt > 0) {
return listOf(build(blueprint, Robot.GEODE) to 15)
}
val possibleBuilds = mutableListOf<Robot>()
if (maxObsidianCost > obsidianRobots && canBuyNext and Robot.OBSIDIAN.bit != 0 &&
ore / obsidianRobotCost.oreQnt > 0 && clay / obsidianRobotCost.clayQnt > 0
) {
possibleBuilds += Robot.OBSIDIAN
}
if (maxClayCost > clayRobots && ore / clayRobotCost > 0 && canBuyNext and Robot.CLAY.bit != 0) {
possibleBuilds += Robot.CLAY
}
if (maxOreCost > oreRobots && ore / oreRobotCost > 0 && canBuyNext and Robot.ORE.bit != 0) {
possibleBuilds += Robot.ORE
}
val stopBuying = possibleBuilds.fold(canBuyNext) { current, acc -> acc.bit xor current }
return listOf(build(blueprint) to stopBuying) + possibleBuilds.map { build(blueprint, it) to 15 }
}
private fun build(blueprint: Blueprint, robot: Robot? = null): MiningWork = with(mine()) {
when (robot) {
null -> this
Robot.ORE -> copy(
oreRobots = oreRobots + 1,
ore = ore - blueprint.oreRobotCost
)
Robot.CLAY -> copy(
clayRobots = clayRobots + 1,
ore = ore - blueprint.clayRobotCost
)
Robot.OBSIDIAN -> copy(
obsidianRobots = obsidianRobots + 1,
ore = ore - blueprint.obsidianRobotCost.oreQnt,
clay = clay - blueprint.obsidianRobotCost.clayQnt
)
Robot.GEODE -> copy(
geodeRobots = geodeRobots + 1,
ore = ore - blueprint.geodeRobotCost.oreQnt,
obsidian = obsidian - blueprint.geodeRobotCost.obsidianQnt
)
}
}
private fun mine() = copy(
ore = ore + oreRobots,
clay = clay + clayRobots,
obsidian = obsidian + obsidianRobots,
geode = geode + geodeRobots
)
fun maxMinedGeodesIn(minutes: Int, blueprint: Blueprint, canBuyNext: Int = 15): Int {
if (minutes == 0) return geode
val nextStates = possibleNextStates(blueprint, canBuyNext)
return nextStates.maxOf { (it, canBuy) -> it.maxMinedGeodesIn(minutes - 1, blueprint, canBuy) }
}
}
data class Blueprint(
val id: Int, val oreRobotCost: Int, val clayRobotCost: Int, val obsidianRobotCost: ObsidianRobotCost,
val geodeRobotCost: GeodeRobotCost
) {
companion object {
fun fromInputString(input: String): Blueprint {
val split = Regex("\\d+").findAll(input).map { it.value.toInt() }.toList()
return Blueprint(
split[0], split[1], split[2], ObsidianRobotCost(split[3], split[4]), GeodeRobotCost(split[5], split[6])
)
}
}
}
data class ObsidianRobotCost(val oreQnt: Int, val clayQnt: Int)
data class GeodeRobotCost(val oreQnt: Int, val obsidianQnt: Int)
enum class Robot(val bit: Int) { ORE(1), CLAY(2), OBSIDIAN(4), GEODE(8) }