aoc2015/day22/day.go
2021-10-24 12:32:52 +02:00

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
}