1def times_decorator(func, times):
2 def wrapper(target):
3 for _ in range(times):
4 func(target)
5
6 return wrapper
7
8
9def is_merged_decorator(func):
10 def wrapper(*args, **kwargs):
11 self = args[0]
12 other = args[1]
13 if self.is_merged or (other.is_merged if isinstance(other, Potion) else False):
14 raise TypeError('Potion is now part of something bigger than itself.')
15 try:
16 return func(*args, **kwargs)
17 finally:
18 if func.__name__ != '__getattr__':
19 """Set both both potions as merged and 'part of something bigger than itself'
20 if the function is not __getattr__.
21 If the potion is merged, __getattr__ will also raise an exception.
22 """
23 self.set_is_merged(True)
24 other.set_is_merged(True)
25
26 return wrapper
27
28
29class Potion:
30 def __init__(self, effects, duration, intensities=None):
31 self._effects = effects
32 self._intensities = intensities or {effect: 1 for effect in effects}
33 self._depleted = set()
34 self._duration = duration
35 self._is_merged = False
36 self._is_depleted = False
37
38 @property
39 def duration(self):
40 return self._duration
41
42 @property
43 def is_merged(self):
44 return self._is_merged
45
46 @property
47 def effects(self):
48 return list(self._effects.keys())
49
50 @property
51 def depleted(self):
52 return self._depleted
53
54 def __getitem__(self, item):
55 return self.__getattr__(item)
56
57 def set_is_depleted(self):
58 self._is_depleted = True
59
60 def set_is_merged(self, value):
61 self._is_merged = value
62
63 @is_merged_decorator
64 def __getattr__(self, effect_name):
65 if effect_name not in self._effects:
66 raise AttributeError
67
68 if effect_name in self._depleted:
69 raise TypeError('Effect is depleted')
70
71 if self._is_depleted:
72 raise TypeError('Potion is depleted.')
73
74 self._depleted.add(effect_name)
75 return times_decorator(self._effects[effect_name], self._intensities[effect_name])
76
77 @is_merged_decorator
78 def __add__(self, other):
79 if not isinstance(other, Potion):
80 raise TypeError('Potions can only be combined with other potions')
81
82 effects = {**self._effects, **other._effects}
83 duration = max(self._duration, other._duration)
84 intensities = {
85 effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0))
86 for effect in effects
87 }
88 return Potion(effects, duration, intensities)
89
90 @is_merged_decorator
91 def __sub__(self, other):
92 if not isinstance(other, Potion):
93 raise TypeError('Potions can only be combined with other potions')
94
95 if other._effects.keys() - self._effects.keys() != set():
96 raise TypeError('All effects of the right potion must be present in the left potion')
97
98 duration = self._duration
99 effects = {}
100 intensities = {}
101 for effect in self._effects:
102 if effect in other._effects:
103 new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect)
104 if new_effect_intensity > 0:
105 effects[effect] = self._effects[effect]
106 intensities[effect] = new_effect_intensity
107 else:
108 effects[effect] = self._effects[effect]
109 intensities[effect] = self._intensities[effect]
110
111 return Potion(effects, duration, intensities)
112
113 @is_merged_decorator
114 def __mul__(self, intensity_growth):
115 try:
116 float(intensity_growth)
117 except ValueError:
118 raise TypeError('Potions can only be multiplied by integers')
119
120 effects = {**self._effects}
121 duration = self._duration
122 intensities = {
123 effect: round(self._intensities.get(effect, 0) * intensity_growth)
124 for effect in effects
125 }
126 return Potion(effects, duration, intensities)
127
128 @is_merged_decorator
129 def __truediv__(self, new_potions_count):
130 try:
131 int(new_potions_count)
132 except ValueError:
133 raise TypeError('Potions can only be divided by integers')
134
135 return tuple([Potion({**self._effects},
136 self._duration,
137 {effect: round(self._intensities.get(effect, 0) / new_potions_count)
138 for effect in self._intensities}
139 ) for _ in range(new_potions_count)])
140
141 def __eq__(self, other):
142 if not isinstance(other, Potion):
143 return False
144
145 return self._effects.keys() - other._effects.keys() == set() and all(
146 [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()]
147 )
148
149 def __lt__(self, other):
150 if not isinstance(other, Potion):
151 return False
152
153 return sum(self._intensities.values()) < sum(other._intensities.values())
154
155 def __gt__(self, other):
156 if not isinstance(other, Potion):
157 return False
158
159 return not self == other and not self < other
160
161
162class ГоспожатаПоХимия:
163 def __init__(self):
164 self._target = None
165 self._target_public_attributes = None
166 self._current_tick_duration = None
167
168 def _sort_effects_by_molecule_mass(self, effects):
169 new_effects = list(effects)[:]
170 return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True)
171
172 def _get_public_attributes(self, target):
173 all_attributes = vars(target)
174 return {key: value for key, value in all_attributes.items() if
175 not key.startswith('_')}
176
177 def apply(self, target, potion):
178 self._target_public_attributes = self._get_public_attributes(target)
179 self._target = target
180 self._current_tick_duration = potion.duration
181
182 for effect in self._sort_effects_by_molecule_mass(potion.effects):
183 potion[effect](target)
184
185 if set(potion.depleted) == set(potion.effects):
186 potion.set_is_depleted()
187
188 def tick(self):
189 if not self._current_tick_duration:
190 raise Exception('No potion applied')
191
192 self._current_tick_duration -= 1
193
194 if self._current_tick_duration != 0:
195 return
196
197 current_public_attributes = self._get_public_attributes(self._target)
198 for key in current_public_attributes:
199 if key in self._target_public_attributes:
200 setattr(self._target, key, self._target_public_attributes[key])
201 else:
202 del self._target.__dict__[key]
.F.EE..EEEEEF..E.FEF
======================================================================
ERROR: test_equal (test.TestPotionComparison)
Test equality of potions.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 296, in test_equal
potion3 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'
======================================================================
ERROR: test_superbness (test.TestPotionComparison)
Test superbness of potions.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 316, in test_superbness
potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'
======================================================================
ERROR: test_deprecation (test.TestPotionOperations)
Test deprecation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 263, in test_deprecation
potion = potion1 * 2
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'
======================================================================
ERROR: test_dilution (test.TestPotionOperations)
Test dilution of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 118, in test_dilution
half_potion = base_potion * 0.5
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'float' object has no attribute 'set_is_merged'
======================================================================
ERROR: test_potentiation (test.TestPotionOperations)
Test potentiation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 110, in test_potentiation
potion = potion * 3
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'
======================================================================
ERROR: test_purification (test.TestPotionOperations)
Test purification of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 183, in test_purification
potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 3
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'
======================================================================
ERROR: test_separation (test.TestPotionOperations)
Test separation of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 212, in test_separation
potion = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 9
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'
======================================================================
ERROR: test_applying_part_of_potion (test.TestГоспожатаПоХимия)
Test applying only a part of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 365, in test_applying_part_of_potion
self._dimitrichka.apply(self._target, potion)
File "/tmp/solution.py", line 183, in apply
potion[effect](target)
File "/tmp/solution.py", line 55, in __getitem__
return self.__getattr__(item)
File "/tmp/solution.py", line 16, in wrapper
return func(*args, **kwargs)
File "/tmp/solution.py", line 69, in __getattr__
raise TypeError('Effect is depleted')
TypeError: Effect is depleted
======================================================================
ERROR: test_ticking_multiple_targets (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with mutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 479, in test_ticking_multiple_targets
potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
File "/tmp/solution.py", line 24, in wrapper
other.set_is_merged(True)
AttributeError: 'int' object has no attribute 'set_is_merged'
======================================================================
FAIL: test_depletion (test.TestBasicPotion)
Test depletion of a potion effect.
----------------------------------------------------------------------
TypeError: Effect is depleted
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/tmp/test.py", line 78, in test_depletion
with self.assertRaisesRegex(TypeError, 'Effect is depleted\.'):
AssertionError: "Effect is depleted\." does not match "Effect is depleted"
======================================================================
FAIL: test_applying_depleted_potion (test.TestГоспожатаПоХимия)
Test applying a depleted potion or a potion that was used in a reaction.
----------------------------------------------------------------------
TypeError: Effect is depleted
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/tmp/test.py", line 380, in test_applying_depleted_potion
with self.assertRaisesRegex(TypeError, 'Potion is depleted\.'):
AssertionError: "Potion is depleted\." does not match "Effect is depleted"
======================================================================
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: 500 != 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=4, errors=9)
Виктор Бечев
03.12.2023 23:51Да, ако имаш дефинирана `__eq__` и едно от `__gt__` или `__lt__` - Python ще се оправи с останалите. **В общия случай** е така, пак казвам - тук логиката може да се окаже шано, не мога да го сметна наум. :)
|
Филип Филчев
03.12.2023 15:09Относно коментара за \_\_gt__, не е нужно да се предефинира ли даже, ако е в такъв вид? Тоест == и < са достатъчни? Може да е споменавано на лекция, но не съм запомнил.
|
Филип Филчев
03.12.2023 15:08Ами да осъзнавам го това, но според мен е логично подобен проблем да се обособи по някакъв такъв начин, в случая с декоратор. Пък и тъкмо да се упражня с декоратори, понеже ме кефят.
Уж го изтествах и работи. Ако ми остане време преди крайния срок ще се опитам да го счупя.
|
Виктор Бечев
03.12.2023 13:44Не са драма декораторите, проблем е, че се опитваш да генерализираш декориране на неща, които имат различни сигнатури (i.e. в единия случай приемат `Potion` и `Potion`, в другия `Potion` и `int` и т.н.), така че дано си се подсигурил, че това не обърква нещо.
|
Филип Филчев
03.12.2023 00:46Реших да си поиграя с декоратори, получи се доста грозно, че даже и може да не работи много като хората, ама кво такова :)
|
f | 1 | def times_decorator(func, times): | f | 1 | def times_decorator(func, times): |
2 | def wrapper(target): | 2 | def wrapper(target): | ||
3 | for _ in range(times): | 3 | for _ in range(times): | ||
4 | func(target) | 4 | func(target) | ||
5 | 5 | ||||
6 | return wrapper | 6 | return wrapper | ||
7 | 7 | ||||
8 | 8 | ||||
9 | def is_merged_decorator(func): | 9 | def is_merged_decorator(func): | ||
10 | def wrapper(*args, **kwargs): | 10 | def wrapper(*args, **kwargs): | ||
11 | self = args[0] | 11 | self = args[0] | ||
12 | other = args[1] | 12 | other = args[1] | ||
n | 13 | if self.is_merged or other.is_merged: | n | 13 | if self.is_merged or (other.is_merged if isinstance(other, Potion) else False): |
14 | raise TypeError('Potion is now part of something bigger than itself.') | 14 | raise TypeError('Potion is now part of something bigger than itself.') | ||
15 | try: | 15 | try: | ||
16 | return func(*args, **kwargs) | 16 | return func(*args, **kwargs) | ||
17 | finally: | 17 | finally: | ||
18 | if func.__name__ != '__getattr__': | 18 | if func.__name__ != '__getattr__': | ||
19 | """Set both both potions as merged and 'part of something bigger than itself' | 19 | """Set both both potions as merged and 'part of something bigger than itself' | ||
20 | if the function is not __getattr__. | 20 | if the function is not __getattr__. | ||
21 | If the potion is merged, __getattr__ will also raise an exception. | 21 | If the potion is merged, __getattr__ will also raise an exception. | ||
22 | """ | 22 | """ | ||
23 | self.set_is_merged(True) | 23 | self.set_is_merged(True) | ||
24 | other.set_is_merged(True) | 24 | other.set_is_merged(True) | ||
25 | 25 | ||||
26 | return wrapper | 26 | return wrapper | ||
27 | 27 | ||||
28 | 28 | ||||
29 | class Potion: | 29 | class Potion: | ||
30 | def __init__(self, effects, duration, intensities=None): | 30 | def __init__(self, effects, duration, intensities=None): | ||
31 | self._effects = effects | 31 | self._effects = effects | ||
32 | self._intensities = intensities or {effect: 1 for effect in effects} | 32 | self._intensities = intensities or {effect: 1 for effect in effects} | ||
33 | self._depleted = set() | 33 | self._depleted = set() | ||
34 | self._duration = duration | 34 | self._duration = duration | ||
35 | self._is_merged = False | 35 | self._is_merged = False | ||
36 | self._is_depleted = False | 36 | self._is_depleted = False | ||
37 | 37 | ||||
38 | @property | 38 | @property | ||
39 | def duration(self): | 39 | def duration(self): | ||
40 | return self._duration | 40 | return self._duration | ||
41 | 41 | ||||
42 | @property | 42 | @property | ||
43 | def is_merged(self): | 43 | def is_merged(self): | ||
44 | return self._is_merged | 44 | return self._is_merged | ||
45 | 45 | ||||
46 | @property | 46 | @property | ||
47 | def effects(self): | 47 | def effects(self): | ||
48 | return list(self._effects.keys()) | 48 | return list(self._effects.keys()) | ||
49 | 49 | ||||
50 | @property | 50 | @property | ||
51 | def depleted(self): | 51 | def depleted(self): | ||
52 | return self._depleted | 52 | return self._depleted | ||
53 | 53 | ||||
54 | def __getitem__(self, item): | 54 | def __getitem__(self, item): | ||
55 | return self.__getattr__(item) | 55 | return self.__getattr__(item) | ||
56 | 56 | ||||
57 | def set_is_depleted(self): | 57 | def set_is_depleted(self): | ||
58 | self._is_depleted = True | 58 | self._is_depleted = True | ||
59 | 59 | ||||
60 | def set_is_merged(self, value): | 60 | def set_is_merged(self, value): | ||
61 | self._is_merged = value | 61 | self._is_merged = value | ||
62 | 62 | ||||
63 | @is_merged_decorator | 63 | @is_merged_decorator | ||
64 | def __getattr__(self, effect_name): | 64 | def __getattr__(self, effect_name): | ||
65 | if effect_name not in self._effects: | 65 | if effect_name not in self._effects: | ||
66 | raise AttributeError | 66 | raise AttributeError | ||
67 | 67 | ||||
68 | if effect_name in self._depleted: | 68 | if effect_name in self._depleted: | ||
69 | raise TypeError('Effect is depleted') | 69 | raise TypeError('Effect is depleted') | ||
70 | 70 | ||||
71 | if self._is_depleted: | 71 | if self._is_depleted: | ||
72 | raise TypeError('Potion is depleted.') | 72 | raise TypeError('Potion is depleted.') | ||
73 | 73 | ||||
74 | self._depleted.add(effect_name) | 74 | self._depleted.add(effect_name) | ||
75 | return times_decorator(self._effects[effect_name], self._intensities[effect_name]) | 75 | return times_decorator(self._effects[effect_name], self._intensities[effect_name]) | ||
76 | 76 | ||||
77 | @is_merged_decorator | 77 | @is_merged_decorator | ||
78 | def __add__(self, other): | 78 | def __add__(self, other): | ||
79 | if not isinstance(other, Potion): | 79 | if not isinstance(other, Potion): | ||
80 | raise TypeError('Potions can only be combined with other potions') | 80 | raise TypeError('Potions can only be combined with other potions') | ||
81 | 81 | ||||
82 | effects = {**self._effects, **other._effects} | 82 | effects = {**self._effects, **other._effects} | ||
83 | duration = max(self._duration, other._duration) | 83 | duration = max(self._duration, other._duration) | ||
84 | intensities = { | 84 | intensities = { | ||
85 | effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0)) | 85 | effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0)) | ||
86 | for effect in effects | 86 | for effect in effects | ||
87 | } | 87 | } | ||
88 | return Potion(effects, duration, intensities) | 88 | return Potion(effects, duration, intensities) | ||
89 | 89 | ||||
90 | @is_merged_decorator | 90 | @is_merged_decorator | ||
91 | def __sub__(self, other): | 91 | def __sub__(self, other): | ||
92 | if not isinstance(other, Potion): | 92 | if not isinstance(other, Potion): | ||
93 | raise TypeError('Potions can only be combined with other potions') | 93 | raise TypeError('Potions can only be combined with other potions') | ||
94 | 94 | ||||
95 | if other._effects.keys() - self._effects.keys() != set(): | 95 | if other._effects.keys() - self._effects.keys() != set(): | ||
96 | raise TypeError('All effects of the right potion must be present in the left potion') | 96 | raise TypeError('All effects of the right potion must be present in the left potion') | ||
97 | 97 | ||||
98 | duration = self._duration | 98 | duration = self._duration | ||
99 | effects = {} | 99 | effects = {} | ||
100 | intensities = {} | 100 | intensities = {} | ||
101 | for effect in self._effects: | 101 | for effect in self._effects: | ||
102 | if effect in other._effects: | 102 | if effect in other._effects: | ||
103 | new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect) | 103 | new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect) | ||
104 | if new_effect_intensity > 0: | 104 | if new_effect_intensity > 0: | ||
105 | effects[effect] = self._effects[effect] | 105 | effects[effect] = self._effects[effect] | ||
106 | intensities[effect] = new_effect_intensity | 106 | intensities[effect] = new_effect_intensity | ||
107 | else: | 107 | else: | ||
108 | effects[effect] = self._effects[effect] | 108 | effects[effect] = self._effects[effect] | ||
109 | intensities[effect] = self._intensities[effect] | 109 | intensities[effect] = self._intensities[effect] | ||
110 | 110 | ||||
111 | return Potion(effects, duration, intensities) | 111 | return Potion(effects, duration, intensities) | ||
112 | 112 | ||||
113 | @is_merged_decorator | 113 | @is_merged_decorator | ||
114 | def __mul__(self, intensity_growth): | 114 | def __mul__(self, intensity_growth): | ||
115 | try: | 115 | try: | ||
116 | float(intensity_growth) | 116 | float(intensity_growth) | ||
117 | except ValueError: | 117 | except ValueError: | ||
118 | raise TypeError('Potions can only be multiplied by integers') | 118 | raise TypeError('Potions can only be multiplied by integers') | ||
119 | 119 | ||||
120 | effects = {**self._effects} | 120 | effects = {**self._effects} | ||
121 | duration = self._duration | 121 | duration = self._duration | ||
122 | intensities = { | 122 | intensities = { | ||
123 | effect: round(self._intensities.get(effect, 0) * intensity_growth) | 123 | effect: round(self._intensities.get(effect, 0) * intensity_growth) | ||
124 | for effect in effects | 124 | for effect in effects | ||
125 | } | 125 | } | ||
126 | return Potion(effects, duration, intensities) | 126 | return Potion(effects, duration, intensities) | ||
127 | 127 | ||||
128 | @is_merged_decorator | 128 | @is_merged_decorator | ||
129 | def __truediv__(self, new_potions_count): | 129 | def __truediv__(self, new_potions_count): | ||
130 | try: | 130 | try: | ||
131 | int(new_potions_count) | 131 | int(new_potions_count) | ||
132 | except ValueError: | 132 | except ValueError: | ||
133 | raise TypeError('Potions can only be divided by integers') | 133 | raise TypeError('Potions can only be divided by integers') | ||
134 | 134 | ||||
135 | return tuple([Potion({**self._effects}, | 135 | return tuple([Potion({**self._effects}, | ||
136 | self._duration, | 136 | self._duration, | ||
137 | {effect: round(self._intensities.get(effect, 0) / new_potions_count) | 137 | {effect: round(self._intensities.get(effect, 0) / new_potions_count) | ||
138 | for effect in self._intensities} | 138 | for effect in self._intensities} | ||
139 | ) for _ in range(new_potions_count)]) | 139 | ) for _ in range(new_potions_count)]) | ||
140 | 140 | ||||
141 | def __eq__(self, other): | 141 | def __eq__(self, other): | ||
142 | if not isinstance(other, Potion): | 142 | if not isinstance(other, Potion): | ||
143 | return False | 143 | return False | ||
144 | 144 | ||||
145 | return self._effects.keys() - other._effects.keys() == set() and all( | 145 | return self._effects.keys() - other._effects.keys() == set() and all( | ||
146 | [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()] | 146 | [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()] | ||
147 | ) | 147 | ) | ||
148 | 148 | ||||
149 | def __lt__(self, other): | 149 | def __lt__(self, other): | ||
150 | if not isinstance(other, Potion): | 150 | if not isinstance(other, Potion): | ||
151 | return False | 151 | return False | ||
152 | 152 | ||||
153 | return sum(self._intensities.values()) < sum(other._intensities.values()) | 153 | return sum(self._intensities.values()) < sum(other._intensities.values()) | ||
154 | 154 | ||||
155 | def __gt__(self, other): | 155 | def __gt__(self, other): | ||
156 | if not isinstance(other, Potion): | 156 | if not isinstance(other, Potion): | ||
157 | return False | 157 | return False | ||
158 | 158 | ||||
159 | return not self == other and not self < other | 159 | return not self == other and not self < other | ||
160 | 160 | ||||
161 | 161 | ||||
162 | class ГоспожатаПоХимия: | 162 | class ГоспожатаПоХимия: | ||
163 | def __init__(self): | 163 | def __init__(self): | ||
164 | self._target = None | 164 | self._target = None | ||
165 | self._target_public_attributes = None | 165 | self._target_public_attributes = None | ||
166 | self._current_tick_duration = None | 166 | self._current_tick_duration = None | ||
167 | 167 | ||||
168 | def _sort_effects_by_molecule_mass(self, effects): | 168 | def _sort_effects_by_molecule_mass(self, effects): | ||
169 | new_effects = list(effects)[:] | 169 | new_effects = list(effects)[:] | ||
170 | return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True) | 170 | return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True) | ||
171 | 171 | ||||
172 | def _get_public_attributes(self, target): | 172 | def _get_public_attributes(self, target): | ||
173 | all_attributes = vars(target) | 173 | all_attributes = vars(target) | ||
174 | return {key: value for key, value in all_attributes.items() if | 174 | return {key: value for key, value in all_attributes.items() if | ||
175 | not key.startswith('_')} | 175 | not key.startswith('_')} | ||
176 | 176 | ||||
177 | def apply(self, target, potion): | 177 | def apply(self, target, potion): | ||
178 | self._target_public_attributes = self._get_public_attributes(target) | 178 | self._target_public_attributes = self._get_public_attributes(target) | ||
179 | self._target = target | 179 | self._target = target | ||
180 | self._current_tick_duration = potion.duration | 180 | self._current_tick_duration = potion.duration | ||
181 | 181 | ||||
182 | for effect in self._sort_effects_by_molecule_mass(potion.effects): | 182 | for effect in self._sort_effects_by_molecule_mass(potion.effects): | ||
183 | potion[effect](target) | 183 | potion[effect](target) | ||
184 | 184 | ||||
185 | if set(potion.depleted) == set(potion.effects): | 185 | if set(potion.depleted) == set(potion.effects): | ||
186 | potion.set_is_depleted() | 186 | potion.set_is_depleted() | ||
187 | 187 | ||||
188 | def tick(self): | 188 | def tick(self): | ||
189 | if not self._current_tick_duration: | 189 | if not self._current_tick_duration: | ||
190 | raise Exception('No potion applied') | 190 | raise Exception('No potion applied') | ||
191 | 191 | ||||
192 | self._current_tick_duration -= 1 | 192 | self._current_tick_duration -= 1 | ||
193 | 193 | ||||
194 | if self._current_tick_duration != 0: | 194 | if self._current_tick_duration != 0: | ||
195 | return | 195 | return | ||
196 | 196 | ||||
197 | current_public_attributes = self._get_public_attributes(self._target) | 197 | current_public_attributes = self._get_public_attributes(self._target) | ||
198 | for key in current_public_attributes: | 198 | for key in current_public_attributes: | ||
199 | if key in self._target_public_attributes: | 199 | if key in self._target_public_attributes: | ||
200 | setattr(self._target, key, self._target_public_attributes[key]) | 200 | setattr(self._target, key, self._target_public_attributes[key]) | ||
201 | else: | 201 | else: | ||
202 | del self._target.__dict__[key] | 202 | del self._target.__dict__[key] | ||
t | t | 203 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | def times_decorator(func, times): | f | 1 | def times_decorator(func, times): |
2 | def wrapper(target): | 2 | def wrapper(target): | ||
3 | for _ in range(times): | 3 | for _ in range(times): | ||
4 | func(target) | 4 | func(target) | ||
5 | 5 | ||||
6 | return wrapper | 6 | return wrapper | ||
7 | 7 | ||||
8 | 8 | ||||
9 | def is_merged_decorator(func): | 9 | def is_merged_decorator(func): | ||
10 | def wrapper(*args, **kwargs): | 10 | def wrapper(*args, **kwargs): | ||
11 | self = args[0] | 11 | self = args[0] | ||
n | n | 12 | other = args[1] | ||
12 | if self._is_merged: | 13 | if self.is_merged or other.is_merged: | ||
13 | raise TypeError('Potion is now part of something bigger than itself.') | 14 | raise TypeError('Potion is now part of something bigger than itself.') | ||
14 | try: | 15 | try: | ||
15 | return func(*args, **kwargs) | 16 | return func(*args, **kwargs) | ||
16 | finally: | 17 | finally: | ||
17 | if func.__name__ != '__getattr__': | 18 | if func.__name__ != '__getattr__': | ||
n | n | 19 | """Set both both potions as merged and 'part of something bigger than itself' | ||
20 | if the function is not __getattr__. | ||||
21 | If the potion is merged, __getattr__ will also raise an exception. | ||||
22 | """ | ||||
18 | self._is_merged = True | 23 | self.set_is_merged(True) | ||
24 | other.set_is_merged(True) | ||||
19 | 25 | ||||
20 | return wrapper | 26 | return wrapper | ||
21 | 27 | ||||
22 | 28 | ||||
23 | class Potion: | 29 | class Potion: | ||
24 | def __init__(self, effects, duration, intensities=None): | 30 | def __init__(self, effects, duration, intensities=None): | ||
n | 25 | self._is_merged = False | n | ||
26 | self._effects = effects | 31 | self._effects = effects | ||
27 | self._intensities = intensities or {effect: 1 for effect in effects} | 32 | self._intensities = intensities or {effect: 1 for effect in effects} | ||
28 | self._depleted = set() | 33 | self._depleted = set() | ||
29 | self._duration = duration | 34 | self._duration = duration | ||
n | n | 35 | self._is_merged = False | ||
30 | self._is_depleted = False | 36 | self._is_depleted = False | ||
31 | 37 | ||||
32 | @property | 38 | @property | ||
33 | def duration(self): | 39 | def duration(self): | ||
34 | return self._duration | 40 | return self._duration | ||
35 | 41 | ||||
36 | @property | 42 | @property | ||
n | n | 43 | def is_merged(self): | ||
44 | return self._is_merged | ||||
45 | |||||
46 | @property | ||||
37 | def effects(self): | 47 | def effects(self): | ||
38 | return list(self._effects.keys()) | 48 | return list(self._effects.keys()) | ||
39 | 49 | ||||
40 | @property | 50 | @property | ||
41 | def depleted(self): | 51 | def depleted(self): | ||
42 | return self._depleted | 52 | return self._depleted | ||
43 | 53 | ||||
44 | def __getitem__(self, item): | 54 | def __getitem__(self, item): | ||
45 | return self.__getattr__(item) | 55 | return self.__getattr__(item) | ||
46 | 56 | ||||
n | 47 | def deplete(self): | n | 57 | def set_is_depleted(self): |
48 | self._is_depleted = True | 58 | self._is_depleted = True | ||
49 | 59 | ||||
n | 50 | def __repr__(self): | n | 60 | def set_is_merged(self, value): |
51 | return f'Potion({self._effects}, {self._duration})' | 61 | self._is_merged = value | ||
52 | 62 | ||||
53 | @is_merged_decorator | 63 | @is_merged_decorator | ||
54 | def __getattr__(self, effect_name): | 64 | def __getattr__(self, effect_name): | ||
55 | if effect_name not in self._effects: | 65 | if effect_name not in self._effects: | ||
56 | raise AttributeError | 66 | raise AttributeError | ||
57 | 67 | ||||
58 | if effect_name in self._depleted: | 68 | if effect_name in self._depleted: | ||
59 | raise TypeError('Effect is depleted') | 69 | raise TypeError('Effect is depleted') | ||
60 | 70 | ||||
61 | if self._is_depleted: | 71 | if self._is_depleted: | ||
62 | raise TypeError('Potion is depleted.') | 72 | raise TypeError('Potion is depleted.') | ||
63 | 73 | ||||
64 | self._depleted.add(effect_name) | 74 | self._depleted.add(effect_name) | ||
65 | return times_decorator(self._effects[effect_name], self._intensities[effect_name]) | 75 | return times_decorator(self._effects[effect_name], self._intensities[effect_name]) | ||
66 | 76 | ||||
67 | @is_merged_decorator | 77 | @is_merged_decorator | ||
68 | def __add__(self, other): | 78 | def __add__(self, other): | ||
69 | if not isinstance(other, Potion): | 79 | if not isinstance(other, Potion): | ||
70 | raise TypeError('Potions can only be combined with other potions') | 80 | raise TypeError('Potions can only be combined with other potions') | ||
71 | 81 | ||||
72 | effects = {**self._effects, **other._effects} | 82 | effects = {**self._effects, **other._effects} | ||
73 | duration = max(self._duration, other._duration) | 83 | duration = max(self._duration, other._duration) | ||
74 | intensities = { | 84 | intensities = { | ||
75 | effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0)) | 85 | effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0)) | ||
76 | for effect in effects | 86 | for effect in effects | ||
77 | } | 87 | } | ||
78 | return Potion(effects, duration, intensities) | 88 | return Potion(effects, duration, intensities) | ||
79 | 89 | ||||
80 | @is_merged_decorator | 90 | @is_merged_decorator | ||
81 | def __sub__(self, other): | 91 | def __sub__(self, other): | ||
82 | if not isinstance(other, Potion): | 92 | if not isinstance(other, Potion): | ||
83 | raise TypeError('Potions can only be combined with other potions') | 93 | raise TypeError('Potions can only be combined with other potions') | ||
84 | 94 | ||||
85 | if other._effects.keys() - self._effects.keys() != set(): | 95 | if other._effects.keys() - self._effects.keys() != set(): | ||
86 | raise TypeError('All effects of the right potion must be present in the left potion') | 96 | raise TypeError('All effects of the right potion must be present in the left potion') | ||
87 | 97 | ||||
88 | duration = self._duration | 98 | duration = self._duration | ||
89 | effects = {} | 99 | effects = {} | ||
90 | intensities = {} | 100 | intensities = {} | ||
91 | for effect in self._effects: | 101 | for effect in self._effects: | ||
92 | if effect in other._effects: | 102 | if effect in other._effects: | ||
93 | new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect) | 103 | new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect) | ||
94 | if new_effect_intensity > 0: | 104 | if new_effect_intensity > 0: | ||
95 | effects[effect] = self._effects[effect] | 105 | effects[effect] = self._effects[effect] | ||
96 | intensities[effect] = new_effect_intensity | 106 | intensities[effect] = new_effect_intensity | ||
97 | else: | 107 | else: | ||
98 | effects[effect] = self._effects[effect] | 108 | effects[effect] = self._effects[effect] | ||
99 | intensities[effect] = self._intensities[effect] | 109 | intensities[effect] = self._intensities[effect] | ||
100 | 110 | ||||
101 | return Potion(effects, duration, intensities) | 111 | return Potion(effects, duration, intensities) | ||
102 | 112 | ||||
103 | @is_merged_decorator | 113 | @is_merged_decorator | ||
104 | def __mul__(self, intensity_growth): | 114 | def __mul__(self, intensity_growth): | ||
105 | try: | 115 | try: | ||
106 | float(intensity_growth) | 116 | float(intensity_growth) | ||
107 | except ValueError: | 117 | except ValueError: | ||
108 | raise TypeError('Potions can only be multiplied by integers') | 118 | raise TypeError('Potions can only be multiplied by integers') | ||
109 | 119 | ||||
110 | effects = {**self._effects} | 120 | effects = {**self._effects} | ||
111 | duration = self._duration | 121 | duration = self._duration | ||
112 | intensities = { | 122 | intensities = { | ||
113 | effect: round(self._intensities.get(effect, 0) * intensity_growth) | 123 | effect: round(self._intensities.get(effect, 0) * intensity_growth) | ||
114 | for effect in effects | 124 | for effect in effects | ||
115 | } | 125 | } | ||
116 | return Potion(effects, duration, intensities) | 126 | return Potion(effects, duration, intensities) | ||
117 | 127 | ||||
118 | @is_merged_decorator | 128 | @is_merged_decorator | ||
119 | def __truediv__(self, new_potions_count): | 129 | def __truediv__(self, new_potions_count): | ||
120 | try: | 130 | try: | ||
121 | int(new_potions_count) | 131 | int(new_potions_count) | ||
122 | except ValueError: | 132 | except ValueError: | ||
123 | raise TypeError('Potions can only be divided by integers') | 133 | raise TypeError('Potions can only be divided by integers') | ||
124 | 134 | ||||
125 | return tuple([Potion({**self._effects}, | 135 | return tuple([Potion({**self._effects}, | ||
126 | self._duration, | 136 | self._duration, | ||
127 | {effect: round(self._intensities.get(effect, 0) / new_potions_count) | 137 | {effect: round(self._intensities.get(effect, 0) / new_potions_count) | ||
128 | for effect in self._intensities} | 138 | for effect in self._intensities} | ||
129 | ) for _ in range(new_potions_count)]) | 139 | ) for _ in range(new_potions_count)]) | ||
130 | 140 | ||||
131 | def __eq__(self, other): | 141 | def __eq__(self, other): | ||
132 | if not isinstance(other, Potion): | 142 | if not isinstance(other, Potion): | ||
133 | return False | 143 | return False | ||
134 | 144 | ||||
135 | return self._effects.keys() - other._effects.keys() == set() and all( | 145 | return self._effects.keys() - other._effects.keys() == set() and all( | ||
136 | [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()] | 146 | [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()] | ||
137 | ) | 147 | ) | ||
138 | 148 | ||||
139 | def __lt__(self, other): | 149 | def __lt__(self, other): | ||
140 | if not isinstance(other, Potion): | 150 | if not isinstance(other, Potion): | ||
141 | return False | 151 | return False | ||
142 | 152 | ||||
143 | return sum(self._intensities.values()) < sum(other._intensities.values()) | 153 | return sum(self._intensities.values()) < sum(other._intensities.values()) | ||
144 | 154 | ||||
145 | def __gt__(self, other): | 155 | def __gt__(self, other): | ||
146 | if not isinstance(other, Potion): | 156 | if not isinstance(other, Potion): | ||
147 | return False | 157 | return False | ||
148 | 158 | ||||
149 | return not self == other and not self < other | 159 | return not self == other and not self < other | ||
150 | 160 | ||||
151 | 161 | ||||
152 | class ГоспожатаПоХимия: | 162 | class ГоспожатаПоХимия: | ||
153 | def __init__(self): | 163 | def __init__(self): | ||
154 | self._target = None | 164 | self._target = None | ||
155 | self._target_public_attributes = None | 165 | self._target_public_attributes = None | ||
156 | self._current_tick_duration = None | 166 | self._current_tick_duration = None | ||
157 | 167 | ||||
158 | def _sort_effects_by_molecule_mass(self, effects): | 168 | def _sort_effects_by_molecule_mass(self, effects): | ||
159 | new_effects = list(effects)[:] | 169 | new_effects = list(effects)[:] | ||
160 | return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True) | 170 | return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True) | ||
161 | 171 | ||||
162 | def _get_public_attributes(self, target): | 172 | def _get_public_attributes(self, target): | ||
163 | all_attributes = vars(target) | 173 | all_attributes = vars(target) | ||
164 | return {key: value for key, value in all_attributes.items() if | 174 | return {key: value for key, value in all_attributes.items() if | ||
165 | not key.startswith('_')} | 175 | not key.startswith('_')} | ||
166 | 176 | ||||
167 | def apply(self, target, potion): | 177 | def apply(self, target, potion): | ||
168 | self._target_public_attributes = self._get_public_attributes(target) | 178 | self._target_public_attributes = self._get_public_attributes(target) | ||
169 | self._target = target | 179 | self._target = target | ||
170 | self._current_tick_duration = potion.duration | 180 | self._current_tick_duration = potion.duration | ||
171 | 181 | ||||
172 | for effect in self._sort_effects_by_molecule_mass(potion.effects): | 182 | for effect in self._sort_effects_by_molecule_mass(potion.effects): | ||
173 | potion[effect](target) | 183 | potion[effect](target) | ||
174 | 184 | ||||
175 | if set(potion.depleted) == set(potion.effects): | 185 | if set(potion.depleted) == set(potion.effects): | ||
t | 176 | potion.deplete() | t | 186 | potion.set_is_depleted() |
177 | 187 | ||||
178 | def tick(self): | 188 | def tick(self): | ||
179 | if not self._current_tick_duration: | 189 | if not self._current_tick_duration: | ||
180 | raise Exception('No potion applied') | 190 | raise Exception('No potion applied') | ||
181 | 191 | ||||
182 | self._current_tick_duration -= 1 | 192 | self._current_tick_duration -= 1 | ||
183 | 193 | ||||
184 | if self._current_tick_duration != 0: | 194 | if self._current_tick_duration != 0: | ||
185 | return | 195 | return | ||
186 | 196 | ||||
187 | current_public_attributes = self._get_public_attributes(self._target) | 197 | current_public_attributes = self._get_public_attributes(self._target) | ||
188 | for key in current_public_attributes: | 198 | for key in current_public_attributes: | ||
189 | if key in self._target_public_attributes: | 199 | if key in self._target_public_attributes: | ||
190 | setattr(self._target, key, self._target_public_attributes[key]) | 200 | setattr(self._target, key, self._target_public_attributes[key]) | ||
191 | else: | 201 | else: | ||
192 | del self._target.__dict__[key] | 202 | del self._target.__dict__[key] |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | def times_decorator(func, times): | f | 1 | def times_decorator(func, times): |
2 | def wrapper(target): | 2 | def wrapper(target): | ||
3 | for _ in range(times): | 3 | for _ in range(times): | ||
4 | func(target) | 4 | func(target) | ||
5 | 5 | ||||
6 | return wrapper | 6 | return wrapper | ||
7 | 7 | ||||
8 | 8 | ||||
9 | def is_merged_decorator(func): | 9 | def is_merged_decorator(func): | ||
10 | def wrapper(*args, **kwargs): | 10 | def wrapper(*args, **kwargs): | ||
11 | self = args[0] | 11 | self = args[0] | ||
12 | if self._is_merged: | 12 | if self._is_merged: | ||
13 | raise TypeError('Potion is now part of something bigger than itself.') | 13 | raise TypeError('Potion is now part of something bigger than itself.') | ||
14 | try: | 14 | try: | ||
15 | return func(*args, **kwargs) | 15 | return func(*args, **kwargs) | ||
16 | finally: | 16 | finally: | ||
17 | if func.__name__ != '__getattr__': | 17 | if func.__name__ != '__getattr__': | ||
18 | self._is_merged = True | 18 | self._is_merged = True | ||
19 | 19 | ||||
20 | return wrapper | 20 | return wrapper | ||
21 | 21 | ||||
22 | 22 | ||||
23 | class Potion: | 23 | class Potion: | ||
24 | def __init__(self, effects, duration, intensities=None): | 24 | def __init__(self, effects, duration, intensities=None): | ||
25 | self._is_merged = False | 25 | self._is_merged = False | ||
26 | self._effects = effects | 26 | self._effects = effects | ||
27 | self._intensities = intensities or {effect: 1 for effect in effects} | 27 | self._intensities = intensities or {effect: 1 for effect in effects} | ||
28 | self._depleted = set() | 28 | self._depleted = set() | ||
29 | self._duration = duration | 29 | self._duration = duration | ||
n | 30 | self._depleted_message = 'Effect is depleted' | n | ||
31 | self._is_depleted = False | 30 | self._is_depleted = False | ||
32 | 31 | ||||
33 | @property | 32 | @property | ||
34 | def duration(self): | 33 | def duration(self): | ||
35 | return self._duration | 34 | return self._duration | ||
36 | 35 | ||||
37 | @property | 36 | @property | ||
38 | def effects(self): | 37 | def effects(self): | ||
39 | return list(self._effects.keys()) | 38 | return list(self._effects.keys()) | ||
40 | 39 | ||||
41 | @property | 40 | @property | ||
42 | def depleted(self): | 41 | def depleted(self): | ||
43 | return self._depleted | 42 | return self._depleted | ||
44 | 43 | ||||
45 | def __getitem__(self, item): | 44 | def __getitem__(self, item): | ||
46 | return self.__getattr__(item) | 45 | return self.__getattr__(item) | ||
47 | 46 | ||||
n | 48 | def deplete(self, depleted_message): | n | 47 | def deplete(self): |
49 | self._depleted_message = depleted_message | ||||
50 | self._is_depleted = True | 48 | self._is_depleted = True | ||
51 | 49 | ||||
52 | def __repr__(self): | 50 | def __repr__(self): | ||
53 | return f'Potion({self._effects}, {self._duration})' | 51 | return f'Potion({self._effects}, {self._duration})' | ||
54 | 52 | ||||
55 | @is_merged_decorator | 53 | @is_merged_decorator | ||
56 | def __getattr__(self, effect_name): | 54 | def __getattr__(self, effect_name): | ||
57 | if effect_name not in self._effects: | 55 | if effect_name not in self._effects: | ||
58 | raise AttributeError | 56 | raise AttributeError | ||
59 | 57 | ||||
n | 60 | if effect_name in self._depleted or self._is_depleted: | n | 58 | if effect_name in self._depleted: |
61 | raise TypeError(self._depleted_message) | 59 | raise TypeError('Effect is depleted') | ||
60 | |||||
61 | if self._is_depleted: | ||||
62 | raise TypeError('Potion is depleted.') | ||||
62 | 63 | ||||
63 | self._depleted.add(effect_name) | 64 | self._depleted.add(effect_name) | ||
64 | return times_decorator(self._effects[effect_name], self._intensities[effect_name]) | 65 | return times_decorator(self._effects[effect_name], self._intensities[effect_name]) | ||
65 | 66 | ||||
66 | @is_merged_decorator | 67 | @is_merged_decorator | ||
67 | def __add__(self, other): | 68 | def __add__(self, other): | ||
68 | if not isinstance(other, Potion): | 69 | if not isinstance(other, Potion): | ||
69 | raise TypeError('Potions can only be combined with other potions') | 70 | raise TypeError('Potions can only be combined with other potions') | ||
70 | 71 | ||||
71 | effects = {**self._effects, **other._effects} | 72 | effects = {**self._effects, **other._effects} | ||
72 | duration = max(self._duration, other._duration) | 73 | duration = max(self._duration, other._duration) | ||
73 | intensities = { | 74 | intensities = { | ||
74 | effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0)) | 75 | effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0)) | ||
75 | for effect in effects | 76 | for effect in effects | ||
76 | } | 77 | } | ||
77 | return Potion(effects, duration, intensities) | 78 | return Potion(effects, duration, intensities) | ||
78 | 79 | ||||
79 | @is_merged_decorator | 80 | @is_merged_decorator | ||
80 | def __sub__(self, other): | 81 | def __sub__(self, other): | ||
81 | if not isinstance(other, Potion): | 82 | if not isinstance(other, Potion): | ||
82 | raise TypeError('Potions can only be combined with other potions') | 83 | raise TypeError('Potions can only be combined with other potions') | ||
83 | 84 | ||||
84 | if other._effects.keys() - self._effects.keys() != set(): | 85 | if other._effects.keys() - self._effects.keys() != set(): | ||
85 | raise TypeError('All effects of the right potion must be present in the left potion') | 86 | raise TypeError('All effects of the right potion must be present in the left potion') | ||
86 | 87 | ||||
87 | duration = self._duration | 88 | duration = self._duration | ||
88 | effects = {} | 89 | effects = {} | ||
89 | intensities = {} | 90 | intensities = {} | ||
90 | for effect in self._effects: | 91 | for effect in self._effects: | ||
91 | if effect in other._effects: | 92 | if effect in other._effects: | ||
92 | new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect) | 93 | new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect) | ||
93 | if new_effect_intensity > 0: | 94 | if new_effect_intensity > 0: | ||
94 | effects[effect] = self._effects[effect] | 95 | effects[effect] = self._effects[effect] | ||
95 | intensities[effect] = new_effect_intensity | 96 | intensities[effect] = new_effect_intensity | ||
96 | else: | 97 | else: | ||
97 | effects[effect] = self._effects[effect] | 98 | effects[effect] = self._effects[effect] | ||
98 | intensities[effect] = self._intensities[effect] | 99 | intensities[effect] = self._intensities[effect] | ||
99 | 100 | ||||
100 | return Potion(effects, duration, intensities) | 101 | return Potion(effects, duration, intensities) | ||
101 | 102 | ||||
102 | @is_merged_decorator | 103 | @is_merged_decorator | ||
103 | def __mul__(self, intensity_growth): | 104 | def __mul__(self, intensity_growth): | ||
104 | try: | 105 | try: | ||
105 | float(intensity_growth) | 106 | float(intensity_growth) | ||
106 | except ValueError: | 107 | except ValueError: | ||
107 | raise TypeError('Potions can only be multiplied by integers') | 108 | raise TypeError('Potions can only be multiplied by integers') | ||
108 | 109 | ||||
109 | effects = {**self._effects} | 110 | effects = {**self._effects} | ||
110 | duration = self._duration | 111 | duration = self._duration | ||
111 | intensities = { | 112 | intensities = { | ||
112 | effect: round(self._intensities.get(effect, 0) * intensity_growth) | 113 | effect: round(self._intensities.get(effect, 0) * intensity_growth) | ||
113 | for effect in effects | 114 | for effect in effects | ||
114 | } | 115 | } | ||
115 | return Potion(effects, duration, intensities) | 116 | return Potion(effects, duration, intensities) | ||
116 | 117 | ||||
117 | @is_merged_decorator | 118 | @is_merged_decorator | ||
118 | def __truediv__(self, new_potions_count): | 119 | def __truediv__(self, new_potions_count): | ||
119 | try: | 120 | try: | ||
120 | int(new_potions_count) | 121 | int(new_potions_count) | ||
121 | except ValueError: | 122 | except ValueError: | ||
122 | raise TypeError('Potions can only be divided by integers') | 123 | raise TypeError('Potions can only be divided by integers') | ||
123 | 124 | ||||
124 | return tuple([Potion({**self._effects}, | 125 | return tuple([Potion({**self._effects}, | ||
125 | self._duration, | 126 | self._duration, | ||
126 | {effect: round(self._intensities.get(effect, 0) / new_potions_count) | 127 | {effect: round(self._intensities.get(effect, 0) / new_potions_count) | ||
127 | for effect in self._intensities} | 128 | for effect in self._intensities} | ||
128 | ) for _ in range(new_potions_count)]) | 129 | ) for _ in range(new_potions_count)]) | ||
129 | 130 | ||||
130 | def __eq__(self, other): | 131 | def __eq__(self, other): | ||
131 | if not isinstance(other, Potion): | 132 | if not isinstance(other, Potion): | ||
132 | return False | 133 | return False | ||
133 | 134 | ||||
134 | return self._effects.keys() - other._effects.keys() == set() and all( | 135 | return self._effects.keys() - other._effects.keys() == set() and all( | ||
135 | [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()] | 136 | [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()] | ||
136 | ) | 137 | ) | ||
137 | 138 | ||||
138 | def __lt__(self, other): | 139 | def __lt__(self, other): | ||
139 | if not isinstance(other, Potion): | 140 | if not isinstance(other, Potion): | ||
140 | return False | 141 | return False | ||
141 | 142 | ||||
142 | return sum(self._intensities.values()) < sum(other._intensities.values()) | 143 | return sum(self._intensities.values()) < sum(other._intensities.values()) | ||
143 | 144 | ||||
144 | def __gt__(self, other): | 145 | def __gt__(self, other): | ||
145 | if not isinstance(other, Potion): | 146 | if not isinstance(other, Potion): | ||
146 | return False | 147 | return False | ||
147 | 148 | ||||
148 | return not self == other and not self < other | 149 | return not self == other and not self < other | ||
149 | 150 | ||||
150 | 151 | ||||
151 | class ГоспожатаПоХимия: | 152 | class ГоспожатаПоХимия: | ||
152 | def __init__(self): | 153 | def __init__(self): | ||
153 | self._target = None | 154 | self._target = None | ||
154 | self._target_public_attributes = None | 155 | self._target_public_attributes = None | ||
155 | self._current_tick_duration = None | 156 | self._current_tick_duration = None | ||
156 | 157 | ||||
157 | def _sort_effects_by_molecule_mass(self, effects): | 158 | def _sort_effects_by_molecule_mass(self, effects): | ||
158 | new_effects = list(effects)[:] | 159 | new_effects = list(effects)[:] | ||
159 | return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True) | 160 | return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True) | ||
160 | 161 | ||||
161 | def _get_public_attributes(self, target): | 162 | def _get_public_attributes(self, target): | ||
162 | all_attributes = vars(target) | 163 | all_attributes = vars(target) | ||
163 | return {key: value for key, value in all_attributes.items() if | 164 | return {key: value for key, value in all_attributes.items() if | ||
164 | not key.startswith('_')} | 165 | not key.startswith('_')} | ||
165 | 166 | ||||
166 | def apply(self, target, potion): | 167 | def apply(self, target, potion): | ||
167 | self._target_public_attributes = self._get_public_attributes(target) | 168 | self._target_public_attributes = self._get_public_attributes(target) | ||
168 | self._target = target | 169 | self._target = target | ||
169 | self._current_tick_duration = potion.duration | 170 | self._current_tick_duration = potion.duration | ||
170 | 171 | ||||
171 | for effect in self._sort_effects_by_molecule_mass(potion.effects): | 172 | for effect in self._sort_effects_by_molecule_mass(potion.effects): | ||
172 | potion[effect](target) | 173 | potion[effect](target) | ||
173 | 174 | ||||
174 | if set(potion.depleted) == set(potion.effects): | 175 | if set(potion.depleted) == set(potion.effects): | ||
n | 175 | potion.deplete('Potion is depleted.') | n | 176 | potion.deplete() |
176 | 177 | ||||
177 | def tick(self): | 178 | def tick(self): | ||
178 | if not self._current_tick_duration: | 179 | if not self._current_tick_duration: | ||
179 | raise Exception('No potion applied') | 180 | raise Exception('No potion applied') | ||
180 | 181 | ||||
181 | self._current_tick_duration -= 1 | 182 | self._current_tick_duration -= 1 | ||
182 | 183 | ||||
183 | if self._current_tick_duration != 0: | 184 | if self._current_tick_duration != 0: | ||
184 | return | 185 | return | ||
185 | 186 | ||||
186 | current_public_attributes = self._get_public_attributes(self._target) | 187 | current_public_attributes = self._get_public_attributes(self._target) | ||
187 | for key in current_public_attributes: | 188 | for key in current_public_attributes: | ||
188 | if key in self._target_public_attributes: | 189 | if key in self._target_public_attributes: | ||
189 | setattr(self._target, key, self._target_public_attributes[key]) | 190 | setattr(self._target, key, self._target_public_attributes[key]) | ||
190 | else: | 191 | else: | ||
191 | del self._target.__dict__[key] | 192 | del self._target.__dict__[key] | ||
t | 192 | t | |||
193 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | def times_decorator(func, times): | f | 1 | def times_decorator(func, times): |
2 | def wrapper(target): | 2 | def wrapper(target): | ||
3 | for _ in range(times): | 3 | for _ in range(times): | ||
4 | func(target) | 4 | func(target) | ||
5 | 5 | ||||
6 | return wrapper | 6 | return wrapper | ||
7 | 7 | ||||
8 | 8 | ||||
9 | def is_merged_decorator(func): | 9 | def is_merged_decorator(func): | ||
10 | def wrapper(*args, **kwargs): | 10 | def wrapper(*args, **kwargs): | ||
11 | self = args[0] | 11 | self = args[0] | ||
12 | if self._is_merged: | 12 | if self._is_merged: | ||
13 | raise TypeError('Potion is now part of something bigger than itself.') | 13 | raise TypeError('Potion is now part of something bigger than itself.') | ||
14 | try: | 14 | try: | ||
15 | return func(*args, **kwargs) | 15 | return func(*args, **kwargs) | ||
16 | finally: | 16 | finally: | ||
17 | if func.__name__ != '__getattr__': | 17 | if func.__name__ != '__getattr__': | ||
18 | self._is_merged = True | 18 | self._is_merged = True | ||
19 | 19 | ||||
20 | return wrapper | 20 | return wrapper | ||
21 | 21 | ||||
22 | 22 | ||||
23 | class Potion: | 23 | class Potion: | ||
24 | def __init__(self, effects, duration, intensities=None): | 24 | def __init__(self, effects, duration, intensities=None): | ||
25 | self._is_merged = False | 25 | self._is_merged = False | ||
26 | self._effects = effects | 26 | self._effects = effects | ||
27 | self._intensities = intensities or {effect: 1 for effect in effects} | 27 | self._intensities = intensities or {effect: 1 for effect in effects} | ||
28 | self._depleted = set() | 28 | self._depleted = set() | ||
29 | self._duration = duration | 29 | self._duration = duration | ||
30 | self._depleted_message = 'Effect is depleted' | 30 | self._depleted_message = 'Effect is depleted' | ||
31 | self._is_depleted = False | 31 | self._is_depleted = False | ||
32 | 32 | ||||
33 | @property | 33 | @property | ||
34 | def duration(self): | 34 | def duration(self): | ||
35 | return self._duration | 35 | return self._duration | ||
36 | 36 | ||||
37 | @property | 37 | @property | ||
38 | def effects(self): | 38 | def effects(self): | ||
39 | return list(self._effects.keys()) | 39 | return list(self._effects.keys()) | ||
40 | 40 | ||||
41 | @property | 41 | @property | ||
42 | def depleted(self): | 42 | def depleted(self): | ||
43 | return self._depleted | 43 | return self._depleted | ||
44 | 44 | ||||
45 | def __getitem__(self, item): | 45 | def __getitem__(self, item): | ||
46 | return self.__getattr__(item) | 46 | return self.__getattr__(item) | ||
47 | 47 | ||||
48 | def deplete(self, depleted_message): | 48 | def deplete(self, depleted_message): | ||
49 | self._depleted_message = depleted_message | 49 | self._depleted_message = depleted_message | ||
50 | self._is_depleted = True | 50 | self._is_depleted = True | ||
51 | 51 | ||||
52 | def __repr__(self): | 52 | def __repr__(self): | ||
53 | return f'Potion({self._effects}, {self._duration})' | 53 | return f'Potion({self._effects}, {self._duration})' | ||
54 | 54 | ||||
55 | @is_merged_decorator | 55 | @is_merged_decorator | ||
56 | def __getattr__(self, effect_name): | 56 | def __getattr__(self, effect_name): | ||
57 | if effect_name not in self._effects: | 57 | if effect_name not in self._effects: | ||
58 | raise AttributeError | 58 | raise AttributeError | ||
59 | 59 | ||||
60 | if effect_name in self._depleted or self._is_depleted: | 60 | if effect_name in self._depleted or self._is_depleted: | ||
61 | raise TypeError(self._depleted_message) | 61 | raise TypeError(self._depleted_message) | ||
62 | 62 | ||||
63 | self._depleted.add(effect_name) | 63 | self._depleted.add(effect_name) | ||
64 | return times_decorator(self._effects[effect_name], self._intensities[effect_name]) | 64 | return times_decorator(self._effects[effect_name], self._intensities[effect_name]) | ||
65 | 65 | ||||
66 | @is_merged_decorator | 66 | @is_merged_decorator | ||
67 | def __add__(self, other): | 67 | def __add__(self, other): | ||
68 | if not isinstance(other, Potion): | 68 | if not isinstance(other, Potion): | ||
69 | raise TypeError('Potions can only be combined with other potions') | 69 | raise TypeError('Potions can only be combined with other potions') | ||
70 | 70 | ||||
71 | effects = {**self._effects, **other._effects} | 71 | effects = {**self._effects, **other._effects} | ||
72 | duration = max(self._duration, other._duration) | 72 | duration = max(self._duration, other._duration) | ||
73 | intensities = { | 73 | intensities = { | ||
74 | effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0)) | 74 | effect: (self._intensities.get(effect, 0)) + (other._intensities.get(effect, 0)) | ||
75 | for effect in effects | 75 | for effect in effects | ||
76 | } | 76 | } | ||
77 | return Potion(effects, duration, intensities) | 77 | return Potion(effects, duration, intensities) | ||
78 | 78 | ||||
79 | @is_merged_decorator | 79 | @is_merged_decorator | ||
80 | def __sub__(self, other): | 80 | def __sub__(self, other): | ||
81 | if not isinstance(other, Potion): | 81 | if not isinstance(other, Potion): | ||
82 | raise TypeError('Potions can only be combined with other potions') | 82 | raise TypeError('Potions can only be combined with other potions') | ||
83 | 83 | ||||
t | 84 | if len(other._effects.keys() - self._effects.keys()): | t | 84 | if other._effects.keys() - self._effects.keys() != set(): |
85 | raise TypeError('All effects of the right potion must be present in the left potion') | 85 | raise TypeError('All effects of the right potion must be present in the left potion') | ||
86 | 86 | ||||
87 | duration = self._duration | 87 | duration = self._duration | ||
88 | effects = {} | 88 | effects = {} | ||
89 | intensities = {} | 89 | intensities = {} | ||
90 | for effect in self._effects: | 90 | for effect in self._effects: | ||
91 | if effect in other._effects: | 91 | if effect in other._effects: | ||
92 | new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect) | 92 | new_effect_intensity = self._intensities.get(effect) - other._intensities.get(effect) | ||
93 | if new_effect_intensity > 0: | 93 | if new_effect_intensity > 0: | ||
94 | effects[effect] = self._effects[effect] | 94 | effects[effect] = self._effects[effect] | ||
95 | intensities[effect] = new_effect_intensity | 95 | intensities[effect] = new_effect_intensity | ||
96 | else: | 96 | else: | ||
97 | effects[effect] = self._effects[effect] | 97 | effects[effect] = self._effects[effect] | ||
98 | intensities[effect] = self._intensities[effect] | 98 | intensities[effect] = self._intensities[effect] | ||
99 | 99 | ||||
100 | return Potion(effects, duration, intensities) | 100 | return Potion(effects, duration, intensities) | ||
101 | 101 | ||||
102 | @is_merged_decorator | 102 | @is_merged_decorator | ||
103 | def __mul__(self, intensity_growth): | 103 | def __mul__(self, intensity_growth): | ||
104 | try: | 104 | try: | ||
105 | float(intensity_growth) | 105 | float(intensity_growth) | ||
106 | except ValueError: | 106 | except ValueError: | ||
107 | raise TypeError('Potions can only be multiplied by integers') | 107 | raise TypeError('Potions can only be multiplied by integers') | ||
108 | 108 | ||||
109 | effects = {**self._effects} | 109 | effects = {**self._effects} | ||
110 | duration = self._duration | 110 | duration = self._duration | ||
111 | intensities = { | 111 | intensities = { | ||
112 | effect: round(self._intensities.get(effect, 0) * intensity_growth) | 112 | effect: round(self._intensities.get(effect, 0) * intensity_growth) | ||
113 | for effect in effects | 113 | for effect in effects | ||
114 | } | 114 | } | ||
115 | return Potion(effects, duration, intensities) | 115 | return Potion(effects, duration, intensities) | ||
116 | 116 | ||||
117 | @is_merged_decorator | 117 | @is_merged_decorator | ||
118 | def __truediv__(self, new_potions_count): | 118 | def __truediv__(self, new_potions_count): | ||
119 | try: | 119 | try: | ||
120 | int(new_potions_count) | 120 | int(new_potions_count) | ||
121 | except ValueError: | 121 | except ValueError: | ||
122 | raise TypeError('Potions can only be divided by integers') | 122 | raise TypeError('Potions can only be divided by integers') | ||
123 | 123 | ||||
124 | return tuple([Potion({**self._effects}, | 124 | return tuple([Potion({**self._effects}, | ||
125 | self._duration, | 125 | self._duration, | ||
126 | {effect: round(self._intensities.get(effect, 0) / new_potions_count) | 126 | {effect: round(self._intensities.get(effect, 0) / new_potions_count) | ||
127 | for effect in self._intensities} | 127 | for effect in self._intensities} | ||
128 | ) for _ in range(new_potions_count)]) | 128 | ) for _ in range(new_potions_count)]) | ||
129 | 129 | ||||
130 | def __eq__(self, other): | 130 | def __eq__(self, other): | ||
131 | if not isinstance(other, Potion): | 131 | if not isinstance(other, Potion): | ||
132 | return False | 132 | return False | ||
133 | 133 | ||||
134 | return self._effects.keys() - other._effects.keys() == set() and all( | 134 | return self._effects.keys() - other._effects.keys() == set() and all( | ||
135 | [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()] | 135 | [self._intensities[effect] == other._intensities[effect] for effect in self._effects.keys()] | ||
136 | ) | 136 | ) | ||
137 | 137 | ||||
138 | def __lt__(self, other): | 138 | def __lt__(self, other): | ||
139 | if not isinstance(other, Potion): | 139 | if not isinstance(other, Potion): | ||
140 | return False | 140 | return False | ||
141 | 141 | ||||
142 | return sum(self._intensities.values()) < sum(other._intensities.values()) | 142 | return sum(self._intensities.values()) < sum(other._intensities.values()) | ||
143 | 143 | ||||
144 | def __gt__(self, other): | 144 | def __gt__(self, other): | ||
145 | if not isinstance(other, Potion): | 145 | if not isinstance(other, Potion): | ||
146 | return False | 146 | return False | ||
147 | 147 | ||||
148 | return not self == other and not self < other | 148 | return not self == other and not self < other | ||
149 | 149 | ||||
150 | 150 | ||||
151 | class ГоспожатаПоХимия: | 151 | class ГоспожатаПоХимия: | ||
152 | def __init__(self): | 152 | def __init__(self): | ||
153 | self._target = None | 153 | self._target = None | ||
154 | self._target_public_attributes = None | 154 | self._target_public_attributes = None | ||
155 | self._current_tick_duration = None | 155 | self._current_tick_duration = None | ||
156 | 156 | ||||
157 | def _sort_effects_by_molecule_mass(self, effects): | 157 | def _sort_effects_by_molecule_mass(self, effects): | ||
158 | new_effects = list(effects)[:] | 158 | new_effects = list(effects)[:] | ||
159 | return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True) | 159 | return sorted(new_effects, key=lambda effect: sum(ord(ch) for ch in effect), reverse=True) | ||
160 | 160 | ||||
161 | def _get_public_attributes(self, target): | 161 | def _get_public_attributes(self, target): | ||
162 | all_attributes = vars(target) | 162 | all_attributes = vars(target) | ||
163 | return {key: value for key, value in all_attributes.items() if | 163 | return {key: value for key, value in all_attributes.items() if | ||
164 | not key.startswith('_')} | 164 | not key.startswith('_')} | ||
165 | 165 | ||||
166 | def apply(self, target, potion): | 166 | def apply(self, target, potion): | ||
167 | self._target_public_attributes = self._get_public_attributes(target) | 167 | self._target_public_attributes = self._get_public_attributes(target) | ||
168 | self._target = target | 168 | self._target = target | ||
169 | self._current_tick_duration = potion.duration | 169 | self._current_tick_duration = potion.duration | ||
170 | 170 | ||||
171 | for effect in self._sort_effects_by_molecule_mass(potion.effects): | 171 | for effect in self._sort_effects_by_molecule_mass(potion.effects): | ||
172 | potion[effect](target) | 172 | potion[effect](target) | ||
173 | 173 | ||||
174 | if set(potion.depleted) == set(potion.effects): | 174 | if set(potion.depleted) == set(potion.effects): | ||
175 | potion.deplete('Potion is depleted.') | 175 | potion.deplete('Potion is depleted.') | ||
176 | 176 | ||||
177 | def tick(self): | 177 | def tick(self): | ||
178 | if not self._current_tick_duration: | 178 | if not self._current_tick_duration: | ||
179 | raise Exception('No potion applied') | 179 | raise Exception('No potion applied') | ||
180 | 180 | ||||
181 | self._current_tick_duration -= 1 | 181 | self._current_tick_duration -= 1 | ||
182 | 182 | ||||
183 | if self._current_tick_duration != 0: | 183 | if self._current_tick_duration != 0: | ||
184 | return | 184 | return | ||
185 | 185 | ||||
186 | current_public_attributes = self._get_public_attributes(self._target) | 186 | current_public_attributes = self._get_public_attributes(self._target) | ||
187 | for key in current_public_attributes: | 187 | for key in current_public_attributes: | ||
188 | if key in self._target_public_attributes: | 188 | if key in self._target_public_attributes: | ||
189 | setattr(self._target, key, self._target_public_attributes[key]) | 189 | setattr(self._target, key, self._target_public_attributes[key]) | ||
190 | else: | 190 | else: | ||
191 | del self._target.__dict__[key] | 191 | del self._target.__dict__[key] | ||
192 | 192 | ||||
193 | 193 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
03.12.2023 13:35
03.12.2023 13:42