Skip to content

Commit a03064b

Browse files
committed
Solution 2015-21 (RPG Simulator 20XX)
1 parent e4f8cb8 commit a03064b

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package de.ronny_h.aoc.year2015.day21
2+
3+
import de.ronny_h.aoc.AdventOfCode
4+
import kotlin.math.max
5+
6+
fun main() = RPGSimulator20XX().run(111, 188)
7+
8+
class RPGSimulator20XX : AdventOfCode<Int>(2015, 21) {
9+
10+
// must buy exactly one
11+
private val weapons = listOf(
12+
Item("Dagger", 8, 4, 0),
13+
Item("Shortsword", 10, 5, 0),
14+
Item("Warhammer", 25, 6, 0),
15+
Item("Longsword", 40, 7, 0),
16+
Item("Greataxe", 74, 8, 0),
17+
)
18+
19+
// optional: 0-1
20+
private val armors = listOf(
21+
Item("No Armor", 0, 0, 0),
22+
Item("Leather", 13, 0, 1),
23+
Item("Chainmail", 31, 0, 2),
24+
Item("Splintmail", 53, 0, 3),
25+
Item("Bandedmail", 75, 0, 4),
26+
Item("Platemail", 102, 0, 5),
27+
)
28+
29+
// optional: 0-2
30+
private val rings = listOf(
31+
Item("No Ring 1", 0, 0, 0),
32+
Item("No Ring 2", 0, 0, 0),
33+
Item("Damage +1", 25, 1, 0),
34+
Item("Damage +2", 50, 2, 0),
35+
Item("Damage +3", 100, 3, 0),
36+
Item("Defense +1", 20, 0, 1),
37+
Item("Defense +2", 40, 0, 2),
38+
Item("Defense +3", 80, 0, 3),
39+
)
40+
41+
override fun part1(input: List<String>): Int {
42+
val bossStats = input.parseBossStats()
43+
44+
var minCost = Int.MAX_VALUE
45+
var items = emptyList<Item>()
46+
forAllPossibleItemCombinations { boughtItems ->
47+
val (cost, playerStats) = boughtItems.calcPlayerStats()
48+
if (playerWins(playerStats, bossStats) && cost < minCost) {
49+
minCost = cost
50+
items = boughtItems
51+
}
52+
}
53+
println("bought items: $items, cost: $minCost")
54+
return minCost
55+
}
56+
57+
private fun List<Item>.calcPlayerStats(): Pair<Int, Stats> {
58+
val playerStats = Stats(100, sumOf(Item::damage), sumOf(Item::armor))
59+
return Pair(sumOf(Item::cost), playerStats)
60+
}
61+
62+
private fun forAllPossibleItemCombinations(block: (List<Item>) -> Unit) {
63+
for (weapon in weapons) {
64+
for (armor in armors) {
65+
for (ring1 in rings) {
66+
for (ring2 in (rings - ring1)) {
67+
block(listOf(weapon, armor, ring1, ring2))
68+
}
69+
}
70+
}
71+
}
72+
}
73+
74+
override fun part2(input: List<String>): Int {
75+
val bossStats = input.parseBossStats()
76+
77+
var maxCost = 0
78+
var items = emptyList<Item>()
79+
forAllPossibleItemCombinations { boughtItems ->
80+
val (cost, playerStats) = boughtItems.calcPlayerStats()
81+
if (!playerWins(playerStats, bossStats) && cost > maxCost) {
82+
maxCost = cost
83+
items = boughtItems
84+
}
85+
}
86+
println("bought items: $items, cost: $maxCost")
87+
return maxCost
88+
}
89+
}
90+
91+
data class Item(val name: String, val cost: Int, val damage: Int, val armor: Int)
92+
93+
data class Stats(val hitPoints: Int, val damage: Int, val armor: Int)
94+
95+
fun List<String>.parseBossStats() = Stats(
96+
hitPoints = get(0).substringAfter("Hit Points: ").toInt(),
97+
damage = get(1).substringAfter("Damage: ").toInt(),
98+
armor = get(2).substringAfter("Armor: ").toInt(),
99+
)
100+
101+
fun playerWins(player: Stats, boss: Stats): Boolean {
102+
var playerHitPoints = player.hitPoints
103+
var bossHitPoins = boss.hitPoints
104+
while (playerHitPoints > 0 && bossHitPoins > 0) {
105+
bossHitPoins -= max(player.damage - boss.armor, 1)
106+
playerHitPoints -= max(boss.damage - player.armor, 1)
107+
}
108+
return bossHitPoins <= 0
109+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package de.ronny_h.aoc.year2015.day21
2+
3+
import io.kotest.core.spec.style.StringSpec
4+
import io.kotest.matchers.shouldBe
5+
6+
class RPGSimulator20XXTest : StringSpec({
7+
8+
"can parse the input" {
9+
val input = listOf(
10+
"Hit Points: 100",
11+
"Damage: 7",
12+
"Armor: 3",
13+
)
14+
input.parseBossStats() shouldBe Stats(100, 7, 3)
15+
}
16+
17+
"the player wins in the given example" {
18+
val bossStats = Stats(hitPoints = 12, damage = 7, armor = 2)
19+
val playerStats = Stats(hitPoints = 8, damage = 5, armor = 5)
20+
playerWins(playerStats, bossStats) shouldBe true
21+
}
22+
23+
"part 1: the min cost to win - a Dagger for 8 is enough" {
24+
val input = listOf(
25+
"Hit Points: 100",
26+
"Damage: 1",
27+
"Armor: 1",
28+
)
29+
RPGSimulator20XX().part1(input) shouldBe 8
30+
}
31+
32+
"part 2: the max cost to loose - a Dagger and a Damage +1 ring" {
33+
val input = listOf(
34+
"Hit Points: 100",
35+
"Damage: 3",
36+
"Armor: 3",
37+
)
38+
RPGSimulator20XX().part2(input) shouldBe 33
39+
}
40+
})

0 commit comments

Comments
 (0)