1class Effect:
2 def __init__(self, effect):
3 self.effect = effect
4 self.intensity = 1
5
6 def __call__(self, target):
7 effect = self.effect
8 result = None
9
10 while self.intensity > 0:
11 result = effect(target)
12 self.intensity -= 1
13 self.intensity = -1
14 return result
15
16
17class Potion:
18 def __init__(self, effects, duration):
19 self.duration = duration
20 self.used = False
21 self.is_depleted = False
22 if isinstance(next(iter(effects.values())), Effect):
23 self.effects = effects
24 else:
25 effects_object = {}
26 for effect_name, effect in effects.items():
27 effects_object[effect_name] = Effect(effect)
28 self.effects = effects_object
29
30 def _create_effects(self, effects):
31 effects_object = {}
32 for effect_name, effect in effects.items():
33 effects_object[effect_name] = Effect(effect.effect) if (isinstance(effect, Effect)) else Effect(effect)
34 return effects_object
35
36 def __add__(self, potion):
37 combined_effects = self._create_effects(self.effects)
38 for effect_name, effect in potion.effects.items():
39 if effect_name in combined_effects:
40 combined_effects[effect_name].intensity += 1
41 else:
42 combined_effects[effect_name] = Effect(effect)
43 self.used = True
44 potion.used =True
45 return Potion(combined_effects, max(self.duration, potion.duration))
46
47 def __mul__(self, intensity):
48 def enchance(effect):
49 effect.intensity *= intensity
50
51 def downgrade(effect):
52 new_intesity = effect.intensity * intensity
53 effect.intensity = int(new_intesity) if new_intesity % 1 <= 0.5 else int(new_intesity + 1)
54
55 enchanced_effects = self._create_effects(self.effects)
56
57 if intensity <= 1:
58 for effect_name, effect in enchanced_effects.items():
59 effect.intensity = self.effects[effect_name].intensity
60
61 action = enchance if intensity > 1 else downgrade
62 for effect in enchanced_effects.values():
63 action(effect)
64 self.used = True
65 return Potion(enchanced_effects, self.duration)
66
67 def __sub__(self, potion):
68 def is_valid_effect(effect_name, effect):
69 if effect_name in potion.effects.keys():
70 if effect.intensity > 1:
71 effect.intensity -= potion.effects[effect_name].intensity
72 return effect.intensity > 0
73 return False
74 return True
75
76 if any(map(lambda effect: effect not in self.effects.keys(), potion.effects.keys())):
77 raise TypeError("Ко? Не.")
78
79 effects = self._create_effects(self.effects)
80 filtered_effects = {effect_name: effect for effect_name, effect in effects.items() if is_valid_effect(effect_name, effect)}
81 self.used = True
82 potion.used =True
83 return Potion(filtered_effects, self.duration)
84
85 def __truediv__(self, divider):
86 new_potions = []
87 intensity_coefficient = 1 / divider
88 self.is_divided = True
89 for _ in range(divider):
90 new_potions.append(self * intensity_coefficient)
91 self.used = True
92 return new_potions
93
94 def __eq__(self, potion):
95 return all(
96 list(map(
97 lambda effect: effect in self.effects.keys() and potion.effects[effect].intensity == self.effects[effect].intensity,
98 potion.effects.keys()
99 ))
100 )
101
102 def __gt__(self, other_potion):
103 return self.get_score() > other_potion.get_score()
104
105 def __lt__(self, other_potion):
106 return self.get_score() < other_potion.get_score()
107
108 def __getattribute__(self, name):
109 effects = object.__getattribute__(self, 'effects')
110 if name in effects:
111 if effects[name].intensity < 0:
112 raise TypeError("Effect is depleted.")
113 self.check_validity()
114 return effects[name]
115 return object.__getattribute__(self, name)
116
117 def check_validity(self):
118 if self.used:
119 raise TypeError("Potion is now part of something bigger than itself.")
120 is_depleted = True
121 for effect in self.effects.values():
122 if (effect.intensity >= 0):
123 is_depleted = False
124 break
125 if is_depleted:
126 self.is_depleted = is_depleted
127 raise TypeError("Potion is depleted.")
128
129 def get_score(potion):
130 return sum(getattr(potion, effect).intensity for effect in potion.effects if getattr(potion, effect, False))
131
132
133class ГоспожатаПоХимия:
134 def __init__(self):
135 self.ticks_effects_end = {}
136 self.timer = 0
137
138 def tick(self):
139 self.timer += 1
140 if self.timer not in self.ticks_effects_end:
141 return
142
143 for target_data in self.ticks_effects_end[self.timer]:
144 target = target_data["target"]
145 for state_name, state in target_data["target_changes"].items():
146 setattr(target, state_name, state)
147
148 def apply(self, target, potion):
149 potion.check_validity()
150
151 effects_tuples = sorted(potion.effects.items(), key = lambda item: sum([ord(char) for char in item[0]]), reverse = True)
152 effects = {effect_tuple[0]: effect_tuple[1] for effect_tuple in effects_tuples}
153
154 for effect in effects.values():
155 end_time = self.timer + potion.duration
156 if end_time not in self.ticks_effects_end:
157 self.ticks_effects_end[end_time] = []
158 target_dict = dict(target.__dict__)
159 try:
160 for _ in range(effect.intensity):
161 effect(target)
162 target_changes = {key: target_dict[key] for key, value in target.__dict__.items() if target_dict[key] != value}
163 target_changes.update({key: 0 for key in target.__dict__.keys() if key not in target_dict})
164
165 self.ticks_effects_end[end_time].append({
166 "target": target,
167 "target_changes": target_changes
168 })
169 except TypeError:
170 raise TypeError("Potion is depleted.")
..E....E..E.F....F.F
======================================================================
ERROR: test_empty (test.TestBasicPotion)
Test initialization with empty effects.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 53, in test_empty
potion = Potion({}, duration=0)
File "/tmp/solution.py", line 22, in __init__
if isinstance(next(iter(effects.values())), Effect):
StopIteration
======================================================================
ERROR: test_deprecation (test.TestPotionOperations)
Test deprecation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 269, in test_deprecation
potion = potion1 - potion2
File "/tmp/solution.py", line 83, in __sub__
return Potion(filtered_effects, self.duration)
File "/tmp/solution.py", line 22, in __init__
if isinstance(next(iter(effects.values())), Effect):
StopIteration
======================================================================
ERROR: test_purification (test.TestPotionOperations)
Test purification of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 190, in test_purification
potion.int_attr_fun(self._target)
File "/tmp/solution.py", line 115, in __getattribute__
return object.__getattribute__(self, name)
AttributeError: 'Potion' object has no attribute 'int_attr_fun'
======================================================================
FAIL: test_applying_depleted_potion (test.TestГоспожатаПоХимия)
Test applying a depleted potion or a potion that was used in a reaction.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 382, in test_applying_depleted_potion
with self.assertRaisesRegex(TypeError, 'Potion is depleted\.'):
AssertionError: TypeError not raised
======================================================================
FAIL: test_ticking_multiple_potions (test.TestГоспожатаПоХимия)
Test ticking after applying multiple potions which affect the same attribute.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 458, in test_ticking_multiple_potions
self.assertEqual(self._target.int_attr, 50)
AssertionError: 5 != 50
======================================================================
FAIL: test_ticking_mutable (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with mutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 446, in test_ticking_mutable
self.assertEqual(self._target.list_attr, [1, 2, 3])
AssertionError: Lists differ: [1, 2, 3, 4] != [1, 2, 3]
First list contains 1 additional elements.
First extra element 3:
4
- [1, 2, 3, 4]
? ---
+ [1, 2, 3]
----------------------------------------------------------------------
Ran 20 tests in 0.002s
FAILED (failures=3, errors=3)
Виктор Бечев
04.12.2023 15:09Нямам стилови забележки. Има тук таме някой друг ред който се чете по-трудно, но на повечето места е чисто визуално, а не непременно защото е сложна логика.
|
f | 1 | class Effect: | f | 1 | class Effect: |
2 | def __init__(self, effect): | 2 | def __init__(self, effect): | ||
3 | self.effect = effect | 3 | self.effect = effect | ||
4 | self.intensity = 1 | 4 | self.intensity = 1 | ||
5 | 5 | ||||
6 | def __call__(self, target): | 6 | def __call__(self, target): | ||
7 | effect = self.effect | 7 | effect = self.effect | ||
8 | result = None | 8 | result = None | ||
9 | 9 | ||||
10 | while self.intensity > 0: | 10 | while self.intensity > 0: | ||
11 | result = effect(target) | 11 | result = effect(target) | ||
12 | self.intensity -= 1 | 12 | self.intensity -= 1 | ||
13 | self.intensity = -1 | 13 | self.intensity = -1 | ||
14 | return result | 14 | return result | ||
15 | 15 | ||||
16 | 16 | ||||
17 | class Potion: | 17 | class Potion: | ||
18 | def __init__(self, effects, duration): | 18 | def __init__(self, effects, duration): | ||
19 | self.duration = duration | 19 | self.duration = duration | ||
20 | self.used = False | 20 | self.used = False | ||
21 | self.is_depleted = False | 21 | self.is_depleted = False | ||
22 | if isinstance(next(iter(effects.values())), Effect): | 22 | if isinstance(next(iter(effects.values())), Effect): | ||
23 | self.effects = effects | 23 | self.effects = effects | ||
24 | else: | 24 | else: | ||
25 | effects_object = {} | 25 | effects_object = {} | ||
26 | for effect_name, effect in effects.items(): | 26 | for effect_name, effect in effects.items(): | ||
27 | effects_object[effect_name] = Effect(effect) | 27 | effects_object[effect_name] = Effect(effect) | ||
28 | self.effects = effects_object | 28 | self.effects = effects_object | ||
29 | 29 | ||||
30 | def _create_effects(self, effects): | 30 | def _create_effects(self, effects): | ||
31 | effects_object = {} | 31 | effects_object = {} | ||
32 | for effect_name, effect in effects.items(): | 32 | for effect_name, effect in effects.items(): | ||
33 | effects_object[effect_name] = Effect(effect.effect) if (isinstance(effect, Effect)) else Effect(effect) | 33 | effects_object[effect_name] = Effect(effect.effect) if (isinstance(effect, Effect)) else Effect(effect) | ||
34 | return effects_object | 34 | return effects_object | ||
35 | 35 | ||||
36 | def __add__(self, potion): | 36 | def __add__(self, potion): | ||
37 | combined_effects = self._create_effects(self.effects) | 37 | combined_effects = self._create_effects(self.effects) | ||
38 | for effect_name, effect in potion.effects.items(): | 38 | for effect_name, effect in potion.effects.items(): | ||
39 | if effect_name in combined_effects: | 39 | if effect_name in combined_effects: | ||
40 | combined_effects[effect_name].intensity += 1 | 40 | combined_effects[effect_name].intensity += 1 | ||
41 | else: | 41 | else: | ||
42 | combined_effects[effect_name] = Effect(effect) | 42 | combined_effects[effect_name] = Effect(effect) | ||
43 | self.used = True | 43 | self.used = True | ||
44 | potion.used =True | 44 | potion.used =True | ||
45 | return Potion(combined_effects, max(self.duration, potion.duration)) | 45 | return Potion(combined_effects, max(self.duration, potion.duration)) | ||
46 | 46 | ||||
47 | def __mul__(self, intensity): | 47 | def __mul__(self, intensity): | ||
48 | def enchance(effect): | 48 | def enchance(effect): | ||
49 | effect.intensity *= intensity | 49 | effect.intensity *= intensity | ||
50 | 50 | ||||
51 | def downgrade(effect): | 51 | def downgrade(effect): | ||
52 | new_intesity = effect.intensity * intensity | 52 | new_intesity = effect.intensity * intensity | ||
53 | effect.intensity = int(new_intesity) if new_intesity % 1 <= 0.5 else int(new_intesity + 1) | 53 | effect.intensity = int(new_intesity) if new_intesity % 1 <= 0.5 else int(new_intesity + 1) | ||
54 | 54 | ||||
55 | enchanced_effects = self._create_effects(self.effects) | 55 | enchanced_effects = self._create_effects(self.effects) | ||
56 | 56 | ||||
57 | if intensity <= 1: | 57 | if intensity <= 1: | ||
58 | for effect_name, effect in enchanced_effects.items(): | 58 | for effect_name, effect in enchanced_effects.items(): | ||
59 | effect.intensity = self.effects[effect_name].intensity | 59 | effect.intensity = self.effects[effect_name].intensity | ||
60 | 60 | ||||
61 | action = enchance if intensity > 1 else downgrade | 61 | action = enchance if intensity > 1 else downgrade | ||
62 | for effect in enchanced_effects.values(): | 62 | for effect in enchanced_effects.values(): | ||
63 | action(effect) | 63 | action(effect) | ||
64 | self.used = True | 64 | self.used = True | ||
65 | return Potion(enchanced_effects, self.duration) | 65 | return Potion(enchanced_effects, self.duration) | ||
66 | 66 | ||||
67 | def __sub__(self, potion): | 67 | def __sub__(self, potion): | ||
68 | def is_valid_effect(effect_name, effect): | 68 | def is_valid_effect(effect_name, effect): | ||
69 | if effect_name in potion.effects.keys(): | 69 | if effect_name in potion.effects.keys(): | ||
70 | if effect.intensity > 1: | 70 | if effect.intensity > 1: | ||
71 | effect.intensity -= potion.effects[effect_name].intensity | 71 | effect.intensity -= potion.effects[effect_name].intensity | ||
72 | return effect.intensity > 0 | 72 | return effect.intensity > 0 | ||
73 | return False | 73 | return False | ||
74 | return True | 74 | return True | ||
75 | 75 | ||||
76 | if any(map(lambda effect: effect not in self.effects.keys(), potion.effects.keys())): | 76 | if any(map(lambda effect: effect not in self.effects.keys(), potion.effects.keys())): | ||
77 | raise TypeError("Ко? Не.") | 77 | raise TypeError("Ко? Не.") | ||
78 | 78 | ||||
79 | effects = self._create_effects(self.effects) | 79 | effects = self._create_effects(self.effects) | ||
80 | filtered_effects = {effect_name: effect for effect_name, effect in effects.items() if is_valid_effect(effect_name, effect)} | 80 | filtered_effects = {effect_name: effect for effect_name, effect in effects.items() if is_valid_effect(effect_name, effect)} | ||
81 | self.used = True | 81 | self.used = True | ||
82 | potion.used =True | 82 | potion.used =True | ||
83 | return Potion(filtered_effects, self.duration) | 83 | return Potion(filtered_effects, self.duration) | ||
84 | 84 | ||||
85 | def __truediv__(self, divider): | 85 | def __truediv__(self, divider): | ||
86 | new_potions = [] | 86 | new_potions = [] | ||
87 | intensity_coefficient = 1 / divider | 87 | intensity_coefficient = 1 / divider | ||
88 | self.is_divided = True | 88 | self.is_divided = True | ||
89 | for _ in range(divider): | 89 | for _ in range(divider): | ||
90 | new_potions.append(self * intensity_coefficient) | 90 | new_potions.append(self * intensity_coefficient) | ||
91 | self.used = True | 91 | self.used = True | ||
92 | return new_potions | 92 | return new_potions | ||
93 | 93 | ||||
94 | def __eq__(self, potion): | 94 | def __eq__(self, potion): | ||
95 | return all( | 95 | return all( | ||
96 | list(map( | 96 | list(map( | ||
97 | lambda effect: effect in self.effects.keys() and potion.effects[effect].intensity == self.effects[effect].intensity, | 97 | lambda effect: effect in self.effects.keys() and potion.effects[effect].intensity == self.effects[effect].intensity, | ||
98 | potion.effects.keys() | 98 | potion.effects.keys() | ||
99 | )) | 99 | )) | ||
100 | ) | 100 | ) | ||
101 | 101 | ||||
102 | def __gt__(self, other_potion): | 102 | def __gt__(self, other_potion): | ||
103 | return self.get_score() > other_potion.get_score() | 103 | return self.get_score() > other_potion.get_score() | ||
104 | 104 | ||||
105 | def __lt__(self, other_potion): | 105 | def __lt__(self, other_potion): | ||
106 | return self.get_score() < other_potion.get_score() | 106 | return self.get_score() < other_potion.get_score() | ||
107 | 107 | ||||
108 | def __getattribute__(self, name): | 108 | def __getattribute__(self, name): | ||
109 | effects = object.__getattribute__(self, 'effects') | 109 | effects = object.__getattribute__(self, 'effects') | ||
110 | if name in effects: | 110 | if name in effects: | ||
n | n | 111 | if effects[name].intensity < 0: | ||
112 | raise TypeError("Effect is depleted.") | ||||
113 | self.check_validity() | ||||
114 | return effects[name] | ||||
115 | return object.__getattribute__(self, name) | ||||
116 | |||||
117 | def check_validity(self): | ||||
111 | if self.used: | 118 | if self.used: | ||
112 | raise TypeError("Potion is now part of something bigger than itself.") | 119 | raise TypeError("Potion is now part of something bigger than itself.") | ||
n | 113 | if effects[name].intensity < 0: | n | ||
114 | raise TypeError("Effect is depleted") | ||||
115 | is_depleted = True | 120 | is_depleted = True | ||
116 | for name, effect in effects.items(): | 121 | for effect in self.effects.values(): | ||
117 | if (effect.intensity >= 0): | 122 | if (effect.intensity >= 0): | ||
118 | is_depleted = False | 123 | is_depleted = False | ||
119 | break | 124 | break | ||
125 | if is_depleted: | ||||
120 | self.is_depleted = is_depleted | 126 | self.is_depleted = is_depleted | ||
n | 121 | return effects[name] | n | 127 | raise TypeError("Potion is depleted.") |
122 | return object.__getattribute__(self, name) | ||||
123 | 128 | ||||
124 | def get_score(potion): | 129 | def get_score(potion): | ||
125 | return sum(getattr(potion, effect).intensity for effect in potion.effects if getattr(potion, effect, False)) | 130 | return sum(getattr(potion, effect).intensity for effect in potion.effects if getattr(potion, effect, False)) | ||
126 | 131 | ||||
127 | 132 | ||||
128 | class ГоспожатаПоХимия: | 133 | class ГоспожатаПоХимия: | ||
129 | def __init__(self): | 134 | def __init__(self): | ||
130 | self.ticks_effects_end = {} | 135 | self.ticks_effects_end = {} | ||
131 | self.timer = 0 | 136 | self.timer = 0 | ||
132 | 137 | ||||
133 | def tick(self): | 138 | def tick(self): | ||
134 | self.timer += 1 | 139 | self.timer += 1 | ||
135 | if self.timer not in self.ticks_effects_end: | 140 | if self.timer not in self.ticks_effects_end: | ||
136 | return | 141 | return | ||
137 | 142 | ||||
138 | for target_data in self.ticks_effects_end[self.timer]: | 143 | for target_data in self.ticks_effects_end[self.timer]: | ||
139 | target = target_data["target"] | 144 | target = target_data["target"] | ||
140 | for state_name, state in target_data["target_changes"].items(): | 145 | for state_name, state in target_data["target_changes"].items(): | ||
141 | setattr(target, state_name, state) | 146 | setattr(target, state_name, state) | ||
142 | 147 | ||||
143 | def apply(self, target, potion): | 148 | def apply(self, target, potion): | ||
n | 144 | if potion.used: | n | 149 | potion.check_validity() |
145 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
146 | if not potion.duration or potion.is_depleted: | ||||
147 | raise TypeError("Potion is depleted") | ||||
148 | 150 | ||||
149 | effects_tuples = sorted(potion.effects.items(), key = lambda item: sum([ord(char) for char in item[0]]), reverse = True) | 151 | effects_tuples = sorted(potion.effects.items(), key = lambda item: sum([ord(char) for char in item[0]]), reverse = True) | ||
150 | effects = {effect_tuple[0]: effect_tuple[1] for effect_tuple in effects_tuples} | 152 | effects = {effect_tuple[0]: effect_tuple[1] for effect_tuple in effects_tuples} | ||
151 | 153 | ||||
152 | for effect in effects.values(): | 154 | for effect in effects.values(): | ||
153 | end_time = self.timer + potion.duration | 155 | end_time = self.timer + potion.duration | ||
154 | if end_time not in self.ticks_effects_end: | 156 | if end_time not in self.ticks_effects_end: | ||
155 | self.ticks_effects_end[end_time] = [] | 157 | self.ticks_effects_end[end_time] = [] | ||
156 | target_dict = dict(target.__dict__) | 158 | target_dict = dict(target.__dict__) | ||
157 | try: | 159 | try: | ||
158 | for _ in range(effect.intensity): | 160 | for _ in range(effect.intensity): | ||
159 | effect(target) | 161 | effect(target) | ||
160 | target_changes = {key: target_dict[key] for key, value in target.__dict__.items() if target_dict[key] != value} | 162 | target_changes = {key: target_dict[key] for key, value in target.__dict__.items() if target_dict[key] != value} | ||
161 | target_changes.update({key: 0 for key in target.__dict__.keys() if key not in target_dict}) | 163 | target_changes.update({key: 0 for key in target.__dict__.keys() if key not in target_dict}) | ||
162 | 164 | ||||
163 | self.ticks_effects_end[end_time].append({ | 165 | self.ticks_effects_end[end_time].append({ | ||
164 | "target": target, | 166 | "target": target, | ||
165 | "target_changes": target_changes | 167 | "target_changes": target_changes | ||
166 | }) | 168 | }) | ||
167 | except TypeError: | 169 | except TypeError: | ||
t | 168 | raise TypeError("Potion is depleted") | t | 170 | raise TypeError("Potion is depleted.") |
169 | potion.used = True |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
04.12.2023 14:58
04.12.2023 15:04