back home

Day - 22

Part 1 - Code

      
# template
import os
import copy

YEAR = 2015
DAY = 22

def get_advent_folder_name(year, day):
    base = os.environ["ADVENT_HOME"]
    if not base:
        raise "ADVENT_HOME folder not set"
    
    return f"{base}/{year}/data/day{day}"

def get_data(name="data.txt"):
  data = []
  filename = get_advent_folder_name(YEAR, DAY)
  path = f"{filename}/{name}"
  try:
      file = open(path, 'r')
      return file
  except IOError:
      print(f"Could not fetch file {file} ")
      return None

boss = {"hp": 51, "dmg": 9}

class Effect:
    def __init__(self, name, ttl, effect):
        self.ttl = ttl
        self.name = name
        self.effect = effect

class Spell:
    def __init__(self, name, mana_cost, dmg, hp, effect):
        self.name = name
        self.mana_cost = mana_cost
        self.dmg = dmg
        self.hp = hp
        self.effect = effect

boss = {"hp": 51, "dmg": 9}
player = {"hp": 50, "mana": 500, "armor": 0}

magic_missile = Spell("magic_missile", 53, 4, 0, None)
drain = Spell("drain", 73, 2, 2, None)
shield = Spell("shield", 113, 0, 0, Effect("shield", 6, {"armor": 7}))
poison = Spell("poison", 173, 0, 0, Effect("poison", 6, {"dmg": 3}))
recharge = Spell("recharge", 229, 0, 0, Effect("recharge", 5, {"mana": 101}))

class Player:
    def __init__(self, hp, mana):
        self.hp = hp
        self.mana = mana
        self.total_mana_spent = 0
        self.active_effects = dict()
        self.spells = [magic_missile, drain, shield, poison, recharge]
        self.spell_history = []
    def __copy__(self):
        other = Player(self.hp, self.mana)
        other.active_effects = copy.deepcopy(self.active_effects)
        other.total_mana_spent = self.total_mana_spent
        other.spell_history = [i for i in self.spell_history]
        return other

    def apply_effects(self):
        if recharge.name in self.active_effects:
            self.mana += recharge.effect.effect["mana"]
    
    def update_effects(self):
        for i in self.active_effects:
            self.active_effects[i].ttl -= 1
        self.active_effects = {k:v for k,v in self.active_effects.items() if v.ttl > 0}
    
    def can_use_spell(self, spell):
        if self.mana >= spell.mana_cost:
            if spell.name in self.active_effects:
                return self.active_effects[spell.name].ttl <= 1
            return True
        return False
                 
    def get_armor(self):
        if shield.name in self.active_effects:
            return shield.effect.effect["armor"]
        return 0

    def get_poison(self):
        if poison.name in self.active_effects:
            return 3
        return 0

    def get_available_spells(self):
        return filter(self.can_use_spell, self.spells)
        

class Boss:
    def __init__(self, hp, dmg):
        self.hp = hp
        self.dmg = dmg


def handle_player_turn(player, boss, spell, hard_mode):
    p = copy.deepcopy(player)
    b = copy.deepcopy(boss)

    if hard_mode:
        p.hp -= 1
    
    if p.hp <= 0:
        return p,b
    
    p.total_mana_spent += spell.mana_cost
    b.hp -= p.get_poison()
    # print("p.get_poison()", p.get_poison())

    if b.hp <= 0:
        return p,b
    p.spell_history.append(spell.name)
    p.update_effects()

    p.mana -= spell.mana_cost
    p.hp += spell.hp
    b.hp -= spell.dmg

    if b.hp <= 0:
        return p,b

    if spell.effect:
        p.active_effects[spell.name] = spell.effect


    return p,b
def handle_boss_turn(player, boss):
    p = copy.deepcopy(player)
    b = copy.deepcopy(boss)  
    # print("p.get_poison()", p.get_poison())
    b.hp -= p.get_poison()
    if b.hp <= 0:
        return p,b
    boss_dmg_dealt = max(1,b.dmg - p.get_armor())
    p.hp -= boss_dmg_dealt

    p.update_effects()
    return p,b

wins_mana_spent = []

def duel(player, boss, turn = 0, hard_mode = False):
    # if turn > 1000: return
    if player.hp <= 0 or (player.mana <= 0 and recharge.name not in player.active_effects) or (len(wins_mana_spent) > 0 and min(wins_mana_spent) < player.total_mana_spent):
        # print("player is dead at turn", turn-1)
        return
    if boss.hp <= 0: 
        wins_mana_spent.append(player.total_mana_spent)
        # print("boss is dead at turn", turn-1, "from ", player.spell_history, "with total mana spent", player.total_mana_spent)
        return
    p = player
    b = boss
    p.apply_effects()
    # print("handling turn")
    if turn % 2 == 0:
        # player turn
        # print("available spells at turn ", turn, list(map(lambda x: x.name, p.get_available_spells())))
        for spell in p.get_available_spells():
            # print("using spell ", spell.name, "on turn ", turn)
            pp, bb = handle_player_turn(p, b, spell, hard_mode)
            # print(bb.hp)
            if boss.hp <= 0: 
                wins_mana_spent.append(player.total_mana_spent)
                # print("boss is dead at turn", turn-1, "from ", player.spell_history)
                return
            duel(pp,bb,turn+1)
        
    else:
        # boss turn
        pp,bb = handle_boss_turn(p, b)
        duel(pp,bb,turn+1)


def main():
    # duel(Player(10,250), Boss(14, 8))
    duel(Player(50,500), Boss(51, 9))
    print(min(wins_mana_spent))
    wins_mana_spent.clear()
    duel(Player(50,500), Boss(51, 9), True)
    print(min(wins_mana_spent))
    pass

if __name__ == "__main__":
    main()