summaryrefslogtreecommitdiff
path: root/21/main.py
blob: 2c75798f76b602080d1f04cc8a57425ce34e8708 (plain)
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/python

from copy import copy



class Entity:
    pass

class Item:
    pass

class Shop:
    pass



# Annoying boilerplate to initialize the shop with the items
# given in the text. This is as compact as I could make it.
def init_shop():
    table_weapons = [
        "Dagger        8     4       0",
        "Shortsword   10     5       0",
        "Warhammer    25     6       0",
        "Longsword    40     7       0",
        "Greataxe     74     8       0"
    ]
    table_armour = [
        "Leather      13     0       1",
        "Chainmail    31     0       2",
        "Splintmail   53     0       3",
        "Bandedmail   75     0       4",
        "Platemail   102     0       5",
        "NONE          0     0       0"  # Because it's optional
    ]
    table_rings = [
        "Damage+1     25     1       0",
        "Damage+2     50     2       0",
        "Damage+3    100     3       0",
        "Defense+1    20     0       1",
        "Defense+2    40     0       2",
        "Defense+3    80     0       3",
        "NONE          0     0       0"  # Because it's optional
    ]
    tables = [
        (table_weapons, "weapons"),
        (table_armour, "armour"),
        (table_rings, "rings")
    ]

    shop = Shop()
    shop.weapons = []
    shop.armour  = []
    shop.rings   = []

    for table, attr in tables:
        for row in table:
            fields = row.split()

            item = Item()
            item.name   = fields[0]
            item.cost   = int(fields[1])
            item.damage = int(fields[2])
            item.armour = int(fields[3])

            # Sometimes I love you, Python. But usually I don't.
            shop.__dict__[attr].append(item)

    return shop



def will_win(player, boss):
    turn = 0
    while boss.health > 0 and player.health > 0:
        # Player's turn to attack
        if turn % 2 == 0:
            boss.health   -= max(1, player.damage -   boss.armour)
        # Boss' turn to attack
        else: # turn % 2 == 1
            player.health -= max(1,   boss.damage - player.armour)
        turn += 1

    # Did the player win?
    return boss.health <= 0



def solve_partn(partn, player_stats, boss_stats):
    shop = init_shop()

    min_cost = 666
    max_cost =   0
    for w in shop.weapons:
        for a in shop.armour:
            for r1 in shop.rings:
                for r2 in shop.rings:
                    # No more than one of each item
                    if r1 == r2:
                        continue

                    boss   = copy(  boss_stats)
                    player = copy(player_stats)

                    cost = w.cost + a.cost + r1.cost + r2.cost
                    player.damage +=  w.damage
                    player.damage += r1.damage
                    player.damage += r2.damage
                    player.armour +=  a.armour
                    player.armour += r1.armour
                    player.armour += r2.armour

                    # Part 1: find lowest cost where player wins
                    if will_win(player, boss):
                        if cost < min_cost:
                            min_cost = cost
                    # Part 2: find highest cost where player loses
                    else:
                        if cost > max_cost:
                            max_cost = cost

    if partn == 1:
        return min_cost
    else: # partn == 2
        return max_cost



def main():
    # Set up player's stats given in puzzle text
    player = Entity()
    player.health = 100
    player.damage =   0
    player.armour =   0

    # Read boss' stats from input text file
    boss = Entity()
    with open("input.txt", "r") as f:
        lines = f.read().splitlines()
        for i in range(len(lines)): # len(lines) = 3
            words = lines[i].split()
            if i == 0:
                boss.health = int(words[-1])
            if i == 1:
                boss.damage = int(words[-1])
            if i == 2:
                boss.armour = int(words[-1])

    print("Part 1 solution:", solve_partn(1, player, boss)) # 121 for me
    print("Part 2 solution:", solve_partn(2, player, boss)) # 201 for me



if __name__ == "__main__":
    main()