diff --git a/day22/day.go b/day22/day.go index 311cf10..9c943a0 100644 --- a/day22/day.go +++ b/day22/day.go @@ -1,126 +1,183 @@ package day22 import ( - "fmt" "math" "strconv" "strings" "tools" ) -type Effect struct { - damage int - armor int - mana int - timer int -} - -func (e Effect) copy() *Effect { - return &Effect{e.damage, e.armor, e.mana, e.timer} -} - -var activeEffects map[string]*Effect - -type Spell struct { - cost int - damage int - heal int - applyEffect Effect -} +/* +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 - damage int armor int + damage int } -func (e Entity) copy() *Entity { - return &Entity{e.hitpoints, e.mana, e.damage, e.armor} +var spells = map[string]string{ + "mm": "Magic Missile", + "d": "Drain", + "s": "Shield", + "rc": "Recharge", + "p": "Poison", } +var minWinningMana int -func (e Entity) printStats() { - fmt.Println("HP:", e.hitpoints, "Mana:", e.mana, "Damage:", e.damage, "Armor:", e.armor) -} - -var spells = map[string]Spell{ - "Magic Missile": {53, 4, 0, Effect{}}, - "Drain": {73, 2, 2, Effect{}}, - "Shield": {113, 0, 0, Effect{0, 7, 0, 6}}, - "Poison": {173, 0, 0, Effect{3, 0, 0, 6}}, - "Recharge": {229, 0, 0, Effect{0, 0, 101, 5}}, -} - -func applyEffects(player, boss *Entity) { - for eName, eData := range activeEffects { - switch eName { - case "Shield": - if eData.timer == spells["Shield"].applyEffect.timer { - fmt.Println("Apply Shield") - player.armor += eData.armor - } else if eData.timer == 1 { - fmt.Println("Remove Shield") - player.armor -= eData.armor +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 } - case "Recharge": - fmt.Println("Regen Mana") - player.mana += eData.mana - case "Poison": - fmt.Println("Apply Poison") - boss.hitpoints -= eData.damage + // fmt.Println("Shield's timer is now ", t) } - activeEffects[eName].timer-- - if eData.timer == 0 { - fmt.Println(eName, "wears off.") - delete(activeEffects, eName) + if t == 0 { + // fmt.Println(spells[e], " wears off.") + delete(effects, e) + } else { + effects[e] = t } } + + return player, boss, effects } -func fightRound(player, boss *Entity, spellName string) (outcome int) { - // outcome will be -1 on boss win, 0 on both living and 1 on player win - spell := spells[spellName] - - applyEffects(player, boss) - if player.mana < spell.cost { - return -1 +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 + } } - player.mana -= spell.cost - player.hitpoints += spell.heal - boss.hitpoints -= spell.damage - if _, ok := activeEffects[spellName]; !ok && spell.applyEffect.timer > 0 { - activeEffects[spellName] = spell.applyEffect.copy() + // 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 1 + return player, boss, effects, manaUsed, 1 } - applyEffects(player, boss) + /* + 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 1 + return player, boss, effects, manaUsed, 1 } - player.hitpoints -= tools.Max(1, boss.damage-player.armor) + + 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 -1 + return player, boss, effects, manaUsed, -1 } - return 0 + return player, boss, effects, manaUsed, 0 } -func minManaToTheDeath(player, boss *Entity) int { - manaUsed := 0 - minManaUsed := math.MaxInt64 - - return manaUsed +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{} { - activeEffects = make(map[string]*Effect) player := Entity{50, 500, 0, 0} boss := Entity{0, 0, 0, 0} - bossdata := puzzle.GetInputArray() - for _, line := range bossdata { + bossData := puzzle.GetInputArray() + for _, line := range bossData { parts := strings.Split(line, ": ") switch parts[0] { case "Hit Points": @@ -130,19 +187,30 @@ func Part1(puzzle tools.AoCPuzzle) interface{} { } } - player.printStats() - boss.printStats() - fmt.Println("-----") - for _, s := range []string{"Recharge", "Shield", "Drain", "Poison", "Magic Missile"} { - fightRound(&player, &boss, s) - player.printStats() - boss.printStats() - fmt.Println("-----") - } + effects := make(map[string]int) + minWinningMana = math.MaxInt32 - return 0 + findLowestManaUsed(player, boss, effects, 0, "", false, 0) + return minWinningMana } func Part2(puzzle tools.AoCPuzzle) interface{} { - return 0 + 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 } diff --git a/day25/day.go b/day25/day.go new file mode 100644 index 0000000..a172814 --- /dev/null +++ b/day25/day.go @@ -0,0 +1,36 @@ +package day25 + +import ( + "strconv" + "strings" + "tools" +) + +func Part1(puzzle tools.AoCPuzzle) interface{} { + inputParts := strings.Split(puzzle.GetInputArray()[0], " ") + wantedX, _ := strconv.Atoi(inputParts[18][:len(inputParts[18])-1]) + wantedY, _ := strconv.Atoi(inputParts[16][:len(inputParts[16])-1]) + codeNumber := 20151125 + x := 1 + y := 1 + maxY := 1 + + for { + codeNumber = codeNumber * 252533 % 33554393 + y-- + x++ + if y == 0 { + y = maxY + 1 + maxY = y + x = 1 + } + + if x == wantedX && y == wantedY { + return codeNumber + } + } +} + +func Part2(puzzle tools.AoCPuzzle) interface{} { + return "d25p2 always only checks if you solved everything else" +} diff --git a/inputs/22_test b/inputs/22_test index 02c2d08..cfee43b 100644 --- a/inputs/22_test +++ b/inputs/22_test @@ -1,2 +1,2 @@ -Hit Points: 55 +Hit Points: 14 Damage: 8 \ No newline at end of file diff --git a/main.go b/main.go index 3dfad1d..4bf3198 100644 --- a/main.go +++ b/main.go @@ -25,6 +25,7 @@ import ( "aoc2015/day22" "aoc2015/day23" "aoc2015/day24" + "aoc2015/day25" "flag" "fmt" "os" @@ -66,6 +67,7 @@ func initDayFunctions() { 22: {1: day22.Part1, 2: day22.Part2}, 23: {1: day23.Part1, 2: day23.Part2}, 24: {1: day24.Part1, 2: day24.Part2}, + 25: {1: day25.Part1, 2: day25.Part2}, } }