|
| 1 | +# -------------------------------- Input data -------------------------------- # |
| 2 | +import os, itertools, random |
| 3 | + |
| 4 | +test_data = {} |
| 5 | + |
| 6 | +test = 1 |
| 7 | +test_data[test] = { |
| 8 | + "input": """""", |
| 9 | + "expected": ["Unknown", "Unknown"], |
| 10 | +} |
| 11 | + |
| 12 | +test += 1 |
| 13 | +test_data[test] = { |
| 14 | + "input": """""", |
| 15 | + "expected": ["Unknown", "Unknown"], |
| 16 | +} |
| 17 | + |
| 18 | +test = "real" |
| 19 | +test_data[test] = { |
| 20 | + "input": "", |
| 21 | + "expected": ["900", "1216"], |
| 22 | +} |
| 23 | + |
| 24 | +# -------------------------------- Control program execution -------------------------------- # |
| 25 | + |
| 26 | +case_to_test = 1 |
| 27 | +part_to_test = 2 |
| 28 | +verbose_level = 1 |
| 29 | + |
| 30 | +# -------------------------------- Initialize some variables -------------------------------- # |
| 31 | + |
| 32 | +puzzle_input = test_data[case_to_test]["input"] |
| 33 | +puzzle_expected_result = test_data[case_to_test]["expected"][part_to_test - 1] |
| 34 | +puzzle_actual_result = "Unknown" |
| 35 | + |
| 36 | + |
| 37 | +# -------------------------------- Actual code execution -------------------------------- # |
| 38 | + |
| 39 | + |
| 40 | +spells = { |
| 41 | + # Cost, Duration, Damage, Heal, Armor, Mana |
| 42 | + "M": [53, 1, 4, 0, 0, 0], |
| 43 | + "D": [73, 1, 2, 2, 0, 0], |
| 44 | + "S": [113, 6, 0, 0, 7, 0], |
| 45 | + "P": [173, 6, 3, 0, 0, 0], |
| 46 | + "R": [229, 5, 0, 0, 0, 101], |
| 47 | +} |
| 48 | + |
| 49 | +# Mana, HP, Armor |
| 50 | +init_player_stats = [500, 50, 0] |
| 51 | +# HP, Damage |
| 52 | +init_boss_stats = [51, 9] |
| 53 | +init_counters = {"S": 0, "P": 0, "R": 0} |
| 54 | + |
| 55 | +# Maximum mana used - initially 10 ** 6, reduced with manual tests / strategy |
| 56 | +min_mana_used = 1300 |
| 57 | + |
| 58 | + |
| 59 | +def apply_effects(counters, player_stats, boss_stats): |
| 60 | + global spells |
| 61 | + |
| 62 | + for effect in counters: |
| 63 | + if counters[effect] == 0: |
| 64 | + if effect == "S": |
| 65 | + player_stats[2] = 0 |
| 66 | + continue |
| 67 | + else: |
| 68 | + if effect == "S": |
| 69 | + player_stats[2] = spells[effect][4] |
| 70 | + else: |
| 71 | + boss_stats[0] -= spells[effect][2] |
| 72 | + player_stats[0] += spells[effect][5] |
| 73 | + |
| 74 | + counters[effect] -= 1 |
| 75 | + |
| 76 | + return [counters, player_stats, boss_stats] |
| 77 | + |
| 78 | + |
| 79 | +if part_to_test == 1: |
| 80 | + count_strategies = 5 ** 10 |
| 81 | + for strategy in itertools.product(spells.keys(), repeat=10): |
| 82 | + count_strategies -= 1 |
| 83 | + print( |
| 84 | + "Min mana :", |
| 85 | + min_mana_used, |
| 86 | + "###### Strategy #", |
| 87 | + count_strategies, |
| 88 | + ":", |
| 89 | + strategy, |
| 90 | + ) |
| 91 | + if "S" not in strategy[0:5] or "R" not in strategy[0:5]: |
| 92 | + continue |
| 93 | + counters = init_counters.copy() |
| 94 | + player_stats = init_player_stats.copy() |
| 95 | + boss_stats = init_boss_stats.copy() |
| 96 | + mana_used = 0 |
| 97 | + |
| 98 | + for player_action in strategy: |
| 99 | + # Player turn |
| 100 | + if part_to_test == 2: |
| 101 | + player_stats[1] -= 1 |
| 102 | + if player_stats[1] <= 0: |
| 103 | + if verbose_level >= 2: |
| 104 | + print("Boss wins") |
| 105 | + break |
| 106 | + |
| 107 | + # Apply effects |
| 108 | + counters, player_stats, boss_stats = apply_effects( |
| 109 | + counters, player_stats, boss_stats |
| 110 | + ) |
| 111 | + if verbose_level >= 2: |
| 112 | + print("### Player turn - Player casts", player_action) |
| 113 | + print(counters, player_stats, boss_stats) |
| 114 | + |
| 115 | + # Apply player move |
| 116 | + if spells[player_action][0] > player_stats[0]: |
| 117 | + if verbose_level >= 2: |
| 118 | + print("Aborting: not enough mana") |
| 119 | + break |
| 120 | + if spells[player_action][1] == 1: |
| 121 | + player_stats[1] += spells[player_action][3] |
| 122 | + boss_stats[0] -= spells[player_action][2] |
| 123 | + else: |
| 124 | + if counters[player_action] != 0: |
| 125 | + if verbose_level >= 2: |
| 126 | + print("Aborting: reused " + player_action) |
| 127 | + break |
| 128 | + else: |
| 129 | + counters[player_action] = spells[player_action][1] |
| 130 | + # Mana usage |
| 131 | + player_stats[0] -= spells[player_action][0] |
| 132 | + mana_used += spells[player_action][0] |
| 133 | + if verbose_level >= 2: |
| 134 | + print(counters, player_stats, boss_stats) |
| 135 | + |
| 136 | + if boss_stats[0] <= 0: |
| 137 | + if verbose_level >= 2: |
| 138 | + print("Player wins with", mana_used, "mana used") |
| 139 | + min_mana_used = min(min_mana_used, mana_used) |
| 140 | + break |
| 141 | + if mana_used > min_mana_used: |
| 142 | + print("Aborting: too much mana used") |
| 143 | + break |
| 144 | + |
| 145 | + # Boss turn |
| 146 | + # Apply effects |
| 147 | + counters, player_stats, boss_stats = apply_effects( |
| 148 | + counters, player_stats, boss_stats |
| 149 | + ) |
| 150 | + if verbose_level >= 2: |
| 151 | + print("### Boss turn") |
| 152 | + print(counters, player_stats, boss_stats) |
| 153 | + |
| 154 | + player_stats[1] -= boss_stats[1] - player_stats[2] |
| 155 | + if verbose_level >= 2: |
| 156 | + print(counters, player_stats, boss_stats) |
| 157 | + |
| 158 | + if player_stats[1] <= 0: |
| 159 | + if verbose_level >= 2: |
| 160 | + print("Boss wins") |
| 161 | + break |
| 162 | +else: |
| 163 | + max_moves = 15 |
| 164 | + pruned_strategies = [] |
| 165 | + count_strategies = 5 ** max_moves |
| 166 | + |
| 167 | + # This code is not very efficient, becuase it changes the last spells first (and those are likely not to be used because we finish the combat or our mana before that)... |
| 168 | + |
| 169 | + for strategy in itertools.product(spells.keys(), repeat=max_moves): |
| 170 | + count_strategies -= 1 |
| 171 | + if "S" not in strategy[0:4] or "R" not in strategy[0:5]: |
| 172 | + if verbose_level >= 2: |
| 173 | + print(" Missing Shield or Recharge") |
| 174 | + continue |
| 175 | + if any( |
| 176 | + [True for i in range(1, max_moves) if strategy[0:i] in pruned_strategies] |
| 177 | + ): |
| 178 | + print(" Pruned") |
| 179 | + continue |
| 180 | + |
| 181 | + if verbose_level >= 2: |
| 182 | + print( |
| 183 | + "Min mana :", |
| 184 | + min_mana_used, |
| 185 | + "###### Strategy #", |
| 186 | + count_strategies, |
| 187 | + "- pruned: ", |
| 188 | + len(pruned_strategies), |
| 189 | + "-", |
| 190 | + strategy, |
| 191 | + ) |
| 192 | + shield_left = 0 |
| 193 | + poison_left = 0 |
| 194 | + recharge_left = 0 |
| 195 | + player_hp = 50 |
| 196 | + player_mana = 500 |
| 197 | + player_armor = 0 |
| 198 | + mana_used = 0 |
| 199 | + boss_hp = 51 |
| 200 | + boss_dmg = 9 |
| 201 | + |
| 202 | + for player_action in strategy: |
| 203 | + |
| 204 | + # Player turn |
| 205 | + player_hp -= 1 |
| 206 | + if player_hp <= 0: |
| 207 | + if verbose_level >= 2: |
| 208 | + print("Boss wins") |
| 209 | + # pruned_strategies.append(tuple(actions_done)) |
| 210 | + break |
| 211 | + |
| 212 | + # actions_done += tuple(player_action) |
| 213 | + |
| 214 | + # Apply effects |
| 215 | + if shield_left > 0: |
| 216 | + player_armor = 7 |
| 217 | + shield_left -= 1 |
| 218 | + else: |
| 219 | + player_armor = 0 |
| 220 | + if poison_left > 0: |
| 221 | + boss_hp -= 3 |
| 222 | + poison_left -= 0 |
| 223 | + if recharge_left: |
| 224 | + player_mana += 101 |
| 225 | + recharge_left -= 1 |
| 226 | + |
| 227 | + # Apply player move |
| 228 | + if spells[player_action][0] > player_mana: |
| 229 | + if verbose_level >= 2: |
| 230 | + print("Aborting: not enough mana") |
| 231 | + # pruned_strategies.append(actions_done) |
| 232 | + break |
| 233 | + # Missile |
| 234 | + if player_action == "M": |
| 235 | + player_mana -= 53 |
| 236 | + mana_used += 53 |
| 237 | + boss_hp -= 4 |
| 238 | + # Drain |
| 239 | + elif player_action == "D": |
| 240 | + player_mana -= 73 |
| 241 | + mana_used += 73 |
| 242 | + boss_hp -= 2 |
| 243 | + player_hp += 2 |
| 244 | + # Shield |
| 245 | + elif player_action == "S": |
| 246 | + if shield_left != 0: |
| 247 | + if verbose_level >= 2: |
| 248 | + print("Aborting: reused " + player_action) |
| 249 | + # pruned_strategies.append(actions_done) |
| 250 | + break |
| 251 | + else: |
| 252 | + shield_left = 6 |
| 253 | + # Poison |
| 254 | + elif player_action == "P": |
| 255 | + if poison_left != 0: |
| 256 | + if verbose_level >= 2: |
| 257 | + print("Aborting: reused " + player_action) |
| 258 | + # pruned_strategies.append(actions_done) |
| 259 | + break |
| 260 | + else: |
| 261 | + poison_left = 6 |
| 262 | + # Recharge |
| 263 | + elif player_action == "R": |
| 264 | + if recharge_left != 0: |
| 265 | + if verbose_level >= 2: |
| 266 | + print("Aborting: reused " + player_action) |
| 267 | + # pruned_strategies.append(actions_done) |
| 268 | + break |
| 269 | + else: |
| 270 | + shield_left = 5 |
| 271 | + |
| 272 | + if boss_hp <= 0: |
| 273 | + if verbose_level >= 2: |
| 274 | + print("Player wins with", mana_used, "mana used") |
| 275 | + min_mana_used = min(min_mana_used, mana_used) |
| 276 | + break |
| 277 | + if mana_used > min_mana_used: |
| 278 | + if verbose_level >= 2: |
| 279 | + print("Aborting: too much mana used") |
| 280 | + break |
| 281 | + |
| 282 | + # Boss turn |
| 283 | + # Apply effects |
| 284 | + if shield_left > 0: |
| 285 | + player_armor = 7 |
| 286 | + shield_left -= 1 |
| 287 | + else: |
| 288 | + player_armor = 0 |
| 289 | + if poison_left > 0: |
| 290 | + boss_hp -= 3 |
| 291 | + poison_left -= 0 |
| 292 | + if recharge_left: |
| 293 | + player_mana += 101 |
| 294 | + recharge_left -= 1 |
| 295 | + |
| 296 | + player_hp -= boss_dmg - player_armor |
| 297 | + |
| 298 | + if player_hp <= 0: |
| 299 | + if verbose_level >= 2: |
| 300 | + print("Boss wins") |
| 301 | + # pruned_strategies.append(actions_done) |
| 302 | + break |
| 303 | + else: |
| 304 | + unknown_result.append(strategy) |
| 305 | + # print ('Pruned : ', pruned_strategies) |
| 306 | + print("Unknown : ", unknown_result) |
| 307 | +puzzle_actual_result = min_mana_used |
| 308 | + |
| 309 | + |
| 310 | +# -------------------------------- Outputs / results -------------------------------- # |
| 311 | + |
| 312 | +if verbose_level >= 3: |
| 313 | + print("Input : " + puzzle_input) |
| 314 | +print("Expected result : " + str(puzzle_expected_result)) |
| 315 | +print("Actual result : " + str(puzzle_actual_result)) |
0 commit comments