Skip to content

Commit

Permalink
day 19 (unoptimized)
Browse files Browse the repository at this point in the history
  • Loading branch information
kgeri committed Dec 19, 2022
1 parent f217435 commit e53ebee
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 56 deletions.
119 changes: 65 additions & 54 deletions src/main/kotlin/me/gergo/Aoc19.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,84 +2,95 @@ package me.gergo

import me.gergo.Resource.*
import java.io.File
import java.util.*


fun main() {
val blueprints = File("src/main/resources/input19.txt").readLines()
.map(::parseBlueprints)

val blueprint = blueprints[0]
println("Building $blueprint")
// Part One
// val results1 = solve(blueprints, 24)
// val sumQualityLevels = results1.map { (blueprint, geodes) -> blueprint.id * geodes }.sum()
// println("Sum quality levels: $sumQualityLevels")

val memo = mutableMapOf<State, Int>()

fun simulate(state: State, round: Int): Int {
if (round == 0) return state.resources[GEODE]
val cachedResult = memo[state]

if (cachedResult != null) return cachedResult

val buildStates = state.resources.keys
.filter { state.canBuild(it, blueprint) }
.filter { state.shouldBuild(it, blueprint) }
.map { state.buildAndGather(it, blueprint) }
val maxResourceTypes = buildStates.maxOfOrNull { it.resources.resourceTypeCount() } ?: 0

val possibleStates = buildStates
.filter { it.resources.resourceTypeCount() >= maxResourceTypes } // Preferring states that maximize the number of resource types
.plus(state.gather())
val result = possibleStates.maxOf { simulate(it, round - 1) }
// Part Two
val results2 = solve(blueprints.take(3), 32)
val maxGeodesMultiple = results2.values.reduce(Int::times)
println("Max geodes multiple: $maxGeodesMultiple")
}

memo[state] = result
return result
private fun solve(blueprints: List<Blueprint>, minutes: Int): Map<Blueprint, Int> {
val results = mutableMapOf<Blueprint, Int>()
for (blueprint in blueprints) {
var states = setOf(State(mapOf(ORE to 1), emptyMap()))
for (t in 1..minutes) {
println("Minute $t, number of states: ${states.size}")
val newStates = HashSet<State>(states.size * 2)

val resourceCaps = ResourceTypes.associateWith { type -> // Cap resource counters
val maxCost = blueprint.maxCosts[type]!!
if (maxCost == Int.MAX_VALUE) Int.MAX_VALUE else maxCost * (minutes - t)
}

for (state in states) {
for (type in ResourceTypes) { // Generating possible builder states
if (!state.canBuild(type, blueprint)) continue // Not enough minerals :)
if (!state.shouldBuild(type, blueprint)) continue // We already produce enough minerals of this kind to build anything
newStates.add(state.buildAndGather(type, blueprint, resourceCaps)) // Building a new robot
}
newStates.add(state.gather(resourceCaps)) // Just gathering is always an option
}
states = newStates
}

val maxGeode = states.maxOf(State::numGeodes)
results[blueprint] = maxGeode
println("Maximum number of geodes created: $maxGeode (Blueprint: $blueprint)")
}

val maxGeode = simulate(State(mapOf(ORE to 1), Resources()), 24)
println(maxGeode)
return results
}

private val ResourceTypes = Resource.values()

private enum class Resource {
ORE, CLAY, OBSIDIAN, GEODE;
}

private class Resources() : HashMap<Resource, Int>() {
init {
for (type in Resource.values()) put(type, 0)
}
private data class State(val robots: Map<Resource, Int>, val resources: Map<Resource, Int>) {

constructor(other: Resources) : this() {
other.forEach { (type, amount) -> this[type] = amount }
}
fun canBuild(type: Resource, blueprint: Blueprint) = blueprint.costs[type]!!.all { (t, cost) -> resources.getOrDefault(t, 0) >= cost }

fun resourceTypeCount() = count { it.value > 0 }
fun shouldBuild(type: Resource, blueprint: Blueprint) = // Build robots only until we can produce the most expensive robot of that kind
robots.getOrDefault(type, 0) < blueprint.maxCosts[type]!!

override fun get(key: Resource) = super.get(key)!!
fun numGeodes(): Int = resources.getOrDefault(GEODE, 0)

operator fun plus(deltas: Iterable<Pair<Resource, Int>>): Resources {
val result = Resources(this)
deltas.forEach { (type, delta) -> result[type] = result[type] + delta }
return result
fun buildAndGather(type: Resource, blueprint: Blueprint, resourceCaps: Map<Resource, Int>): State {
val newResources = copyOf(resources)
val newRobots = copyOf(robots) // Building a new robot
blueprint.costs[type]!!.forEach { (t, cost) -> newResources.increment(t, -cost, resourceCaps[t]!!) } // Reducing minerals by the cost
newRobots.increment(type, 1, resourceCaps[type]!!) // Adding the robot
robots.forEach { (t, count) -> newResources.increment(t, count, resourceCaps[t]!!) } // Existing robots are gathering
return State(newRobots, newResources)
}

operator fun minus(deltas: Iterable<Pair<Resource, Int>>): Resources {
val result = Resources(this)
deltas.forEach { (type, delta) -> result[type] = result[type] - delta }
return result
fun gather(resourceCaps: Map<Resource, Int>): State {
val newResources = copyOf(resources)
robots.forEach { (t, count) -> newResources.increment(t, count, resourceCaps[t]!!) } // Existing robots are gathering
return State(robots, newResources)
}
}

private data class State(val robots: Map<Resource, Int>, val resources: Resources) {

fun canBuild(type: Resource, blueprint: Blueprint) = blueprint.costs[type]!!.all { (t, cost) -> resources[t] >= cost }

fun shouldBuild(type: Resource, blueprint: Blueprint) = // Do not build a new robot if we can already produce the max needed amount per turn
robots.getOrDefault(type, 0) < blueprint.maxCosts[type]!!

fun buildAndGather(type: Resource, blueprint: Blueprint) = // Building a robot, decreasing with costs and incrementing with what was gathered
State(robots.plus(Pair(type, robots.getOrDefault(type, 0) + 1)), resources - blueprint.costs[type]!! + gatheredResources())
private fun MutableMap<Resource, Int>.increment(type: Resource, delta: Int, cap: Int) {
val value = this.getOrDefault(type, 0)
this[type] = minOf(value + delta, cap)
}

fun gather() = State(robots, resources + gatheredResources()) // Incrementing with whatever the robots gathered
private fun gatheredResources() = robots.map { (type, count) -> Pair(type, count) }
private fun copyOf(other: Map<Resource, Int>): MutableMap<Resource, Int> {
val result = EnumMap<Resource, Int>(Resource::class.java)
result.putAll(other)
return result
}
}

private data class Blueprint(
Expand Down
32 changes: 30 additions & 2 deletions src/main/resources/input19.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,30 @@
Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.
Blueprint 2: 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 12 obsidian.
Blueprint 1: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 3 ore and 9 obsidian.
Blueprint 2: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 12 clay. Each geode robot costs 4 ore and 19 obsidian.
Blueprint 3: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 14 clay. Each geode robot costs 3 ore and 16 obsidian.
Blueprint 4: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 19 obsidian.
Blueprint 5: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 10 clay. Each geode robot costs 2 ore and 10 obsidian.
Blueprint 6: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 16 clay. Each geode robot costs 3 ore and 9 obsidian.
Blueprint 7: 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 8: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 2 ore and 12 obsidian.
Blueprint 9: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 16 clay. Each geode robot costs 3 ore and 20 obsidian.
Blueprint 10: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 7 clay. Each geode robot costs 2 ore and 14 obsidian.
Blueprint 11: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 16 clay. Each geode robot costs 4 ore and 12 obsidian.
Blueprint 12: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 15 clay. Each geode robot costs 2 ore and 15 obsidian.
Blueprint 13: 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 2 ore and 10 obsidian.
Blueprint 14: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 3 ore and 16 obsidian.
Blueprint 15: 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 16: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 4 ore and 15 clay. Each geode robot costs 4 ore and 9 obsidian.
Blueprint 17: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 16 obsidian.
Blueprint 18: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 13 clay. Each geode robot costs 3 ore and 12 obsidian.
Blueprint 19: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 10 clay. Each geode robot costs 4 ore and 10 obsidian.
Blueprint 20: 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 21: 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 2 ore and 7 obsidian.
Blueprint 22: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 3 ore and 14 obsidian.
Blueprint 23: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 2 ore and 10 obsidian.
Blueprint 24: 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 3 ore and 19 obsidian.
Blueprint 25: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 14 clay. Each geode robot costs 3 ore and 14 obsidian.
Blueprint 26: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 13 clay. Each geode robot costs 3 ore and 19 obsidian.
Blueprint 27: 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 28: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 20 clay. Each geode robot costs 2 ore and 19 obsidian.
Blueprint 29: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 2 ore and 15 obsidian.
Blueprint 30: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 19 clay. Each geode robot costs 2 ore and 18 obsidian.

0 comments on commit e53ebee

Please sign in to comment.