aboutsummaryrefslogtreecommitdiffstats
path: root/src/24/solve.py
blob: fc89c841495b4dff24eddcf4efbd4137d7f3aa0f (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import sys
sys.path.append("../common")
import aoc

immunesys = 0
infection = 1
ctype = 0

# parse input from file
def parse_input():
    groups = list()
    for l in aoc.data.split("\n"):
        pl = parse_entry(l)
        if pl != None:
            groups.append(pl)
    return groups

# parse line of input
def parse_entry(line):
    global ctype
    group = None
    if "Immune" in line:
        ctype = immunesys
    elif "Infection" in line:
        ctype = infection
    elif line != "":
        ls = line.split()
        group = dict()
        group["type"] = ctype
        group["units"] = int(ls[0])
        group["unithp"] = int(ls[4])
        group["initiative"] = int(ls[-1])
        group["weak"] = list()
        group["immune"] = list()
        if "(" in line:
            parenthstr = line.split("(")[1].split(")")[0]
            traits = parenthstr.split(";")
            for traitstr in traits:
                info = traitstr.split()
                group[info[0]] = [v.replace(",","") for v in info[2:]]
        dmginfo = line.split("does")[1].split("damage")[0].split()
        group["dmg"] = int(dmginfo[0])
        group["dmgtype"] = dmginfo[1]
    return group

def effective_power(g):
    return g["units"] * g["dmg"]

def damage(attacker, defender):
    dmg = effective_power(attacker)
    dmg *= (0 if attacker["dmgtype"] in defender["immune"] else 1)
    dmg *= (2 if attacker["dmgtype"] in defender["weak"] else 1)
    return dmg

groups = parse_input()

def fight():
    global groups

    lunits = 0

    immalive = len([g for g in groups if g["type"] == immunesys])
    infalive = len([g for g in groups if g["type"] == infection])

    while immalive > 0 and infalive > 0:
        # target selection
        attacked = list()
        attackpairs = list()
        groups = sorted(groups, key = lambda x : (effective_power(x), x["initiative"]), reverse = True)
        for group in groups:
            # choose group of other army, which is not already being attacked and sort appropriately
            enemies = [g for g in groups if g["type"] != group["type"] and g not in attacked]
            if len(enemies) == 0:
                continue
            target = max(enemies, key = lambda x : \
                (damage(group, x), effective_power(x), x["initiative"]))
            if damage(group, target) != 0: # enemies which arent immune
                attacked.append(target)
                attackpairs.append((groups.index(group), groups.index(target)))

        # attacking phase
        attackpairs = sorted(attackpairs, key = lambda x : groups[x[0]]["initiative"], reverse = True)

        for ap in attackpairs:
            attacker = groups[ap[0]]
            attacked = groups[ap[1]]
             # if attacker or defender is dead, no need to attack
            if attacker["units"] > 0 and attacked["units"] > 0:
                dmg = damage(attacker, attacked)
                 # remove whole numbers of units
                attacked["units"] = max(0, attacked["units"] - dmg // attacked["unithp"])

        groups = [g for g in groups if g["units"] > 0]
        immalive = sum([g["units"] for g in groups if g["type"] == immunesys])
        infalive = sum([g["units"] for g in groups if g["type"] == infection])
        units = immalive + infalive
        if units == lunits:
            return units
        lunits = units

    return units

def solve1(args):
    return fight()

def solve2(args):
    global groups

    immunewin = False
    boost = 1
    while not immunewin:
        groups = parse_input()
        for g in groups:
            if g["type"] == immunesys:
                g["dmg"] += boost

        fight()

        immunewin = (sum([0 if g["type"] == immunesys else 1 for g in groups]) == 0)

        boost += 1

    aoc.debug("boost:", boost)

    return sum([v["units"] for v in groups])

aoc.run(solve1, solve2, sols=[10538, 9174])