|
| 1 | +# -------------------------------- Input data -------------------------------- # |
| 2 | +import os, itertools, re |
| 3 | + |
| 4 | +test_data = {} |
| 5 | + |
| 6 | +test = 1 |
| 7 | +test_data[test] = {"input": {'Hit Points': 12, 'Damage': 7, 'Armor': 2}, |
| 8 | + "expected": ['Player wins (with 2 hit points)', 'Unknown'], |
| 9 | + } |
| 10 | + |
| 11 | +test = 'real' |
| 12 | +test_data[test] = {"input": {'Hit Points': 100, 'Damage': 8, 'Armor': 2}, |
| 13 | + "expected": ['91', '158'], |
| 14 | + } |
| 15 | + |
| 16 | + |
| 17 | +shop = '''Weapons: Cost Damage Armor |
| 18 | +Dagger 8 4 0 |
| 19 | +Shortsword 10 5 0 |
| 20 | +Warhammer 25 6 0 |
| 21 | +Longsword 40 7 0 |
| 22 | +Greataxe 74 8 0 |
| 23 | +
|
| 24 | +Armor: Cost Damage Armor |
| 25 | +Leather 13 0 1 |
| 26 | +Chainmail 31 0 2 |
| 27 | +Splintmail 53 0 3 |
| 28 | +Bandedmail 75 0 4 |
| 29 | +Platemail 102 0 5 |
| 30 | +
|
| 31 | +Rings: Cost Damage Armor |
| 32 | +Damage +1 25 1 0 |
| 33 | +Damage +2 50 2 0 |
| 34 | +Damage +3 100 3 0 |
| 35 | +Defense +1 20 0 1 |
| 36 | +Defense +2 40 0 2 |
| 37 | +Defense +3 80 0 3''' |
| 38 | + |
| 39 | + |
| 40 | +# -------------------------------- Control program execution -------------------------------- # |
| 41 | + |
| 42 | +case_to_test = 'real' |
| 43 | +part_to_test = 1 |
| 44 | +verbose_level = 1 |
| 45 | + |
| 46 | +# -------------------------------- Initialize some variables -------------------------------- # |
| 47 | + |
| 48 | +puzzle_input = test_data[case_to_test]['input'] |
| 49 | +puzzle_expected_result = test_data[case_to_test]['expected'][part_to_test-1] |
| 50 | +puzzle_actual_result = 'Unknown' |
| 51 | + |
| 52 | + |
| 53 | +# -------------------------------- Actual code execution -------------------------------- # |
| 54 | + |
| 55 | +player_initial_stats = {'Hit Points': 100, 'Damage': 0, 'Armor': 0} |
| 56 | +boss_initial_stats = puzzle_input |
| 57 | +minimum_gold = 10 ** 5 |
| 58 | +maximum_gold = 0 |
| 59 | + |
| 60 | + |
| 61 | +# Transforming shop in proper variables |
| 62 | +shop_items = {'Weapons':{}, 'Armor':{}, 'Rings':{}} |
| 63 | +for string in shop.split('\n'): |
| 64 | + if string == '': |
| 65 | + continue |
| 66 | + if ':' in string: |
| 67 | + item_type = string[0:string.find(':')] |
| 68 | + else: |
| 69 | + matches = re.match('([A-Za-z]{1,}(?: \+[0-9])?) *([0-9]*) *([0-9]*) *([0-9]*)', string) |
| 70 | + item, cost, damage, armor = matches.groups() |
| 71 | + shop_items[item_type].update({item: {'cost': int(cost), 'Damage': int(damage), 'Armor': int(armor)}}) |
| 72 | +# Adding no armor and no ring to the options |
| 73 | +shop_items['Armor'].update({'None': {'cost': 0, 'Damage': 0, 'Armor': 0}}) |
| 74 | +shop_items['Rings'].update({'None': {'cost': 0, 'Damage': 0, 'Armor': 0}}) |
| 75 | + |
| 76 | + |
| 77 | + |
| 78 | +player_stats = player_initial_stats.copy() |
| 79 | +gold_used = 0 |
| 80 | + |
| 81 | +ring_options = list(itertools.combinations(shop_items['Rings'], 2)) + [('None', 'None')] |
| 82 | + |
| 83 | +for weapon_name, weapon in shop_items['Weapons'].items(): |
| 84 | + for armor_name, armor in shop_items['Armor'].items(): |
| 85 | + for rings in ring_options: |
| 86 | + player_stats = player_initial_stats.copy() |
| 87 | + player_stats.update({x: player_initial_stats[x] + weapon[x] + armor[x] + shop_items['Rings'][rings[0]][x] + shop_items['Rings'][rings[1]][x] for x in ['Damage', 'Armor']}) |
| 88 | + gold_used = weapon['cost'] + armor['cost'] + shop_items['Rings'][rings[0]]['cost'] + shop_items['Rings'][rings[1]]['cost'] |
| 89 | + |
| 90 | + boss_stats = boss_initial_stats.copy() |
| 91 | + |
| 92 | + while True: |
| 93 | + # Player hits |
| 94 | + boss_stats['Hit Points'] -= max(1, player_stats['Damage'] - boss_stats['Armor']) |
| 95 | + if boss_stats['Hit Points'] <= 0: |
| 96 | + minimum_gold = min(minimum_gold, gold_used) |
| 97 | + break |
| 98 | + |
| 99 | + # Boss hits |
| 100 | + player_stats['Hit Points'] -= max(1, boss_stats['Damage'] - player_stats['Armor']) |
| 101 | + if player_stats['Hit Points'] <= 0: |
| 102 | + maximum_gold = max(maximum_gold, gold_used) |
| 103 | + break |
| 104 | + |
| 105 | +if part_to_test == 1: |
| 106 | + puzzle_actual_result = minimum_gold |
| 107 | +else: |
| 108 | + puzzle_actual_result = maximum_gold |
| 109 | + |
| 110 | + |
| 111 | + |
| 112 | +# -------------------------------- Outputs / results -------------------------------- # |
| 113 | + |
| 114 | +if verbose_level >= 3: |
| 115 | + print ('Input : ' + puzzle_input) |
| 116 | +print ('Expected result : ' + str(puzzle_expected_result)) |
| 117 | +print ('Actual result : ' + str(puzzle_actual_result)) |
| 118 | + |
| 119 | + |
| 120 | + |
| 121 | + |
0 commit comments