217 lines
5.4 KiB
Go
217 lines
5.4 KiB
Go
package day22
|
|
|
|
import (
|
|
"math"
|
|
"strconv"
|
|
"strings"
|
|
"tools"
|
|
)
|
|
|
|
/*
|
|
Spells:
|
|
Magic Missile - 53 Mana - 4 Damage
|
|
Drain - 73 Mana - 2 Damage, 2 Heal
|
|
Shield - 113 Mana - 7 Armor for 6 turns
|
|
Poison - 173 Mana - 3 Damage per turn for 6 turns
|
|
Recharge - 229 Mana - 101 Mana per turn for 5 turns
|
|
*/
|
|
|
|
type Entity struct {
|
|
hitpoints int
|
|
mana int
|
|
armor int
|
|
damage int
|
|
}
|
|
|
|
var spells = map[string]string{
|
|
"mm": "Magic Missile",
|
|
"d": "Drain",
|
|
"s": "Shield",
|
|
"rc": "Recharge",
|
|
"p": "Poison",
|
|
}
|
|
var minWinningMana int
|
|
|
|
func applyEffects(player, boss Entity, effects map[string]int) (Entity, Entity, map[string]int) {
|
|
for e, t := range effects {
|
|
t--
|
|
switch e {
|
|
case "rc":
|
|
player.mana += 101
|
|
// fmt.Println("Recharge provides 101 mana; it's timer is now ", t)
|
|
case "p":
|
|
boss.hitpoints -= 3
|
|
// fmt.Println("Poison deals 3 damage; it's timer is now ", t)
|
|
case "s":
|
|
if t == 0 {
|
|
player.armor -= 7
|
|
}
|
|
// fmt.Println("Shield's timer is now ", t)
|
|
}
|
|
if t == 0 {
|
|
// fmt.Println(spells[e], " wears off.")
|
|
delete(effects, e)
|
|
} else {
|
|
effects[e] = t
|
|
}
|
|
}
|
|
|
|
return player, boss, effects
|
|
}
|
|
|
|
func turn(player, boss Entity, effects map[string]int, spell string, hardmode bool) (Entity, Entity, map[string]int, int, int) {
|
|
// return values: -1 player dead; 0 both alive; 1 boss dead
|
|
/*
|
|
fmt.Println("-- Player Turn --")
|
|
fmt.Println("- Player has ", player.hitpoints, "hit points, ", player.armor, " armor, ", player.mana, " mana")
|
|
fmt.Println("- Boss has ", boss.hitpoints, " hit points")
|
|
*/
|
|
if hardmode {
|
|
player.hitpoints -= 1
|
|
if player.hitpoints <= 0 {
|
|
return player, boss, effects, 0, -1
|
|
}
|
|
}
|
|
// Apply effects, if any
|
|
player, boss, effects = applyEffects(player, boss, effects)
|
|
// Player cast spell -- which one?
|
|
if _, ok := effects[spell]; ok {
|
|
// cannot cast already active effect; how to fail?
|
|
// just pretend the player died ...
|
|
return player, boss, effects, 0, -1
|
|
}
|
|
var manaUsed int
|
|
switch spell {
|
|
case "mm":
|
|
// fmt.Println("Player casts Magic Missile; dealing 4 damage.")
|
|
boss.hitpoints -= 4
|
|
manaUsed = 53
|
|
case "d":
|
|
// fmt.Println("Player casts Drain; dealing 2 damage and healing 2 hit points.")
|
|
player.hitpoints += 2
|
|
boss.hitpoints -= 2
|
|
manaUsed = 73
|
|
case "p":
|
|
// fmt.Println("Player casts Poison.")
|
|
effects[spell] = 6
|
|
manaUsed = 173
|
|
case "s":
|
|
// fmt.Println("Player casts Shield.")
|
|
player.armor += 7
|
|
manaUsed = 113
|
|
effects[spell] = 6
|
|
case "rc":
|
|
// fmt.Println("Player casts recharge.")
|
|
manaUsed = 229
|
|
effects[spell] = 5
|
|
}
|
|
player.mana -= manaUsed
|
|
if player.mana <= 0 {
|
|
return player, boss, effects, manaUsed, -1
|
|
}
|
|
|
|
// Check Boss health
|
|
if boss.hitpoints <= 0 {
|
|
return player, boss, effects, manaUsed, 1
|
|
}
|
|
|
|
/*
|
|
fmt.Println("-- Boss Turn --")
|
|
fmt.Println("- Player has ", player.hitpoints, "hit points, ", player.armor, " armor, ", player.mana, " mana")
|
|
fmt.Println("- Boss has ", boss.hitpoints, " hit points")
|
|
*/
|
|
// Apply effects, if any
|
|
player, boss, effects = applyEffects(player, boss, effects)
|
|
// Check Boss health
|
|
if boss.hitpoints <= 0 {
|
|
return player, boss, effects, manaUsed, 1
|
|
}
|
|
|
|
bossDamage := int(math.Max(1.0, float64(boss.damage-player.armor)))
|
|
// fmt.Println("Boss attacks for ", bossDamage, " damage.")
|
|
// Check Player health
|
|
player.hitpoints -= bossDamage
|
|
if player.hitpoints <= 0 {
|
|
return player, boss, effects, manaUsed, -1
|
|
}
|
|
|
|
return player, boss, effects, manaUsed, 0
|
|
}
|
|
|
|
func findLowestManaUsed(player, boss Entity, effects map[string]int, manaUsed int, spellsUsed string, hardmode bool, depth int) (int, string) {
|
|
minManaUsed := math.MaxInt32
|
|
var minSpellsUsed string
|
|
for s := range spells {
|
|
var thisManaUse, state int
|
|
thisPlayer := player
|
|
thisBoss := boss
|
|
thisEffects := make(map[string]int)
|
|
thisSpells := spellsUsed + "," + s
|
|
for e, t := range effects {
|
|
thisEffects[e] = t
|
|
}
|
|
thisPlayer, thisBoss, thisEffects, thisManaUse, state = turn(thisPlayer, thisBoss, thisEffects, s, hardmode)
|
|
switch state {
|
|
case -1:
|
|
continue
|
|
case 0:
|
|
if thisManaUse+manaUsed < minWinningMana {
|
|
thisManaUse, thisSpells = findLowestManaUsed(thisPlayer, thisBoss, thisEffects, thisManaUse+manaUsed, thisSpells, hardmode, depth+1)
|
|
} else {
|
|
continue
|
|
}
|
|
case 1:
|
|
if manaUsed+thisManaUse < minWinningMana {
|
|
minWinningMana = manaUsed + thisManaUse
|
|
}
|
|
}
|
|
if thisManaUse < minManaUsed {
|
|
minManaUsed = thisManaUse
|
|
minSpellsUsed = thisSpells
|
|
}
|
|
}
|
|
return minManaUsed + manaUsed, minSpellsUsed
|
|
}
|
|
|
|
func Part1(puzzle tools.AoCPuzzle) interface{} {
|
|
player := Entity{50, 500, 0, 0}
|
|
boss := Entity{0, 0, 0, 0}
|
|
bossData := puzzle.GetInputArray()
|
|
for _, line := range bossData {
|
|
parts := strings.Split(line, ": ")
|
|
switch parts[0] {
|
|
case "Hit Points":
|
|
boss.hitpoints, _ = strconv.Atoi(parts[1])
|
|
case "Damage":
|
|
boss.damage, _ = strconv.Atoi(parts[1])
|
|
}
|
|
}
|
|
|
|
effects := make(map[string]int)
|
|
minWinningMana = math.MaxInt32
|
|
|
|
findLowestManaUsed(player, boss, effects, 0, "", false, 0)
|
|
return minWinningMana
|
|
}
|
|
|
|
func Part2(puzzle tools.AoCPuzzle) interface{} {
|
|
player := Entity{50, 500, 0, 0}
|
|
boss := Entity{0, 0, 0, 0}
|
|
bossData := puzzle.GetInputArray()
|
|
for _, line := range bossData {
|
|
parts := strings.Split(line, ": ")
|
|
switch parts[0] {
|
|
case "Hit Points":
|
|
boss.hitpoints, _ = strconv.Atoi(parts[1])
|
|
case "Damage":
|
|
boss.damage, _ = strconv.Atoi(parts[1])
|
|
}
|
|
}
|
|
|
|
effects := make(map[string]int)
|
|
minWinningMana = math.MaxInt32
|
|
|
|
findLowestManaUsed(player, boss, effects, 0, "", true, 0)
|
|
return minWinningMana
|
|
}
|