solve.py (4032B)
1import sys 2sys.path.append("../common") 3import aoc 4 5immunesys = 0 6infection = 1 7ctype = 0 8 9# parse input from file 10def parse_input(): 11 groups = list() 12 for l in aoc.data.split("\n"): 13 pl = parse_entry(l) 14 if pl != None: 15 groups.append(pl) 16 return groups 17 18# parse line of input 19def parse_entry(line): 20 global ctype 21 group = None 22 if "Immune" in line: 23 ctype = immunesys 24 elif "Infection" in line: 25 ctype = infection 26 elif line != "": 27 ls = line.split() 28 group = dict() 29 group["type"] = ctype 30 group["units"] = int(ls[0]) 31 group["unithp"] = int(ls[4]) 32 group["initiative"] = int(ls[-1]) 33 group["weak"] = list() 34 group["immune"] = list() 35 if "(" in line: 36 parenthstr = line.split("(")[1].split(")")[0] 37 traits = parenthstr.split(";") 38 for traitstr in traits: 39 info = traitstr.split() 40 group[info[0]] = [v.replace(",","") for v in info[2:]] 41 dmginfo = line.split("does")[1].split("damage")[0].split() 42 group["dmg"] = int(dmginfo[0]) 43 group["dmgtype"] = dmginfo[1] 44 return group 45 46def effective_power(g): 47 return g["units"] * g["dmg"] 48 49def damage(attacker, defender): 50 dmg = effective_power(attacker) 51 dmg *= (0 if attacker["dmgtype"] in defender["immune"] else 1) 52 dmg *= (2 if attacker["dmgtype"] in defender["weak"] else 1) 53 return dmg 54 55groups = parse_input() 56 57def fight(): 58 global groups 59 60 lunits = 0 61 62 immalive = len([g for g in groups if g["type"] == immunesys]) 63 infalive = len([g for g in groups if g["type"] == infection]) 64 65 while immalive > 0 and infalive > 0: 66 # target selection 67 attacked = list() 68 attackpairs = list() 69 groups = sorted(groups, key = lambda x : (effective_power(x), x["initiative"]), reverse = True) 70 for group in groups: 71 # choose group of other army, which is not already being attacked and sort appropriately 72 enemies = [g for g in groups if g["type"] != group["type"] and g not in attacked] 73 if len(enemies) == 0: 74 continue 75 target = max(enemies, key = lambda x : \ 76 (damage(group, x), effective_power(x), x["initiative"])) 77 if damage(group, target) != 0: # enemies which arent immune 78 attacked.append(target) 79 attackpairs.append((groups.index(group), groups.index(target))) 80 81 # attacking phase 82 attackpairs = sorted(attackpairs, key = lambda x : groups[x[0]]["initiative"], reverse = True) 83 84 for ap in attackpairs: 85 attacker = groups[ap[0]] 86 attacked = groups[ap[1]] 87 # if attacker or defender is dead, no need to attack 88 if attacker["units"] > 0 and attacked["units"] > 0: 89 dmg = damage(attacker, attacked) 90 # remove whole numbers of units 91 attacked["units"] = max(0, attacked["units"] - dmg // attacked["unithp"]) 92 93 groups = [g for g in groups if g["units"] > 0] 94 immalive = sum([g["units"] for g in groups if g["type"] == immunesys]) 95 infalive = sum([g["units"] for g in groups if g["type"] == infection]) 96 units = immalive + infalive 97 if units == lunits: 98 return units 99 lunits = units 100 101 return units 102 103def solve1(args): 104 return fight() 105 106def solve2(args): 107 global groups 108 109 immunewin = False 110 boost = 1 111 while not immunewin: 112 groups = parse_input() 113 for g in groups: 114 if g["type"] == immunesys: 115 g["dmg"] += boost 116 117 fight() 118 119 immunewin = (sum([0 if g["type"] == immunesys else 1 for g in groups]) == 0) 120 121 boost += 1 122 123 aoc.debug("boost:", boost) 124 125 return sum([v["units"] for v in groups]) 126 127aoc.run(solve1, solve2, sols=[10538, 9174])