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 }