summaryrefslogtreecommitdiff
path: root/22/main.py
blob: 9b7bb0b56b7ac9233aef2f9885568d5ae3f31cc9 (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
#!/usr/bin/python

from copy   import copy
from random import randrange



class Entity:
    pass



def solve_partn(partn, player_stats, boss_stats):
    # We assign numbers 0-4 to the spells
    mana_costs = [53, 73, 113, 173, 229]

    least_mana = 666666
    # 100K attempts turns out to be enough to reliably find the solution
    for i in range(100000):
        boss   = copy(  boss_stats)
        player = copy(player_stats)

        spent_mana = 0
        # Timers for "Shield", "Poison" and "Recharge", respectively
        timers = [0, 0, 0]

        turn = 0
        while boss.health > 0 and player.health > 0:
            # Already spent too much; this can't be the solution, abort
            if spent_mana > least_mana:
                break

            # Part 2: hard mode: player loses 1 HP at start of their turn
            if partn == 2 and turn % 2 == 0:
                player.health -= 1
                if player.health <= 0:
                    break

            # Apply status effects from past spells
            # "Shield"
            if timers[0] > 0:
                timers[0] -= 1
                if timers[0] == 0:
                    player.armour -= 7
            # "Poison"
            if timers[1] > 0:
                boss.health -= 3
                # Did the boss die before any actions were taken?
                if boss.health <= 0:
                    break
                timers[1] -= 1
            # "Recharge"
            if timers[2] > 0:
                player.mana += 101
                timers[2] -= 1

            # Player's turn to cast a spell
            if turn % 2 == 0:
                # Can we afford to cast any spell? If not, we've lost; abort
                if player.mana < min(mana_costs):
                    break

                # Randomly select which spell to cast. Sounds inefficient, but
                # works fine: we want to minimize the total mana spent, so we're
                # looking for short sequences, which we're very unlikely to miss.
                s = -1
                while s == -1:
                    s = randrange(0, 5)
                    # Can we afford this spell?
                    if mana_costs[s] > player.mana:
                        s = -1
                    # Isn't this spell already active?
                    if s in [2, 3, 4] and timers[s - 2] > 0:
                        s = -1

                player.mana -= mana_costs[s]
                spent_mana  += mana_costs[s]

                # Apply effect of the spell we've selected
                # "Magic Missile"
                if s == 0:
                    boss.health -= 4
                # "Drain"
                if s == 1:
                    boss.health   -= 2
                    player.health += 2
                # "Shield"
                if s == 2:
                    timers[0] = 6
                    player.armour += 7
                # "Poison"
                if s == 3:
                    timers[1] = 6
                # "Recharge"
                if s == 4:
                    timers[2] = 5

            # Boss' turn to attack
            else: # turn % 2 == 1
                player.health -= max(1, boss.damage - player.armour)

            turn += 1

        # Did we stop because the player won?
        if boss.health <= 0:
            if spent_mana < least_mana:
                least_mana = spent_mana

    return least_mana



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

    # 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) = 2
            words = lines[i].split()
            if i == 0:
                boss.health = int(words[-1])
            if i == 1:
                boss.damage = int(words[-1])

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



if __name__ == "__main__":
    main()