1import math
2import copy
3
4
5def custom_round(result):
6 fractional_part = result % 1
7
8 if fractional_part == 0:
9 rounded_result = result
10 elif fractional_part <= 0.5:
11 rounded_result = math.floor(result)
12 else:
13 rounded_result = math.ceil(result)
14
15 return int(rounded_result)
16
17
18class FunctionWrapper:
19 """A class used to implement function intensities"""
20
21 def __init__(self, func, number_of_calls):
22 self.func = func
23 self.number_of_calls = number_of_calls
24
25 def __call__(self, *args, **kwargs):
26 for _ in range(self.number_of_calls):
27 self.func(*args, **kwargs)
28
29 def __mul__(self, number):
30 """Multiply a function with a given number"""
31 result_function = FunctionWrapper(self.func, self.number_of_calls)
32 result_function.number_of_calls = custom_round(self.number_of_calls * number)
33 return result_function
34
35 def __add__(self, other):
36 """Combine effects of functions"""
37 result_function = FunctionWrapper(self.func, self.number_of_calls)
38
39 # if the other function hasn't been wrapped yet
40 if not isinstance(other, FunctionWrapper):
41 result_function.number_of_calls += 1
42 else:
43 result_function.number_of_calls += other.number_of_calls
44 return result_function
45
46 def __truediv__(self, number):
47 """Divide effects of a function"""
48 result_function = FunctionWrapper(self.func, self.number_of_calls)
49 result_function.number_of_calls = custom_round(result_function.number_of_calls / number)
50 return result_function
51
52
53class Potion:
54 def __init__(self, effects, duration):
55 # since the functions in the dictionary will be changed (wrapped), deep copy it
56 self.effects = copy.deepcopy(effects)
57 self.duration = duration
58
59 self.usable = True
60 self.is_depleted = False
61 # Store keys of depleted effects here
62 self.depleted_effects = set()
63
64 # Wrap all functions that are still not wrapped to make operations on them easier
65 for key in self.effects:
66 if not isinstance(self.effects[key], FunctionWrapper):
67 wrapped_function = FunctionWrapper(self.effects[key], 1)
68 self.effects[key] = wrapped_function
69
70 def perform_checks(self):
71 if self.is_depleted:
72 raise TypeError("Potion is depleted.")
73 if not self.usable:
74 raise TypeError("Potion is now part of something bigger than itself.")
75
76 def __getattribute__(self, item):
77 if item in object.__getattribute__(self, 'effects'):
78 self.perform_checks()
79 if item not in self.depleted_effects:
80 self.depleted_effects.add(item)
81 if len(self.effects) == len(self.depleted_effects):
82 self.is_depleted = True
83 return object.__getattribute__(self, 'effects')[item]
84 else:
85 raise TypeError("Effect is depleted.")
86 return object.__getattribute__(self, item)
87
88 def __add__(self, other):
89 self.perform_checks()
90 other.perform_checks()
91
92 max_duration = max(self.duration, other.duration)
93
94 # merge the two dictionaries of effects together
95 effects_merged = other.effects.copy()
96 # if there are common keys, their values will be overwritten
97 effects_merged.update(self.effects.copy())
98
99 result_potion = Potion(effects_merged, max_duration)
100
101 # get the common functions for the potions
102 common_names = set(self.effects.keys()) & set(other.effects.keys())
103 for name in common_names:
104 result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls
105
106 self.usable = False
107 other.usable = False
108 return result_potion
109
110 def __mul__(self, number):
111 self.perform_checks()
112
113 multiplied_effects = dict(self.effects)
114
115 for key in multiplied_effects:
116 multiplied_effects[key] = multiplied_effects[key] * number
117
118 multiplied_potion = Potion(multiplied_effects, self.duration)
119
120 self.usable = False
121 return multiplied_potion
122
123 def __sub__(self, other):
124 self.perform_checks()
125 other.perform_checks()
126
127 # Compare the keys of the two potions using set operations
128 if not set(other.effects.keys()).issubset(set(self.effects.keys())):
129 raise TypeError("Different effects in right potion")
130
131 purified = Potion(self.effects, self.duration)
132 common_names = set(self.effects.keys()) & set(other.effects.keys())
133
134 for name in common_names:
135 left_intensity = self.effects[name].number_of_calls
136 right_intensity = other.effects[name].number_of_calls
137
138 if right_intensity >= left_intensity:
139 purified.effects.pop(name)
140 else:
141 purified.effects[name].number_of_calls = left_intensity - right_intensity
142
143 self.usable = False
144 other.usable = False
145 return purified
146
147 def __truediv__(self, number):
148 self.perform_checks()
149
150 divided_effects = {key: value / number for key, value in self.effects.items()}
151 self.usable = False
152 return tuple(Potion(divided_effects, self.duration) for _ in range(number))
153
154 def __eq__(self, other):
155 self.perform_checks()
156 other.perform_checks()
157
158 for key in self.effects:
159 if key not in other.effects:
160 return False
161 if other.effects[key].number_of_calls != self.effects[key].number_of_calls:
162 return False
163 return True
164
165 @staticmethod
166 def calculate_sums(left_potion, right_potion):
167 """Calculate sums of effect intensities for two potions"""
168 sum_left = 0
169 sum_right = 0
170
171 for key in left_potion.effects:
172 sum_left += left_potion.effects[key].number_of_calls
173
174 for key in right_potion.effects:
175 sum_right += right_potion.effects[key].number_of_calls
176
177 return sum_left, sum_right
178
179 def __lt__(self, other):
180 self.perform_checks()
181 other.perform_checks()
182
183 sum_self, sum_other = Potion.calculate_sums(self, other)
184 return sum_self < sum_other
185
186 def __gt__(self, other):
187 self.perform_checks()
188 other.perform_checks()
189
190 sum_self, sum_other = Potion.calculate_sums(self, other)
191 return sum_self > sum_other
192
193 def __ge__(self, other):
194 return NotImplemented
195
196 def __le__(self, other):
197 return NotImplemented
198
199
200class ГоспожатаПоХимия:
201 def __init__(self):
202 # for each target, store its original attributes
203 self.initial_states = dict()
204 # original effects of each potion
205 self.effects = dict()
206 # remaining times for all active potions
207 self.times = dict()
208 # associate each potion with a target
209 self.targets = dict()
210
211 def restore_target_initial_state(self, target):
212 for attribute, value in self.initial_states[id(target)].items():
213 setattr(target, attribute, value)
214
215 def apply(self, target, potion):
216 if potion.is_depleted:
217 raise TypeError("Potion is depleted.")
218
219 if id(target) not in self.initial_states.keys():
220 attributes_before_change = copy.deepcopy(target.__dict__)
221 self.initial_states[id(target)] = attributes_before_change
222
223 self.effects[id(potion)] = []
224 self.times[id(potion)] = potion.duration
225 self.targets[id(potion)] = target
226
227 unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects,
228 key=lambda x: sum(ord(c) for c in x),
229 reverse=True)
230
231 for unused_effect in unused_effects:
232 effect = potion.__getattribute__(unused_effect)
233 self.effects[id(potion)].append(copy.deepcopy(effect))
234 effect(target)
235
236 potion.is_depleted = True
237
238 def reapply_active_potions(self, target):
239 for key in self.effects:
240 if self.times[key] == 0:
241 continue
242 for effect in self.effects[key]:
243 number_of_calls = effect.number_of_calls
244 effect(target)
245 effect.number_of_calls = number_of_calls
246
247 def tick(self):
248 to_remove = []
249
250 for key in self.times:
251 self.times[key] -= 1
252
253 for key in self.times:
254 # restore the old state of 'target' and remove the potion
255 if self.times[key] <= 0:
256 target = self.targets[key]
257 self.restore_target_initial_state(target)
258 to_remove.append(key)
259 self.reapply_active_potions(target)
260
261 for key in to_remove:
262 self.times.pop(key)
263 self.effects.pop(key)
264 self.targets.pop(key)
.F.F..............F.
======================================================================
FAIL: test_depletion (test.TestBasicPotion)
Test depletion of a potion effect.
----------------------------------------------------------------------
TypeError: Potion 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 "Potion is depleted."
======================================================================
FAIL: test_equal (test.TestPotionComparison)
Test equality of potions.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 304, in test_equal
self.assertNotEqual(potion1, potion2)
AssertionError: <solution.Potion object at 0x7f80cf0d8490> == <solution.Potion object at 0x7f80cf0d92a0>
======================================================================
FAIL: 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 490, in test_ticking_multiple_targets
self.assertEqual(target1.int_attr, 5)
AssertionError: 50 != 5
----------------------------------------------------------------------
Ran 20 tests in 0.004s
FAILED (failures=3)
Виктор Бечев
03.12.2023 13:59Като цяло - хубаво, поне за мен четимо решение, браво. :)
|
t | 1 | import math | t | 1 | import math |
2 | import copy | 2 | import copy | ||
3 | 3 | ||||
4 | 4 | ||||
5 | def custom_round(result): | 5 | def custom_round(result): | ||
6 | fractional_part = result % 1 | 6 | fractional_part = result % 1 | ||
7 | 7 | ||||
8 | if fractional_part == 0: | 8 | if fractional_part == 0: | ||
9 | rounded_result = result | 9 | rounded_result = result | ||
10 | elif fractional_part <= 0.5: | 10 | elif fractional_part <= 0.5: | ||
11 | rounded_result = math.floor(result) | 11 | rounded_result = math.floor(result) | ||
12 | else: | 12 | else: | ||
13 | rounded_result = math.ceil(result) | 13 | rounded_result = math.ceil(result) | ||
14 | 14 | ||||
15 | return int(rounded_result) | 15 | return int(rounded_result) | ||
16 | 16 | ||||
17 | 17 | ||||
18 | class FunctionWrapper: | 18 | class FunctionWrapper: | ||
19 | """A class used to implement function intensities""" | 19 | """A class used to implement function intensities""" | ||
20 | 20 | ||||
21 | def __init__(self, func, number_of_calls): | 21 | def __init__(self, func, number_of_calls): | ||
22 | self.func = func | 22 | self.func = func | ||
23 | self.number_of_calls = number_of_calls | 23 | self.number_of_calls = number_of_calls | ||
24 | 24 | ||||
25 | def __call__(self, *args, **kwargs): | 25 | def __call__(self, *args, **kwargs): | ||
26 | for _ in range(self.number_of_calls): | 26 | for _ in range(self.number_of_calls): | ||
27 | self.func(*args, **kwargs) | 27 | self.func(*args, **kwargs) | ||
28 | 28 | ||||
29 | def __mul__(self, number): | 29 | def __mul__(self, number): | ||
30 | """Multiply a function with a given number""" | 30 | """Multiply a function with a given number""" | ||
31 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 31 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
32 | result_function.number_of_calls = custom_round(self.number_of_calls * number) | 32 | result_function.number_of_calls = custom_round(self.number_of_calls * number) | ||
33 | return result_function | 33 | return result_function | ||
34 | 34 | ||||
35 | def __add__(self, other): | 35 | def __add__(self, other): | ||
36 | """Combine effects of functions""" | 36 | """Combine effects of functions""" | ||
37 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 37 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
38 | 38 | ||||
39 | # if the other function hasn't been wrapped yet | 39 | # if the other function hasn't been wrapped yet | ||
40 | if not isinstance(other, FunctionWrapper): | 40 | if not isinstance(other, FunctionWrapper): | ||
41 | result_function.number_of_calls += 1 | 41 | result_function.number_of_calls += 1 | ||
42 | else: | 42 | else: | ||
43 | result_function.number_of_calls += other.number_of_calls | 43 | result_function.number_of_calls += other.number_of_calls | ||
44 | return result_function | 44 | return result_function | ||
45 | 45 | ||||
46 | def __truediv__(self, number): | 46 | def __truediv__(self, number): | ||
47 | """Divide effects of a function""" | 47 | """Divide effects of a function""" | ||
48 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 48 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
49 | result_function.number_of_calls = custom_round(result_function.number_of_calls / number) | 49 | result_function.number_of_calls = custom_round(result_function.number_of_calls / number) | ||
50 | return result_function | 50 | return result_function | ||
51 | 51 | ||||
52 | 52 | ||||
53 | class Potion: | 53 | class Potion: | ||
54 | def __init__(self, effects, duration): | 54 | def __init__(self, effects, duration): | ||
55 | # since the functions in the dictionary will be changed (wrapped), deep copy it | 55 | # since the functions in the dictionary will be changed (wrapped), deep copy it | ||
56 | self.effects = copy.deepcopy(effects) | 56 | self.effects = copy.deepcopy(effects) | ||
57 | self.duration = duration | 57 | self.duration = duration | ||
58 | 58 | ||||
59 | self.usable = True | 59 | self.usable = True | ||
60 | self.is_depleted = False | 60 | self.is_depleted = False | ||
61 | # Store keys of depleted effects here | 61 | # Store keys of depleted effects here | ||
62 | self.depleted_effects = set() | 62 | self.depleted_effects = set() | ||
63 | 63 | ||||
64 | # Wrap all functions that are still not wrapped to make operations on them easier | 64 | # Wrap all functions that are still not wrapped to make operations on them easier | ||
65 | for key in self.effects: | 65 | for key in self.effects: | ||
66 | if not isinstance(self.effects[key], FunctionWrapper): | 66 | if not isinstance(self.effects[key], FunctionWrapper): | ||
67 | wrapped_function = FunctionWrapper(self.effects[key], 1) | 67 | wrapped_function = FunctionWrapper(self.effects[key], 1) | ||
68 | self.effects[key] = wrapped_function | 68 | self.effects[key] = wrapped_function | ||
69 | 69 | ||||
70 | def perform_checks(self): | 70 | def perform_checks(self): | ||
71 | if self.is_depleted: | 71 | if self.is_depleted: | ||
72 | raise TypeError("Potion is depleted.") | 72 | raise TypeError("Potion is depleted.") | ||
73 | if not self.usable: | 73 | if not self.usable: | ||
74 | raise TypeError("Potion is now part of something bigger than itself.") | 74 | raise TypeError("Potion is now part of something bigger than itself.") | ||
75 | 75 | ||||
76 | def __getattribute__(self, item): | 76 | def __getattribute__(self, item): | ||
77 | if item in object.__getattribute__(self, 'effects'): | 77 | if item in object.__getattribute__(self, 'effects'): | ||
78 | self.perform_checks() | 78 | self.perform_checks() | ||
79 | if item not in self.depleted_effects: | 79 | if item not in self.depleted_effects: | ||
80 | self.depleted_effects.add(item) | 80 | self.depleted_effects.add(item) | ||
81 | if len(self.effects) == len(self.depleted_effects): | 81 | if len(self.effects) == len(self.depleted_effects): | ||
82 | self.is_depleted = True | 82 | self.is_depleted = True | ||
83 | return object.__getattribute__(self, 'effects')[item] | 83 | return object.__getattribute__(self, 'effects')[item] | ||
84 | else: | 84 | else: | ||
85 | raise TypeError("Effect is depleted.") | 85 | raise TypeError("Effect is depleted.") | ||
86 | return object.__getattribute__(self, item) | 86 | return object.__getattribute__(self, item) | ||
87 | 87 | ||||
88 | def __add__(self, other): | 88 | def __add__(self, other): | ||
89 | self.perform_checks() | 89 | self.perform_checks() | ||
90 | other.perform_checks() | 90 | other.perform_checks() | ||
91 | 91 | ||||
92 | max_duration = max(self.duration, other.duration) | 92 | max_duration = max(self.duration, other.duration) | ||
93 | 93 | ||||
94 | # merge the two dictionaries of effects together | 94 | # merge the two dictionaries of effects together | ||
95 | effects_merged = other.effects.copy() | 95 | effects_merged = other.effects.copy() | ||
96 | # if there are common keys, their values will be overwritten | 96 | # if there are common keys, their values will be overwritten | ||
97 | effects_merged.update(self.effects.copy()) | 97 | effects_merged.update(self.effects.copy()) | ||
98 | 98 | ||||
99 | result_potion = Potion(effects_merged, max_duration) | 99 | result_potion = Potion(effects_merged, max_duration) | ||
100 | 100 | ||||
101 | # get the common functions for the potions | 101 | # get the common functions for the potions | ||
102 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 102 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
103 | for name in common_names: | 103 | for name in common_names: | ||
104 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | 104 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | ||
105 | 105 | ||||
106 | self.usable = False | 106 | self.usable = False | ||
107 | other.usable = False | 107 | other.usable = False | ||
108 | return result_potion | 108 | return result_potion | ||
109 | 109 | ||||
110 | def __mul__(self, number): | 110 | def __mul__(self, number): | ||
111 | self.perform_checks() | 111 | self.perform_checks() | ||
112 | 112 | ||||
113 | multiplied_effects = dict(self.effects) | 113 | multiplied_effects = dict(self.effects) | ||
114 | 114 | ||||
115 | for key in multiplied_effects: | 115 | for key in multiplied_effects: | ||
116 | multiplied_effects[key] = multiplied_effects[key] * number | 116 | multiplied_effects[key] = multiplied_effects[key] * number | ||
117 | 117 | ||||
118 | multiplied_potion = Potion(multiplied_effects, self.duration) | 118 | multiplied_potion = Potion(multiplied_effects, self.duration) | ||
119 | 119 | ||||
120 | self.usable = False | 120 | self.usable = False | ||
121 | return multiplied_potion | 121 | return multiplied_potion | ||
122 | 122 | ||||
123 | def __sub__(self, other): | 123 | def __sub__(self, other): | ||
124 | self.perform_checks() | 124 | self.perform_checks() | ||
125 | other.perform_checks() | 125 | other.perform_checks() | ||
126 | 126 | ||||
127 | # Compare the keys of the two potions using set operations | 127 | # Compare the keys of the two potions using set operations | ||
128 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | 128 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | ||
129 | raise TypeError("Different effects in right potion") | 129 | raise TypeError("Different effects in right potion") | ||
130 | 130 | ||||
131 | purified = Potion(self.effects, self.duration) | 131 | purified = Potion(self.effects, self.duration) | ||
132 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 132 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
133 | 133 | ||||
134 | for name in common_names: | 134 | for name in common_names: | ||
135 | left_intensity = self.effects[name].number_of_calls | 135 | left_intensity = self.effects[name].number_of_calls | ||
136 | right_intensity = other.effects[name].number_of_calls | 136 | right_intensity = other.effects[name].number_of_calls | ||
137 | 137 | ||||
138 | if right_intensity >= left_intensity: | 138 | if right_intensity >= left_intensity: | ||
139 | purified.effects.pop(name) | 139 | purified.effects.pop(name) | ||
140 | else: | 140 | else: | ||
141 | purified.effects[name].number_of_calls = left_intensity - right_intensity | 141 | purified.effects[name].number_of_calls = left_intensity - right_intensity | ||
142 | 142 | ||||
143 | self.usable = False | 143 | self.usable = False | ||
144 | other.usable = False | 144 | other.usable = False | ||
145 | return purified | 145 | return purified | ||
146 | 146 | ||||
147 | def __truediv__(self, number): | 147 | def __truediv__(self, number): | ||
148 | self.perform_checks() | 148 | self.perform_checks() | ||
149 | 149 | ||||
150 | divided_effects = {key: value / number for key, value in self.effects.items()} | 150 | divided_effects = {key: value / number for key, value in self.effects.items()} | ||
151 | self.usable = False | 151 | self.usable = False | ||
152 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | 152 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | ||
153 | 153 | ||||
154 | def __eq__(self, other): | 154 | def __eq__(self, other): | ||
155 | self.perform_checks() | 155 | self.perform_checks() | ||
156 | other.perform_checks() | 156 | other.perform_checks() | ||
157 | 157 | ||||
158 | for key in self.effects: | 158 | for key in self.effects: | ||
159 | if key not in other.effects: | 159 | if key not in other.effects: | ||
160 | return False | 160 | return False | ||
161 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | 161 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | ||
162 | return False | 162 | return False | ||
163 | return True | 163 | return True | ||
164 | 164 | ||||
165 | @staticmethod | 165 | @staticmethod | ||
166 | def calculate_sums(left_potion, right_potion): | 166 | def calculate_sums(left_potion, right_potion): | ||
167 | """Calculate sums of effect intensities for two potions""" | 167 | """Calculate sums of effect intensities for two potions""" | ||
168 | sum_left = 0 | 168 | sum_left = 0 | ||
169 | sum_right = 0 | 169 | sum_right = 0 | ||
170 | 170 | ||||
171 | for key in left_potion.effects: | 171 | for key in left_potion.effects: | ||
172 | sum_left += left_potion.effects[key].number_of_calls | 172 | sum_left += left_potion.effects[key].number_of_calls | ||
173 | 173 | ||||
174 | for key in right_potion.effects: | 174 | for key in right_potion.effects: | ||
175 | sum_right += right_potion.effects[key].number_of_calls | 175 | sum_right += right_potion.effects[key].number_of_calls | ||
176 | 176 | ||||
177 | return sum_left, sum_right | 177 | return sum_left, sum_right | ||
178 | 178 | ||||
179 | def __lt__(self, other): | 179 | def __lt__(self, other): | ||
180 | self.perform_checks() | 180 | self.perform_checks() | ||
181 | other.perform_checks() | 181 | other.perform_checks() | ||
182 | 182 | ||||
183 | sum_self, sum_other = Potion.calculate_sums(self, other) | 183 | sum_self, sum_other = Potion.calculate_sums(self, other) | ||
184 | return sum_self < sum_other | 184 | return sum_self < sum_other | ||
185 | 185 | ||||
186 | def __gt__(self, other): | 186 | def __gt__(self, other): | ||
187 | self.perform_checks() | 187 | self.perform_checks() | ||
188 | other.perform_checks() | 188 | other.perform_checks() | ||
189 | 189 | ||||
190 | sum_self, sum_other = Potion.calculate_sums(self, other) | 190 | sum_self, sum_other = Potion.calculate_sums(self, other) | ||
191 | return sum_self > sum_other | 191 | return sum_self > sum_other | ||
192 | 192 | ||||
193 | def __ge__(self, other): | 193 | def __ge__(self, other): | ||
194 | return NotImplemented | 194 | return NotImplemented | ||
195 | 195 | ||||
196 | def __le__(self, other): | 196 | def __le__(self, other): | ||
197 | return NotImplemented | 197 | return NotImplemented | ||
198 | 198 | ||||
199 | 199 | ||||
200 | class ГоспожатаПоХимия: | 200 | class ГоспожатаПоХимия: | ||
201 | def __init__(self): | 201 | def __init__(self): | ||
202 | # for each target, store its original attributes | 202 | # for each target, store its original attributes | ||
203 | self.initial_states = dict() | 203 | self.initial_states = dict() | ||
204 | # original effects of each potion | 204 | # original effects of each potion | ||
205 | self.effects = dict() | 205 | self.effects = dict() | ||
206 | # remaining times for all active potions | 206 | # remaining times for all active potions | ||
207 | self.times = dict() | 207 | self.times = dict() | ||
208 | # associate each potion with a target | 208 | # associate each potion with a target | ||
209 | self.targets = dict() | 209 | self.targets = dict() | ||
210 | 210 | ||||
211 | def restore_target_initial_state(self, target): | 211 | def restore_target_initial_state(self, target): | ||
212 | for attribute, value in self.initial_states[id(target)].items(): | 212 | for attribute, value in self.initial_states[id(target)].items(): | ||
213 | setattr(target, attribute, value) | 213 | setattr(target, attribute, value) | ||
214 | 214 | ||||
215 | def apply(self, target, potion): | 215 | def apply(self, target, potion): | ||
216 | if potion.is_depleted: | 216 | if potion.is_depleted: | ||
217 | raise TypeError("Potion is depleted.") | 217 | raise TypeError("Potion is depleted.") | ||
218 | 218 | ||||
219 | if id(target) not in self.initial_states.keys(): | 219 | if id(target) not in self.initial_states.keys(): | ||
220 | attributes_before_change = copy.deepcopy(target.__dict__) | 220 | attributes_before_change = copy.deepcopy(target.__dict__) | ||
221 | self.initial_states[id(target)] = attributes_before_change | 221 | self.initial_states[id(target)] = attributes_before_change | ||
222 | 222 | ||||
223 | self.effects[id(potion)] = [] | 223 | self.effects[id(potion)] = [] | ||
224 | self.times[id(potion)] = potion.duration | 224 | self.times[id(potion)] = potion.duration | ||
225 | self.targets[id(potion)] = target | 225 | self.targets[id(potion)] = target | ||
226 | 226 | ||||
227 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | 227 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | ||
228 | key=lambda x: sum(ord(c) for c in x), | 228 | key=lambda x: sum(ord(c) for c in x), | ||
229 | reverse=True) | 229 | reverse=True) | ||
230 | 230 | ||||
231 | for unused_effect in unused_effects: | 231 | for unused_effect in unused_effects: | ||
232 | effect = potion.__getattribute__(unused_effect) | 232 | effect = potion.__getattribute__(unused_effect) | ||
233 | self.effects[id(potion)].append(copy.deepcopy(effect)) | 233 | self.effects[id(potion)].append(copy.deepcopy(effect)) | ||
234 | effect(target) | 234 | effect(target) | ||
235 | 235 | ||||
236 | potion.is_depleted = True | 236 | potion.is_depleted = True | ||
237 | 237 | ||||
238 | def reapply_active_potions(self, target): | 238 | def reapply_active_potions(self, target): | ||
239 | for key in self.effects: | 239 | for key in self.effects: | ||
240 | if self.times[key] == 0: | 240 | if self.times[key] == 0: | ||
241 | continue | 241 | continue | ||
242 | for effect in self.effects[key]: | 242 | for effect in self.effects[key]: | ||
243 | number_of_calls = effect.number_of_calls | 243 | number_of_calls = effect.number_of_calls | ||
244 | effect(target) | 244 | effect(target) | ||
245 | effect.number_of_calls = number_of_calls | 245 | effect.number_of_calls = number_of_calls | ||
246 | 246 | ||||
247 | def tick(self): | 247 | def tick(self): | ||
248 | to_remove = [] | 248 | to_remove = [] | ||
249 | 249 | ||||
250 | for key in self.times: | 250 | for key in self.times: | ||
251 | self.times[key] -= 1 | 251 | self.times[key] -= 1 | ||
252 | 252 | ||||
253 | for key in self.times: | 253 | for key in self.times: | ||
254 | # restore the old state of 'target' and remove the potion | 254 | # restore the old state of 'target' and remove the potion | ||
255 | if self.times[key] <= 0: | 255 | if self.times[key] <= 0: | ||
256 | target = self.targets[key] | 256 | target = self.targets[key] | ||
257 | self.restore_target_initial_state(target) | 257 | self.restore_target_initial_state(target) | ||
258 | to_remove.append(key) | 258 | to_remove.append(key) | ||
259 | self.reapply_active_potions(target) | 259 | self.reapply_active_potions(target) | ||
260 | 260 | ||||
261 | for key in to_remove: | 261 | for key in to_remove: | ||
262 | self.times.pop(key) | 262 | self.times.pop(key) | ||
263 | self.effects.pop(key) | 263 | self.effects.pop(key) | ||
264 | self.targets.pop(key) | 264 | self.targets.pop(key) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | import copy | 2 | import copy | ||
3 | 3 | ||||
4 | 4 | ||||
5 | def custom_round(result): | 5 | def custom_round(result): | ||
6 | fractional_part = result % 1 | 6 | fractional_part = result % 1 | ||
7 | 7 | ||||
8 | if fractional_part == 0: | 8 | if fractional_part == 0: | ||
9 | rounded_result = result | 9 | rounded_result = result | ||
10 | elif fractional_part <= 0.5: | 10 | elif fractional_part <= 0.5: | ||
11 | rounded_result = math.floor(result) | 11 | rounded_result = math.floor(result) | ||
12 | else: | 12 | else: | ||
13 | rounded_result = math.ceil(result) | 13 | rounded_result = math.ceil(result) | ||
14 | 14 | ||||
15 | return int(rounded_result) | 15 | return int(rounded_result) | ||
16 | 16 | ||||
17 | 17 | ||||
18 | class FunctionWrapper: | 18 | class FunctionWrapper: | ||
19 | """A class used to implement function intensities""" | 19 | """A class used to implement function intensities""" | ||
20 | 20 | ||||
21 | def __init__(self, func, number_of_calls): | 21 | def __init__(self, func, number_of_calls): | ||
22 | self.func = func | 22 | self.func = func | ||
23 | self.number_of_calls = number_of_calls | 23 | self.number_of_calls = number_of_calls | ||
24 | 24 | ||||
25 | def __call__(self, *args, **kwargs): | 25 | def __call__(self, *args, **kwargs): | ||
26 | for _ in range(self.number_of_calls): | 26 | for _ in range(self.number_of_calls): | ||
27 | self.func(*args, **kwargs) | 27 | self.func(*args, **kwargs) | ||
28 | 28 | ||||
29 | def __mul__(self, number): | 29 | def __mul__(self, number): | ||
30 | """Multiply a function with a given number""" | 30 | """Multiply a function with a given number""" | ||
31 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 31 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
32 | result_function.number_of_calls = custom_round(self.number_of_calls * number) | 32 | result_function.number_of_calls = custom_round(self.number_of_calls * number) | ||
33 | return result_function | 33 | return result_function | ||
34 | 34 | ||||
35 | def __add__(self, other): | 35 | def __add__(self, other): | ||
36 | """Combine effects of functions""" | 36 | """Combine effects of functions""" | ||
37 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 37 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
38 | 38 | ||||
39 | # if the other function hasn't been wrapped yet | 39 | # if the other function hasn't been wrapped yet | ||
40 | if not isinstance(other, FunctionWrapper): | 40 | if not isinstance(other, FunctionWrapper): | ||
41 | result_function.number_of_calls += 1 | 41 | result_function.number_of_calls += 1 | ||
42 | else: | 42 | else: | ||
43 | result_function.number_of_calls += other.number_of_calls | 43 | result_function.number_of_calls += other.number_of_calls | ||
44 | return result_function | 44 | return result_function | ||
45 | 45 | ||||
46 | def __truediv__(self, number): | 46 | def __truediv__(self, number): | ||
47 | """Divide effects of a function""" | 47 | """Divide effects of a function""" | ||
48 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 48 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
49 | result_function.number_of_calls = custom_round(result_function.number_of_calls / number) | 49 | result_function.number_of_calls = custom_round(result_function.number_of_calls / number) | ||
50 | return result_function | 50 | return result_function | ||
51 | 51 | ||||
52 | 52 | ||||
53 | class Potion: | 53 | class Potion: | ||
54 | def __init__(self, effects, duration): | 54 | def __init__(self, effects, duration): | ||
55 | # since the functions in the dictionary will be changed (wrapped), deep copy it | 55 | # since the functions in the dictionary will be changed (wrapped), deep copy it | ||
56 | self.effects = copy.deepcopy(effects) | 56 | self.effects = copy.deepcopy(effects) | ||
57 | self.duration = duration | 57 | self.duration = duration | ||
58 | 58 | ||||
59 | self.usable = True | 59 | self.usable = True | ||
60 | self.is_depleted = False | 60 | self.is_depleted = False | ||
61 | # Store keys of depleted effects here | 61 | # Store keys of depleted effects here | ||
62 | self.depleted_effects = set() | 62 | self.depleted_effects = set() | ||
63 | 63 | ||||
64 | # Wrap all functions that are still not wrapped to make operations on them easier | 64 | # Wrap all functions that are still not wrapped to make operations on them easier | ||
65 | for key in self.effects: | 65 | for key in self.effects: | ||
66 | if not isinstance(self.effects[key], FunctionWrapper): | 66 | if not isinstance(self.effects[key], FunctionWrapper): | ||
67 | wrapped_function = FunctionWrapper(self.effects[key], 1) | 67 | wrapped_function = FunctionWrapper(self.effects[key], 1) | ||
68 | self.effects[key] = wrapped_function | 68 | self.effects[key] = wrapped_function | ||
69 | 69 | ||||
70 | def perform_checks(self): | 70 | def perform_checks(self): | ||
71 | if self.is_depleted: | 71 | if self.is_depleted: | ||
72 | raise TypeError("Potion is depleted.") | 72 | raise TypeError("Potion is depleted.") | ||
73 | if not self.usable: | 73 | if not self.usable: | ||
74 | raise TypeError("Potion is now part of something bigger than itself.") | 74 | raise TypeError("Potion is now part of something bigger than itself.") | ||
75 | 75 | ||||
76 | def __getattribute__(self, item): | 76 | def __getattribute__(self, item): | ||
77 | if item in object.__getattribute__(self, 'effects'): | 77 | if item in object.__getattribute__(self, 'effects'): | ||
78 | self.perform_checks() | 78 | self.perform_checks() | ||
79 | if item not in self.depleted_effects: | 79 | if item not in self.depleted_effects: | ||
80 | self.depleted_effects.add(item) | 80 | self.depleted_effects.add(item) | ||
81 | if len(self.effects) == len(self.depleted_effects): | 81 | if len(self.effects) == len(self.depleted_effects): | ||
82 | self.is_depleted = True | 82 | self.is_depleted = True | ||
83 | return object.__getattribute__(self, 'effects')[item] | 83 | return object.__getattribute__(self, 'effects')[item] | ||
84 | else: | 84 | else: | ||
85 | raise TypeError("Effect is depleted.") | 85 | raise TypeError("Effect is depleted.") | ||
86 | return object.__getattribute__(self, item) | 86 | return object.__getattribute__(self, item) | ||
87 | 87 | ||||
88 | def __add__(self, other): | 88 | def __add__(self, other): | ||
89 | self.perform_checks() | 89 | self.perform_checks() | ||
n | n | 90 | other.perform_checks() | ||
90 | 91 | ||||
91 | max_duration = max(self.duration, other.duration) | 92 | max_duration = max(self.duration, other.duration) | ||
92 | 93 | ||||
93 | # merge the two dictionaries of effects together | 94 | # merge the two dictionaries of effects together | ||
94 | effects_merged = other.effects.copy() | 95 | effects_merged = other.effects.copy() | ||
95 | # if there are common keys, their values will be overwritten | 96 | # if there are common keys, their values will be overwritten | ||
96 | effects_merged.update(self.effects.copy()) | 97 | effects_merged.update(self.effects.copy()) | ||
97 | 98 | ||||
98 | result_potion = Potion(effects_merged, max_duration) | 99 | result_potion = Potion(effects_merged, max_duration) | ||
99 | 100 | ||||
100 | # get the common functions for the potions | 101 | # get the common functions for the potions | ||
101 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 102 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
102 | for name in common_names: | 103 | for name in common_names: | ||
103 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | 104 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | ||
104 | 105 | ||||
105 | self.usable = False | 106 | self.usable = False | ||
106 | other.usable = False | 107 | other.usable = False | ||
107 | return result_potion | 108 | return result_potion | ||
108 | 109 | ||||
109 | def __mul__(self, number): | 110 | def __mul__(self, number): | ||
110 | self.perform_checks() | 111 | self.perform_checks() | ||
111 | 112 | ||||
112 | multiplied_effects = dict(self.effects) | 113 | multiplied_effects = dict(self.effects) | ||
113 | 114 | ||||
114 | for key in multiplied_effects: | 115 | for key in multiplied_effects: | ||
115 | multiplied_effects[key] = multiplied_effects[key] * number | 116 | multiplied_effects[key] = multiplied_effects[key] * number | ||
116 | 117 | ||||
117 | multiplied_potion = Potion(multiplied_effects, self.duration) | 118 | multiplied_potion = Potion(multiplied_effects, self.duration) | ||
118 | 119 | ||||
119 | self.usable = False | 120 | self.usable = False | ||
120 | return multiplied_potion | 121 | return multiplied_potion | ||
121 | 122 | ||||
122 | def __sub__(self, other): | 123 | def __sub__(self, other): | ||
123 | self.perform_checks() | 124 | self.perform_checks() | ||
n | n | 125 | other.perform_checks() | ||
124 | 126 | ||||
125 | # Compare the keys of the two potions using set operations | 127 | # Compare the keys of the two potions using set operations | ||
126 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | 128 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | ||
127 | raise TypeError("Different effects in right potion") | 129 | raise TypeError("Different effects in right potion") | ||
128 | 130 | ||||
129 | purified = Potion(self.effects, self.duration) | 131 | purified = Potion(self.effects, self.duration) | ||
130 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 132 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
131 | 133 | ||||
132 | for name in common_names: | 134 | for name in common_names: | ||
133 | left_intensity = self.effects[name].number_of_calls | 135 | left_intensity = self.effects[name].number_of_calls | ||
134 | right_intensity = other.effects[name].number_of_calls | 136 | right_intensity = other.effects[name].number_of_calls | ||
135 | 137 | ||||
136 | if right_intensity >= left_intensity: | 138 | if right_intensity >= left_intensity: | ||
137 | purified.effects.pop(name) | 139 | purified.effects.pop(name) | ||
138 | else: | 140 | else: | ||
139 | purified.effects[name].number_of_calls = left_intensity - right_intensity | 141 | purified.effects[name].number_of_calls = left_intensity - right_intensity | ||
140 | 142 | ||||
141 | self.usable = False | 143 | self.usable = False | ||
142 | other.usable = False | 144 | other.usable = False | ||
143 | return purified | 145 | return purified | ||
144 | 146 | ||||
145 | def __truediv__(self, number): | 147 | def __truediv__(self, number): | ||
146 | self.perform_checks() | 148 | self.perform_checks() | ||
147 | 149 | ||||
148 | divided_effects = {key: value / number for key, value in self.effects.items()} | 150 | divided_effects = {key: value / number for key, value in self.effects.items()} | ||
149 | self.usable = False | 151 | self.usable = False | ||
150 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | 152 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | ||
151 | 153 | ||||
152 | def __eq__(self, other): | 154 | def __eq__(self, other): | ||
n | n | 155 | self.perform_checks() | ||
156 | other.perform_checks() | ||||
157 | |||||
153 | for key in self.effects: | 158 | for key in self.effects: | ||
154 | if key not in other.effects: | 159 | if key not in other.effects: | ||
155 | return False | 160 | return False | ||
156 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | 161 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | ||
157 | return False | 162 | return False | ||
158 | return True | 163 | return True | ||
159 | 164 | ||||
160 | @staticmethod | 165 | @staticmethod | ||
161 | def calculate_sums(left_potion, right_potion): | 166 | def calculate_sums(left_potion, right_potion): | ||
162 | """Calculate sums of effect intensities for two potions""" | 167 | """Calculate sums of effect intensities for two potions""" | ||
163 | sum_left = 0 | 168 | sum_left = 0 | ||
164 | sum_right = 0 | 169 | sum_right = 0 | ||
165 | 170 | ||||
166 | for key in left_potion.effects: | 171 | for key in left_potion.effects: | ||
167 | sum_left += left_potion.effects[key].number_of_calls | 172 | sum_left += left_potion.effects[key].number_of_calls | ||
168 | 173 | ||||
169 | for key in right_potion.effects: | 174 | for key in right_potion.effects: | ||
170 | sum_right += right_potion.effects[key].number_of_calls | 175 | sum_right += right_potion.effects[key].number_of_calls | ||
171 | 176 | ||||
172 | return sum_left, sum_right | 177 | return sum_left, sum_right | ||
173 | 178 | ||||
174 | def __lt__(self, other): | 179 | def __lt__(self, other): | ||
n | n | 180 | self.perform_checks() | ||
181 | other.perform_checks() | ||||
182 | |||||
175 | sum_self, sum_other = Potion.calculate_sums(self, other) | 183 | sum_self, sum_other = Potion.calculate_sums(self, other) | ||
176 | return sum_self < sum_other | 184 | return sum_self < sum_other | ||
177 | 185 | ||||
178 | def __gt__(self, other): | 186 | def __gt__(self, other): | ||
t | t | 187 | self.perform_checks() | ||
188 | other.perform_checks() | ||||
189 | |||||
179 | sum_self, sum_other = Potion.calculate_sums(self, other) | 190 | sum_self, sum_other = Potion.calculate_sums(self, other) | ||
180 | return sum_self > sum_other | 191 | return sum_self > sum_other | ||
181 | 192 | ||||
182 | def __ge__(self, other): | 193 | def __ge__(self, other): | ||
183 | return NotImplemented | 194 | return NotImplemented | ||
184 | 195 | ||||
185 | def __le__(self, other): | 196 | def __le__(self, other): | ||
186 | return NotImplemented | 197 | return NotImplemented | ||
187 | 198 | ||||
188 | 199 | ||||
189 | class ГоспожатаПоХимия: | 200 | class ГоспожатаПоХимия: | ||
190 | def __init__(self): | 201 | def __init__(self): | ||
191 | # for each target, store its original attributes | 202 | # for each target, store its original attributes | ||
192 | self.initial_states = dict() | 203 | self.initial_states = dict() | ||
193 | # original effects of each potion | 204 | # original effects of each potion | ||
194 | self.effects = dict() | 205 | self.effects = dict() | ||
195 | # remaining times for all active potions | 206 | # remaining times for all active potions | ||
196 | self.times = dict() | 207 | self.times = dict() | ||
197 | # associate each potion with a target | 208 | # associate each potion with a target | ||
198 | self.targets = dict() | 209 | self.targets = dict() | ||
199 | 210 | ||||
200 | def restore_target_initial_state(self, target): | 211 | def restore_target_initial_state(self, target): | ||
201 | for attribute, value in self.initial_states[id(target)].items(): | 212 | for attribute, value in self.initial_states[id(target)].items(): | ||
202 | setattr(target, attribute, value) | 213 | setattr(target, attribute, value) | ||
203 | 214 | ||||
204 | def apply(self, target, potion): | 215 | def apply(self, target, potion): | ||
205 | if potion.is_depleted: | 216 | if potion.is_depleted: | ||
206 | raise TypeError("Potion is depleted.") | 217 | raise TypeError("Potion is depleted.") | ||
207 | 218 | ||||
208 | if id(target) not in self.initial_states.keys(): | 219 | if id(target) not in self.initial_states.keys(): | ||
209 | attributes_before_change = copy.deepcopy(target.__dict__) | 220 | attributes_before_change = copy.deepcopy(target.__dict__) | ||
210 | self.initial_states[id(target)] = attributes_before_change | 221 | self.initial_states[id(target)] = attributes_before_change | ||
211 | 222 | ||||
212 | self.effects[id(potion)] = [] | 223 | self.effects[id(potion)] = [] | ||
213 | self.times[id(potion)] = potion.duration | 224 | self.times[id(potion)] = potion.duration | ||
214 | self.targets[id(potion)] = target | 225 | self.targets[id(potion)] = target | ||
215 | 226 | ||||
216 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | 227 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | ||
217 | key=lambda x: sum(ord(c) for c in x), | 228 | key=lambda x: sum(ord(c) for c in x), | ||
218 | reverse=True) | 229 | reverse=True) | ||
219 | 230 | ||||
220 | for unused_effect in unused_effects: | 231 | for unused_effect in unused_effects: | ||
221 | effect = potion.__getattribute__(unused_effect) | 232 | effect = potion.__getattribute__(unused_effect) | ||
222 | self.effects[id(potion)].append(copy.deepcopy(effect)) | 233 | self.effects[id(potion)].append(copy.deepcopy(effect)) | ||
223 | effect(target) | 234 | effect(target) | ||
224 | 235 | ||||
225 | potion.is_depleted = True | 236 | potion.is_depleted = True | ||
226 | 237 | ||||
227 | def reapply_active_potions(self, target): | 238 | def reapply_active_potions(self, target): | ||
228 | for key in self.effects: | 239 | for key in self.effects: | ||
229 | if self.times[key] == 0: | 240 | if self.times[key] == 0: | ||
230 | continue | 241 | continue | ||
231 | for effect in self.effects[key]: | 242 | for effect in self.effects[key]: | ||
232 | number_of_calls = effect.number_of_calls | 243 | number_of_calls = effect.number_of_calls | ||
233 | effect(target) | 244 | effect(target) | ||
234 | effect.number_of_calls = number_of_calls | 245 | effect.number_of_calls = number_of_calls | ||
235 | 246 | ||||
236 | def tick(self): | 247 | def tick(self): | ||
237 | to_remove = [] | 248 | to_remove = [] | ||
238 | 249 | ||||
239 | for key in self.times: | 250 | for key in self.times: | ||
240 | self.times[key] -= 1 | 251 | self.times[key] -= 1 | ||
241 | 252 | ||||
242 | for key in self.times: | 253 | for key in self.times: | ||
243 | # restore the old state of 'target' and remove the potion | 254 | # restore the old state of 'target' and remove the potion | ||
244 | if self.times[key] <= 0: | 255 | if self.times[key] <= 0: | ||
245 | target = self.targets[key] | 256 | target = self.targets[key] | ||
246 | self.restore_target_initial_state(target) | 257 | self.restore_target_initial_state(target) | ||
247 | to_remove.append(key) | 258 | to_remove.append(key) | ||
248 | self.reapply_active_potions(target) | 259 | self.reapply_active_potions(target) | ||
249 | 260 | ||||
250 | for key in to_remove: | 261 | for key in to_remove: | ||
251 | self.times.pop(key) | 262 | self.times.pop(key) | ||
252 | self.effects.pop(key) | 263 | self.effects.pop(key) | ||
253 | self.targets.pop(key) | 264 | self.targets.pop(key) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | import copy | 2 | import copy | ||
3 | 3 | ||||
4 | 4 | ||||
5 | def custom_round(result): | 5 | def custom_round(result): | ||
6 | fractional_part = result % 1 | 6 | fractional_part = result % 1 | ||
7 | 7 | ||||
8 | if fractional_part == 0: | 8 | if fractional_part == 0: | ||
9 | rounded_result = result | 9 | rounded_result = result | ||
10 | elif fractional_part <= 0.5: | 10 | elif fractional_part <= 0.5: | ||
11 | rounded_result = math.floor(result) | 11 | rounded_result = math.floor(result) | ||
12 | else: | 12 | else: | ||
13 | rounded_result = math.ceil(result) | 13 | rounded_result = math.ceil(result) | ||
14 | 14 | ||||
15 | return int(rounded_result) | 15 | return int(rounded_result) | ||
16 | 16 | ||||
17 | 17 | ||||
18 | class FunctionWrapper: | 18 | class FunctionWrapper: | ||
19 | """A class used to implement function intensities""" | 19 | """A class used to implement function intensities""" | ||
20 | 20 | ||||
21 | def __init__(self, func, number_of_calls): | 21 | def __init__(self, func, number_of_calls): | ||
22 | self.func = func | 22 | self.func = func | ||
23 | self.number_of_calls = number_of_calls | 23 | self.number_of_calls = number_of_calls | ||
24 | 24 | ||||
25 | def __call__(self, *args, **kwargs): | 25 | def __call__(self, *args, **kwargs): | ||
26 | for _ in range(self.number_of_calls): | 26 | for _ in range(self.number_of_calls): | ||
27 | self.func(*args, **kwargs) | 27 | self.func(*args, **kwargs) | ||
28 | 28 | ||||
29 | def __mul__(self, number): | 29 | def __mul__(self, number): | ||
30 | """Multiply a function with a given number""" | 30 | """Multiply a function with a given number""" | ||
31 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 31 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
32 | result_function.number_of_calls = custom_round(self.number_of_calls * number) | 32 | result_function.number_of_calls = custom_round(self.number_of_calls * number) | ||
33 | return result_function | 33 | return result_function | ||
34 | 34 | ||||
35 | def __add__(self, other): | 35 | def __add__(self, other): | ||
36 | """Combine effects of functions""" | 36 | """Combine effects of functions""" | ||
37 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 37 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
38 | 38 | ||||
39 | # if the other function hasn't been wrapped yet | 39 | # if the other function hasn't been wrapped yet | ||
40 | if not isinstance(other, FunctionWrapper): | 40 | if not isinstance(other, FunctionWrapper): | ||
41 | result_function.number_of_calls += 1 | 41 | result_function.number_of_calls += 1 | ||
42 | else: | 42 | else: | ||
43 | result_function.number_of_calls += other.number_of_calls | 43 | result_function.number_of_calls += other.number_of_calls | ||
44 | return result_function | 44 | return result_function | ||
45 | 45 | ||||
46 | def __truediv__(self, number): | 46 | def __truediv__(self, number): | ||
47 | """Divide effects of a function""" | 47 | """Divide effects of a function""" | ||
48 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 48 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
49 | result_function.number_of_calls = custom_round(result_function.number_of_calls / number) | 49 | result_function.number_of_calls = custom_round(result_function.number_of_calls / number) | ||
50 | return result_function | 50 | return result_function | ||
51 | 51 | ||||
52 | 52 | ||||
53 | class Potion: | 53 | class Potion: | ||
54 | def __init__(self, effects, duration): | 54 | def __init__(self, effects, duration): | ||
n | n | 55 | # since the functions in the dictionary will be changed (wrapped), deep copy it | ||
55 | self.effects = copy.deepcopy(effects) | 56 | self.effects = copy.deepcopy(effects) | ||
56 | self.duration = duration | 57 | self.duration = duration | ||
57 | 58 | ||||
58 | self.usable = True | 59 | self.usable = True | ||
59 | self.is_depleted = False | 60 | self.is_depleted = False | ||
60 | # Store keys of depleted effects here | 61 | # Store keys of depleted effects here | ||
61 | self.depleted_effects = set() | 62 | self.depleted_effects = set() | ||
62 | 63 | ||||
63 | # Wrap all functions that are still not wrapped to make operations on them easier | 64 | # Wrap all functions that are still not wrapped to make operations on them easier | ||
64 | for key in self.effects: | 65 | for key in self.effects: | ||
65 | if not isinstance(self.effects[key], FunctionWrapper): | 66 | if not isinstance(self.effects[key], FunctionWrapper): | ||
66 | wrapped_function = FunctionWrapper(self.effects[key], 1) | 67 | wrapped_function = FunctionWrapper(self.effects[key], 1) | ||
67 | self.effects[key] = wrapped_function | 68 | self.effects[key] = wrapped_function | ||
68 | 69 | ||||
69 | def perform_checks(self): | 70 | def perform_checks(self): | ||
70 | if self.is_depleted: | 71 | if self.is_depleted: | ||
71 | raise TypeError("Potion is depleted.") | 72 | raise TypeError("Potion is depleted.") | ||
72 | if not self.usable: | 73 | if not self.usable: | ||
73 | raise TypeError("Potion is now part of something bigger than itself.") | 74 | raise TypeError("Potion is now part of something bigger than itself.") | ||
74 | 75 | ||||
75 | def __getattribute__(self, item): | 76 | def __getattribute__(self, item): | ||
76 | if item in object.__getattribute__(self, 'effects'): | 77 | if item in object.__getattribute__(self, 'effects'): | ||
77 | self.perform_checks() | 78 | self.perform_checks() | ||
78 | if item not in self.depleted_effects: | 79 | if item not in self.depleted_effects: | ||
79 | self.depleted_effects.add(item) | 80 | self.depleted_effects.add(item) | ||
80 | if len(self.effects) == len(self.depleted_effects): | 81 | if len(self.effects) == len(self.depleted_effects): | ||
81 | self.is_depleted = True | 82 | self.is_depleted = True | ||
82 | return object.__getattribute__(self, 'effects')[item] | 83 | return object.__getattribute__(self, 'effects')[item] | ||
83 | else: | 84 | else: | ||
84 | raise TypeError("Effect is depleted.") | 85 | raise TypeError("Effect is depleted.") | ||
85 | return object.__getattribute__(self, item) | 86 | return object.__getattribute__(self, item) | ||
86 | 87 | ||||
87 | def __add__(self, other): | 88 | def __add__(self, other): | ||
88 | self.perform_checks() | 89 | self.perform_checks() | ||
89 | 90 | ||||
90 | max_duration = max(self.duration, other.duration) | 91 | max_duration = max(self.duration, other.duration) | ||
91 | 92 | ||||
92 | # merge the two dictionaries of effects together | 93 | # merge the two dictionaries of effects together | ||
93 | effects_merged = other.effects.copy() | 94 | effects_merged = other.effects.copy() | ||
94 | # if there are common keys, their values will be overwritten | 95 | # if there are common keys, their values will be overwritten | ||
95 | effects_merged.update(self.effects.copy()) | 96 | effects_merged.update(self.effects.copy()) | ||
96 | 97 | ||||
97 | result_potion = Potion(effects_merged, max_duration) | 98 | result_potion = Potion(effects_merged, max_duration) | ||
98 | 99 | ||||
n | 99 | # Get the common functions for the potions | n | 100 | # get the common functions for the potions |
100 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 101 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
101 | for name in common_names: | 102 | for name in common_names: | ||
102 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | 103 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | ||
103 | 104 | ||||
104 | self.usable = False | 105 | self.usable = False | ||
105 | other.usable = False | 106 | other.usable = False | ||
n | 106 | n | |||
107 | return result_potion | 107 | return result_potion | ||
108 | 108 | ||||
109 | def __mul__(self, number): | 109 | def __mul__(self, number): | ||
110 | self.perform_checks() | 110 | self.perform_checks() | ||
111 | 111 | ||||
112 | multiplied_effects = dict(self.effects) | 112 | multiplied_effects = dict(self.effects) | ||
113 | 113 | ||||
114 | for key in multiplied_effects: | 114 | for key in multiplied_effects: | ||
115 | multiplied_effects[key] = multiplied_effects[key] * number | 115 | multiplied_effects[key] = multiplied_effects[key] * number | ||
116 | 116 | ||||
117 | multiplied_potion = Potion(multiplied_effects, self.duration) | 117 | multiplied_potion = Potion(multiplied_effects, self.duration) | ||
118 | 118 | ||||
119 | self.usable = False | 119 | self.usable = False | ||
120 | return multiplied_potion | 120 | return multiplied_potion | ||
121 | 121 | ||||
122 | def __sub__(self, other): | 122 | def __sub__(self, other): | ||
123 | self.perform_checks() | 123 | self.perform_checks() | ||
124 | 124 | ||||
125 | # Compare the keys of the two potions using set operations | 125 | # Compare the keys of the two potions using set operations | ||
126 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | 126 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | ||
127 | raise TypeError("Different effects in right potion") | 127 | raise TypeError("Different effects in right potion") | ||
128 | 128 | ||||
129 | purified = Potion(self.effects, self.duration) | 129 | purified = Potion(self.effects, self.duration) | ||
130 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 130 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
131 | 131 | ||||
132 | for name in common_names: | 132 | for name in common_names: | ||
133 | left_intensity = self.effects[name].number_of_calls | 133 | left_intensity = self.effects[name].number_of_calls | ||
134 | right_intensity = other.effects[name].number_of_calls | 134 | right_intensity = other.effects[name].number_of_calls | ||
135 | 135 | ||||
136 | if right_intensity >= left_intensity: | 136 | if right_intensity >= left_intensity: | ||
137 | purified.effects.pop(name) | 137 | purified.effects.pop(name) | ||
138 | else: | 138 | else: | ||
139 | purified.effects[name].number_of_calls = left_intensity - right_intensity | 139 | purified.effects[name].number_of_calls = left_intensity - right_intensity | ||
140 | 140 | ||||
141 | self.usable = False | 141 | self.usable = False | ||
142 | other.usable = False | 142 | other.usable = False | ||
n | 143 | n | |||
144 | return purified | 143 | return purified | ||
145 | 144 | ||||
146 | def __truediv__(self, number): | 145 | def __truediv__(self, number): | ||
147 | self.perform_checks() | 146 | self.perform_checks() | ||
148 | 147 | ||||
149 | divided_effects = {key: value / number for key, value in self.effects.items()} | 148 | divided_effects = {key: value / number for key, value in self.effects.items()} | ||
150 | self.usable = False | 149 | self.usable = False | ||
151 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | 150 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | ||
152 | 151 | ||||
153 | def __eq__(self, other): | 152 | def __eq__(self, other): | ||
154 | for key in self.effects: | 153 | for key in self.effects: | ||
155 | if key not in other.effects: | 154 | if key not in other.effects: | ||
156 | return False | 155 | return False | ||
157 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | 156 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | ||
158 | return False | 157 | return False | ||
159 | return True | 158 | return True | ||
160 | 159 | ||||
161 | @staticmethod | 160 | @staticmethod | ||
162 | def calculate_sums(left_potion, right_potion): | 161 | def calculate_sums(left_potion, right_potion): | ||
n | n | 162 | """Calculate sums of effect intensities for two potions""" | ||
163 | sum_left = 0 | 163 | sum_left = 0 | ||
164 | sum_right = 0 | 164 | sum_right = 0 | ||
165 | 165 | ||||
166 | for key in left_potion.effects: | 166 | for key in left_potion.effects: | ||
167 | sum_left += left_potion.effects[key].number_of_calls | 167 | sum_left += left_potion.effects[key].number_of_calls | ||
168 | 168 | ||||
169 | for key in right_potion.effects: | 169 | for key in right_potion.effects: | ||
170 | sum_right += right_potion.effects[key].number_of_calls | 170 | sum_right += right_potion.effects[key].number_of_calls | ||
171 | 171 | ||||
172 | return sum_left, sum_right | 172 | return sum_left, sum_right | ||
173 | 173 | ||||
174 | def __lt__(self, other): | 174 | def __lt__(self, other): | ||
175 | sum_self, sum_other = Potion.calculate_sums(self, other) | 175 | sum_self, sum_other = Potion.calculate_sums(self, other) | ||
176 | return sum_self < sum_other | 176 | return sum_self < sum_other | ||
177 | 177 | ||||
178 | def __gt__(self, other): | 178 | def __gt__(self, other): | ||
179 | sum_self, sum_other = Potion.calculate_sums(self, other) | 179 | sum_self, sum_other = Potion.calculate_sums(self, other) | ||
180 | return sum_self > sum_other | 180 | return sum_self > sum_other | ||
181 | 181 | ||||
182 | def __ge__(self, other): | 182 | def __ge__(self, other): | ||
183 | return NotImplemented | 183 | return NotImplemented | ||
184 | 184 | ||||
185 | def __le__(self, other): | 185 | def __le__(self, other): | ||
186 | return NotImplemented | 186 | return NotImplemented | ||
187 | 187 | ||||
188 | 188 | ||||
189 | class ГоспожатаПоХимия: | 189 | class ГоспожатаПоХимия: | ||
190 | def __init__(self): | 190 | def __init__(self): | ||
n | 191 | self.time = 0 | n | ||
192 | |||||
193 | # for each target, store its original attributes | 191 | # for each target, store its original attributes | ||
194 | self.initial_states = dict() | 192 | self.initial_states = dict() | ||
195 | # original effects of each potion | 193 | # original effects of each potion | ||
196 | self.effects = dict() | 194 | self.effects = dict() | ||
197 | # remaining times for all active potions | 195 | # remaining times for all active potions | ||
198 | self.times = dict() | 196 | self.times = dict() | ||
199 | # associate each potion with a target | 197 | # associate each potion with a target | ||
200 | self.targets = dict() | 198 | self.targets = dict() | ||
201 | 199 | ||||
202 | def restore_target_initial_state(self, target): | 200 | def restore_target_initial_state(self, target): | ||
203 | for attribute, value in self.initial_states[id(target)].items(): | 201 | for attribute, value in self.initial_states[id(target)].items(): | ||
204 | setattr(target, attribute, value) | 202 | setattr(target, attribute, value) | ||
205 | 203 | ||||
206 | def apply(self, target, potion): | 204 | def apply(self, target, potion): | ||
207 | if potion.is_depleted: | 205 | if potion.is_depleted: | ||
208 | raise TypeError("Potion is depleted.") | 206 | raise TypeError("Potion is depleted.") | ||
209 | 207 | ||||
210 | if id(target) not in self.initial_states.keys(): | 208 | if id(target) not in self.initial_states.keys(): | ||
211 | attributes_before_change = copy.deepcopy(target.__dict__) | 209 | attributes_before_change = copy.deepcopy(target.__dict__) | ||
212 | self.initial_states[id(target)] = attributes_before_change | 210 | self.initial_states[id(target)] = attributes_before_change | ||
213 | 211 | ||||
214 | self.effects[id(potion)] = [] | 212 | self.effects[id(potion)] = [] | ||
n | 215 | self.times[id(potion)] = potion.duration - 1 | n | 213 | self.times[id(potion)] = potion.duration |
216 | self.targets[id(potion)] = target | 214 | self.targets[id(potion)] = target | ||
217 | 215 | ||||
218 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | 216 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | ||
n | 219 | key=lambda x: sum(ord(c) for c in x)) | n | 217 | key=lambda x: sum(ord(c) for c in x), |
218 | reverse=True) | ||||
220 | 219 | ||||
221 | for unused_effect in unused_effects: | 220 | for unused_effect in unused_effects: | ||
222 | effect = potion.__getattribute__(unused_effect) | 221 | effect = potion.__getattribute__(unused_effect) | ||
223 | self.effects[id(potion)].append(copy.deepcopy(effect)) | 222 | self.effects[id(potion)].append(copy.deepcopy(effect)) | ||
224 | effect(target) | 223 | effect(target) | ||
225 | 224 | ||||
226 | potion.is_depleted = True | 225 | potion.is_depleted = True | ||
227 | 226 | ||||
228 | def reapply_active_potions(self, target): | 227 | def reapply_active_potions(self, target): | ||
229 | for key in self.effects: | 228 | for key in self.effects: | ||
230 | if self.times[key] == 0: | 229 | if self.times[key] == 0: | ||
231 | continue | 230 | continue | ||
232 | for effect in self.effects[key]: | 231 | for effect in self.effects[key]: | ||
233 | number_of_calls = effect.number_of_calls | 232 | number_of_calls = effect.number_of_calls | ||
234 | effect(target) | 233 | effect(target) | ||
235 | effect.number_of_calls = number_of_calls | 234 | effect.number_of_calls = number_of_calls | ||
236 | 235 | ||||
237 | def tick(self): | 236 | def tick(self): | ||
238 | to_remove = [] | 237 | to_remove = [] | ||
239 | 238 | ||||
240 | for key in self.times: | 239 | for key in self.times: | ||
n | n | 240 | self.times[key] -= 1 | ||
241 | |||||
242 | for key in self.times: | ||||
241 | # restore the old state of 'target' and remove the potion from 'active' | 243 | # restore the old state of 'target' and remove the potion | ||
242 | if self.times[key] == 0: | 244 | if self.times[key] <= 0: | ||
243 | target = self.targets[key] | 245 | target = self.targets[key] | ||
244 | self.restore_target_initial_state(target) | 246 | self.restore_target_initial_state(target) | ||
245 | to_remove.append(key) | 247 | to_remove.append(key) | ||
246 | self.reapply_active_potions(target) | 248 | self.reapply_active_potions(target) | ||
t | 247 | else: | t | ||
248 | self.times[key] -= 1 | ||||
249 | 249 | ||||
250 | for key in to_remove: | 250 | for key in to_remove: | ||
251 | self.times.pop(key) | 251 | self.times.pop(key) | ||
252 | self.effects.pop(key) | 252 | self.effects.pop(key) | ||
253 | self.targets.pop(key) | 253 | self.targets.pop(key) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | import copy | 2 | import copy | ||
3 | 3 | ||||
4 | 4 | ||||
5 | def custom_round(result): | 5 | def custom_round(result): | ||
6 | fractional_part = result % 1 | 6 | fractional_part = result % 1 | ||
7 | 7 | ||||
8 | if fractional_part == 0: | 8 | if fractional_part == 0: | ||
9 | rounded_result = result | 9 | rounded_result = result | ||
10 | elif fractional_part <= 0.5: | 10 | elif fractional_part <= 0.5: | ||
11 | rounded_result = math.floor(result) | 11 | rounded_result = math.floor(result) | ||
12 | else: | 12 | else: | ||
13 | rounded_result = math.ceil(result) | 13 | rounded_result = math.ceil(result) | ||
14 | 14 | ||||
t | 15 | return rounded_result | t | 15 | return int(rounded_result) |
16 | 16 | ||||
17 | 17 | ||||
18 | class FunctionWrapper: | 18 | class FunctionWrapper: | ||
19 | """A class used to implement function intensities""" | 19 | """A class used to implement function intensities""" | ||
20 | 20 | ||||
21 | def __init__(self, func, number_of_calls): | 21 | def __init__(self, func, number_of_calls): | ||
22 | self.func = func | 22 | self.func = func | ||
23 | self.number_of_calls = number_of_calls | 23 | self.number_of_calls = number_of_calls | ||
24 | 24 | ||||
25 | def __call__(self, *args, **kwargs): | 25 | def __call__(self, *args, **kwargs): | ||
26 | for _ in range(self.number_of_calls): | 26 | for _ in range(self.number_of_calls): | ||
27 | self.func(*args, **kwargs) | 27 | self.func(*args, **kwargs) | ||
28 | 28 | ||||
29 | def __mul__(self, number): | 29 | def __mul__(self, number): | ||
30 | """Multiply a function with a given number""" | 30 | """Multiply a function with a given number""" | ||
31 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 31 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
32 | result_function.number_of_calls = custom_round(self.number_of_calls * number) | 32 | result_function.number_of_calls = custom_round(self.number_of_calls * number) | ||
33 | return result_function | 33 | return result_function | ||
34 | 34 | ||||
35 | def __add__(self, other): | 35 | def __add__(self, other): | ||
36 | """Combine effects of functions""" | 36 | """Combine effects of functions""" | ||
37 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 37 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
38 | 38 | ||||
39 | # if the other function hasn't been wrapped yet | 39 | # if the other function hasn't been wrapped yet | ||
40 | if not isinstance(other, FunctionWrapper): | 40 | if not isinstance(other, FunctionWrapper): | ||
41 | result_function.number_of_calls += 1 | 41 | result_function.number_of_calls += 1 | ||
42 | else: | 42 | else: | ||
43 | result_function.number_of_calls += other.number_of_calls | 43 | result_function.number_of_calls += other.number_of_calls | ||
44 | return result_function | 44 | return result_function | ||
45 | 45 | ||||
46 | def __truediv__(self, number): | 46 | def __truediv__(self, number): | ||
47 | """Divide effects of a function""" | 47 | """Divide effects of a function""" | ||
48 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 48 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
49 | result_function.number_of_calls = custom_round(result_function.number_of_calls / number) | 49 | result_function.number_of_calls = custom_round(result_function.number_of_calls / number) | ||
50 | return result_function | 50 | return result_function | ||
51 | 51 | ||||
52 | 52 | ||||
53 | class Potion: | 53 | class Potion: | ||
54 | def __init__(self, effects, duration): | 54 | def __init__(self, effects, duration): | ||
55 | self.effects = copy.deepcopy(effects) | 55 | self.effects = copy.deepcopy(effects) | ||
56 | self.duration = duration | 56 | self.duration = duration | ||
57 | 57 | ||||
58 | self.usable = True | 58 | self.usable = True | ||
59 | self.is_depleted = False | 59 | self.is_depleted = False | ||
60 | # Store keys of depleted effects here | 60 | # Store keys of depleted effects here | ||
61 | self.depleted_effects = set() | 61 | self.depleted_effects = set() | ||
62 | 62 | ||||
63 | # Wrap all functions that are still not wrapped to make operations on them easier | 63 | # Wrap all functions that are still not wrapped to make operations on them easier | ||
64 | for key in self.effects: | 64 | for key in self.effects: | ||
65 | if not isinstance(self.effects[key], FunctionWrapper): | 65 | if not isinstance(self.effects[key], FunctionWrapper): | ||
66 | wrapped_function = FunctionWrapper(self.effects[key], 1) | 66 | wrapped_function = FunctionWrapper(self.effects[key], 1) | ||
67 | self.effects[key] = wrapped_function | 67 | self.effects[key] = wrapped_function | ||
68 | 68 | ||||
69 | def perform_checks(self): | 69 | def perform_checks(self): | ||
70 | if self.is_depleted: | 70 | if self.is_depleted: | ||
71 | raise TypeError("Potion is depleted.") | 71 | raise TypeError("Potion is depleted.") | ||
72 | if not self.usable: | 72 | if not self.usable: | ||
73 | raise TypeError("Potion is now part of something bigger than itself.") | 73 | raise TypeError("Potion is now part of something bigger than itself.") | ||
74 | 74 | ||||
75 | def __getattribute__(self, item): | 75 | def __getattribute__(self, item): | ||
76 | if item in object.__getattribute__(self, 'effects'): | 76 | if item in object.__getattribute__(self, 'effects'): | ||
77 | self.perform_checks() | 77 | self.perform_checks() | ||
78 | if item not in self.depleted_effects: | 78 | if item not in self.depleted_effects: | ||
79 | self.depleted_effects.add(item) | 79 | self.depleted_effects.add(item) | ||
80 | if len(self.effects) == len(self.depleted_effects): | 80 | if len(self.effects) == len(self.depleted_effects): | ||
81 | self.is_depleted = True | 81 | self.is_depleted = True | ||
82 | return object.__getattribute__(self, 'effects')[item] | 82 | return object.__getattribute__(self, 'effects')[item] | ||
83 | else: | 83 | else: | ||
84 | raise TypeError("Effect is depleted.") | 84 | raise TypeError("Effect is depleted.") | ||
85 | return object.__getattribute__(self, item) | 85 | return object.__getattribute__(self, item) | ||
86 | 86 | ||||
87 | def __add__(self, other): | 87 | def __add__(self, other): | ||
88 | self.perform_checks() | 88 | self.perform_checks() | ||
89 | 89 | ||||
90 | max_duration = max(self.duration, other.duration) | 90 | max_duration = max(self.duration, other.duration) | ||
91 | 91 | ||||
92 | # merge the two dictionaries of effects together | 92 | # merge the two dictionaries of effects together | ||
93 | effects_merged = other.effects.copy() | 93 | effects_merged = other.effects.copy() | ||
94 | # if there are common keys, their values will be overwritten | 94 | # if there are common keys, their values will be overwritten | ||
95 | effects_merged.update(self.effects.copy()) | 95 | effects_merged.update(self.effects.copy()) | ||
96 | 96 | ||||
97 | result_potion = Potion(effects_merged, max_duration) | 97 | result_potion = Potion(effects_merged, max_duration) | ||
98 | 98 | ||||
99 | # Get the common functions for the potions | 99 | # Get the common functions for the potions | ||
100 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 100 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
101 | for name in common_names: | 101 | for name in common_names: | ||
102 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | 102 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | ||
103 | 103 | ||||
104 | self.usable = False | 104 | self.usable = False | ||
105 | other.usable = False | 105 | other.usable = False | ||
106 | 106 | ||||
107 | return result_potion | 107 | return result_potion | ||
108 | 108 | ||||
109 | def __mul__(self, number): | 109 | def __mul__(self, number): | ||
110 | self.perform_checks() | 110 | self.perform_checks() | ||
111 | 111 | ||||
112 | multiplied_effects = dict(self.effects) | 112 | multiplied_effects = dict(self.effects) | ||
113 | 113 | ||||
114 | for key in multiplied_effects: | 114 | for key in multiplied_effects: | ||
115 | multiplied_effects[key] = multiplied_effects[key] * number | 115 | multiplied_effects[key] = multiplied_effects[key] * number | ||
116 | 116 | ||||
117 | multiplied_potion = Potion(multiplied_effects, self.duration) | 117 | multiplied_potion = Potion(multiplied_effects, self.duration) | ||
118 | 118 | ||||
119 | self.usable = False | 119 | self.usable = False | ||
120 | return multiplied_potion | 120 | return multiplied_potion | ||
121 | 121 | ||||
122 | def __sub__(self, other): | 122 | def __sub__(self, other): | ||
123 | self.perform_checks() | 123 | self.perform_checks() | ||
124 | 124 | ||||
125 | # Compare the keys of the two potions using set operations | 125 | # Compare the keys of the two potions using set operations | ||
126 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | 126 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | ||
127 | raise TypeError("Different effects in right potion") | 127 | raise TypeError("Different effects in right potion") | ||
128 | 128 | ||||
129 | purified = Potion(self.effects, self.duration) | 129 | purified = Potion(self.effects, self.duration) | ||
130 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 130 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
131 | 131 | ||||
132 | for name in common_names: | 132 | for name in common_names: | ||
133 | left_intensity = self.effects[name].number_of_calls | 133 | left_intensity = self.effects[name].number_of_calls | ||
134 | right_intensity = other.effects[name].number_of_calls | 134 | right_intensity = other.effects[name].number_of_calls | ||
135 | 135 | ||||
136 | if right_intensity >= left_intensity: | 136 | if right_intensity >= left_intensity: | ||
137 | purified.effects.pop(name) | 137 | purified.effects.pop(name) | ||
138 | else: | 138 | else: | ||
139 | purified.effects[name].number_of_calls = left_intensity - right_intensity | 139 | purified.effects[name].number_of_calls = left_intensity - right_intensity | ||
140 | 140 | ||||
141 | self.usable = False | 141 | self.usable = False | ||
142 | other.usable = False | 142 | other.usable = False | ||
143 | 143 | ||||
144 | return purified | 144 | return purified | ||
145 | 145 | ||||
146 | def __truediv__(self, number): | 146 | def __truediv__(self, number): | ||
147 | self.perform_checks() | 147 | self.perform_checks() | ||
148 | 148 | ||||
149 | divided_effects = {key: value / number for key, value in self.effects.items()} | 149 | divided_effects = {key: value / number for key, value in self.effects.items()} | ||
150 | self.usable = False | 150 | self.usable = False | ||
151 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | 151 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | ||
152 | 152 | ||||
153 | def __eq__(self, other): | 153 | def __eq__(self, other): | ||
154 | for key in self.effects: | 154 | for key in self.effects: | ||
155 | if key not in other.effects: | 155 | if key not in other.effects: | ||
156 | return False | 156 | return False | ||
157 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | 157 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | ||
158 | return False | 158 | return False | ||
159 | return True | 159 | return True | ||
160 | 160 | ||||
161 | @staticmethod | 161 | @staticmethod | ||
162 | def calculate_sums(left_potion, right_potion): | 162 | def calculate_sums(left_potion, right_potion): | ||
163 | sum_left = 0 | 163 | sum_left = 0 | ||
164 | sum_right = 0 | 164 | sum_right = 0 | ||
165 | 165 | ||||
166 | for key in left_potion.effects: | 166 | for key in left_potion.effects: | ||
167 | sum_left += left_potion.effects[key].number_of_calls | 167 | sum_left += left_potion.effects[key].number_of_calls | ||
168 | 168 | ||||
169 | for key in right_potion.effects: | 169 | for key in right_potion.effects: | ||
170 | sum_right += right_potion.effects[key].number_of_calls | 170 | sum_right += right_potion.effects[key].number_of_calls | ||
171 | 171 | ||||
172 | return sum_left, sum_right | 172 | return sum_left, sum_right | ||
173 | 173 | ||||
174 | def __lt__(self, other): | 174 | def __lt__(self, other): | ||
175 | sum_self, sum_other = Potion.calculate_sums(self, other) | 175 | sum_self, sum_other = Potion.calculate_sums(self, other) | ||
176 | return sum_self < sum_other | 176 | return sum_self < sum_other | ||
177 | 177 | ||||
178 | def __gt__(self, other): | 178 | def __gt__(self, other): | ||
179 | sum_self, sum_other = Potion.calculate_sums(self, other) | 179 | sum_self, sum_other = Potion.calculate_sums(self, other) | ||
180 | return sum_self > sum_other | 180 | return sum_self > sum_other | ||
181 | 181 | ||||
182 | def __ge__(self, other): | 182 | def __ge__(self, other): | ||
183 | return NotImplemented | 183 | return NotImplemented | ||
184 | 184 | ||||
185 | def __le__(self, other): | 185 | def __le__(self, other): | ||
186 | return NotImplemented | 186 | return NotImplemented | ||
187 | 187 | ||||
188 | 188 | ||||
189 | class ГоспожатаПоХимия: | 189 | class ГоспожатаПоХимия: | ||
190 | def __init__(self): | 190 | def __init__(self): | ||
191 | self.time = 0 | 191 | self.time = 0 | ||
192 | 192 | ||||
193 | # for each target, store its original attributes | 193 | # for each target, store its original attributes | ||
194 | self.initial_states = dict() | 194 | self.initial_states = dict() | ||
195 | # original effects of each potion | 195 | # original effects of each potion | ||
196 | self.effects = dict() | 196 | self.effects = dict() | ||
197 | # remaining times for all active potions | 197 | # remaining times for all active potions | ||
198 | self.times = dict() | 198 | self.times = dict() | ||
199 | # associate each potion with a target | 199 | # associate each potion with a target | ||
200 | self.targets = dict() | 200 | self.targets = dict() | ||
201 | 201 | ||||
202 | def restore_target_initial_state(self, target): | 202 | def restore_target_initial_state(self, target): | ||
203 | for attribute, value in self.initial_states[id(target)].items(): | 203 | for attribute, value in self.initial_states[id(target)].items(): | ||
204 | setattr(target, attribute, value) | 204 | setattr(target, attribute, value) | ||
205 | 205 | ||||
206 | def apply(self, target, potion): | 206 | def apply(self, target, potion): | ||
207 | if potion.is_depleted: | 207 | if potion.is_depleted: | ||
208 | raise TypeError("Potion is depleted.") | 208 | raise TypeError("Potion is depleted.") | ||
209 | 209 | ||||
210 | if id(target) not in self.initial_states.keys(): | 210 | if id(target) not in self.initial_states.keys(): | ||
211 | attributes_before_change = copy.deepcopy(target.__dict__) | 211 | attributes_before_change = copy.deepcopy(target.__dict__) | ||
212 | self.initial_states[id(target)] = attributes_before_change | 212 | self.initial_states[id(target)] = attributes_before_change | ||
213 | 213 | ||||
214 | self.effects[id(potion)] = [] | 214 | self.effects[id(potion)] = [] | ||
215 | self.times[id(potion)] = potion.duration - 1 | 215 | self.times[id(potion)] = potion.duration - 1 | ||
216 | self.targets[id(potion)] = target | 216 | self.targets[id(potion)] = target | ||
217 | 217 | ||||
218 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | 218 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | ||
219 | key=lambda x: sum(ord(c) for c in x)) | 219 | key=lambda x: sum(ord(c) for c in x)) | ||
220 | 220 | ||||
221 | for unused_effect in unused_effects: | 221 | for unused_effect in unused_effects: | ||
222 | effect = potion.__getattribute__(unused_effect) | 222 | effect = potion.__getattribute__(unused_effect) | ||
223 | self.effects[id(potion)].append(copy.deepcopy(effect)) | 223 | self.effects[id(potion)].append(copy.deepcopy(effect)) | ||
224 | effect(target) | 224 | effect(target) | ||
225 | 225 | ||||
226 | potion.is_depleted = True | 226 | potion.is_depleted = True | ||
227 | 227 | ||||
228 | def reapply_active_potions(self, target): | 228 | def reapply_active_potions(self, target): | ||
229 | for key in self.effects: | 229 | for key in self.effects: | ||
230 | if self.times[key] == 0: | 230 | if self.times[key] == 0: | ||
231 | continue | 231 | continue | ||
232 | for effect in self.effects[key]: | 232 | for effect in self.effects[key]: | ||
233 | number_of_calls = effect.number_of_calls | 233 | number_of_calls = effect.number_of_calls | ||
234 | effect(target) | 234 | effect(target) | ||
235 | effect.number_of_calls = number_of_calls | 235 | effect.number_of_calls = number_of_calls | ||
236 | 236 | ||||
237 | def tick(self): | 237 | def tick(self): | ||
238 | to_remove = [] | 238 | to_remove = [] | ||
239 | 239 | ||||
240 | for key in self.times: | 240 | for key in self.times: | ||
241 | # restore the old state of 'target' and remove the potion from 'active' | 241 | # restore the old state of 'target' and remove the potion from 'active' | ||
242 | if self.times[key] == 0: | 242 | if self.times[key] == 0: | ||
243 | target = self.targets[key] | 243 | target = self.targets[key] | ||
244 | self.restore_target_initial_state(target) | 244 | self.restore_target_initial_state(target) | ||
245 | to_remove.append(key) | 245 | to_remove.append(key) | ||
246 | self.reapply_active_potions(target) | 246 | self.reapply_active_potions(target) | ||
247 | else: | 247 | else: | ||
248 | self.times[key] -= 1 | 248 | self.times[key] -= 1 | ||
249 | 249 | ||||
250 | for key in to_remove: | 250 | for key in to_remove: | ||
251 | self.times.pop(key) | 251 | self.times.pop(key) | ||
252 | self.effects.pop(key) | 252 | self.effects.pop(key) | ||
253 | self.targets.pop(key) | 253 | self.targets.pop(key) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | import copy | 2 | import copy | ||
3 | 3 | ||||
4 | 4 | ||||
n | 5 | def custom_round_mul(a, k): | n | 5 | def custom_round(result): |
6 | x = a * k | ||||
7 | fractional_part = x % 1 | 6 | fractional_part = result % 1 | ||
8 | 7 | ||||
9 | if fractional_part == 0: | 8 | if fractional_part == 0: | ||
n | 10 | result = x | n | 9 | rounded_result = result |
11 | elif fractional_part <= 0.5: | 10 | elif fractional_part <= 0.5: | ||
n | 12 | result = math.floor(x) | n | ||
13 | else: | ||||
14 | result = math.ceil(x) | ||||
15 | |||||
16 | return result | ||||
17 | |||||
18 | |||||
19 | def custom_round_div(a, k): | ||||
20 | result = a / k | ||||
21 | fractional_part = result % 1 | ||||
22 | |||||
23 | if fractional_part <= 0.5: | ||||
24 | rounded_result = math.floor(result) | 11 | rounded_result = math.floor(result) | ||
25 | else: | 12 | else: | ||
26 | rounded_result = math.ceil(result) | 13 | rounded_result = math.ceil(result) | ||
27 | 14 | ||||
28 | return rounded_result | 15 | return rounded_result | ||
29 | 16 | ||||
30 | 17 | ||||
31 | class FunctionWrapper: | 18 | class FunctionWrapper: | ||
n | 32 | """This class is used to implement function intensities""" | n | 19 | """A class used to implement function intensities""" |
33 | 20 | ||||
34 | def __init__(self, func, number_of_calls): | 21 | def __init__(self, func, number_of_calls): | ||
35 | self.func = func | 22 | self.func = func | ||
36 | self.number_of_calls = number_of_calls | 23 | self.number_of_calls = number_of_calls | ||
37 | 24 | ||||
38 | def __call__(self, *args, **kwargs): | 25 | def __call__(self, *args, **kwargs): | ||
39 | for _ in range(self.number_of_calls): | 26 | for _ in range(self.number_of_calls): | ||
40 | self.func(*args, **kwargs) | 27 | self.func(*args, **kwargs) | ||
41 | 28 | ||||
42 | def __mul__(self, number): | 29 | def __mul__(self, number): | ||
43 | """Multiply a function with a given number""" | 30 | """Multiply a function with a given number""" | ||
44 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 31 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
n | 45 | result_function.number_of_calls = custom_round_mul(self.number_of_calls, number) | n | 32 | result_function.number_of_calls = custom_round(self.number_of_calls * number) |
46 | return result_function | 33 | return result_function | ||
47 | 34 | ||||
48 | def __add__(self, other): | 35 | def __add__(self, other): | ||
n | 49 | """Combine effects""" | n | 36 | """Combine effects of functions""" |
50 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 37 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
51 | 38 | ||||
52 | # if the other function hasn't been wrapped yet | 39 | # if the other function hasn't been wrapped yet | ||
53 | if not isinstance(other, FunctionWrapper): | 40 | if not isinstance(other, FunctionWrapper): | ||
54 | result_function.number_of_calls += 1 | 41 | result_function.number_of_calls += 1 | ||
55 | else: | 42 | else: | ||
56 | result_function.number_of_calls += other.number_of_calls | 43 | result_function.number_of_calls += other.number_of_calls | ||
57 | return result_function | 44 | return result_function | ||
58 | 45 | ||||
59 | def __truediv__(self, number): | 46 | def __truediv__(self, number): | ||
n | 60 | """Divide effects""" | n | 47 | """Divide effects of a function""" |
61 | result_function = FunctionWrapper(self.func, self.number_of_calls) | 48 | result_function = FunctionWrapper(self.func, self.number_of_calls) | ||
n | 62 | result_function.number_of_calls = custom_round_div(result_function.number_of_calls, number) | n | 49 | result_function.number_of_calls = custom_round(result_function.number_of_calls / number) |
63 | return result_function | 50 | return result_function | ||
n | 64 | n | |||
65 | |||||
66 | class TransienceDecorator: | ||||
67 | """Perform a check if the potion has been used in an arithmetic operation | ||||
68 | or applied by ГоспожатаПоХимия""" | ||||
69 | |||||
70 | def __init__(self, usable, is_depleted): | ||||
71 | self.usable = usable | ||||
72 | self.is_depleted = is_depleted | ||||
73 | |||||
74 | def __call__(self, method): | ||||
75 | def wrapper(instance, *args, **kwargs): | ||||
76 | usable = getattr(instance, self.usable) | ||||
77 | is_depleted = getattr(instance, self.is_depleted) | ||||
78 | |||||
79 | if is_depleted: | ||||
80 | raise TypeError("Potion is depleted.") | ||||
81 | if not usable: | ||||
82 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
83 | |||||
84 | return wrapper | ||||
85 | 51 | ||||
86 | 52 | ||||
87 | class Potion: | 53 | class Potion: | ||
88 | def __init__(self, effects, duration): | 54 | def __init__(self, effects, duration): | ||
n | 89 | self.effects = effects | n | 55 | self.effects = copy.deepcopy(effects) |
90 | self.duration = duration | 56 | self.duration = duration | ||
91 | 57 | ||||
92 | self.usable = True | 58 | self.usable = True | ||
93 | self.is_depleted = False | 59 | self.is_depleted = False | ||
94 | # Store keys of depleted effects here | 60 | # Store keys of depleted effects here | ||
95 | self.depleted_effects = set() | 61 | self.depleted_effects = set() | ||
96 | 62 | ||||
97 | # Wrap all functions that are still not wrapped to make operations on them easier | 63 | # Wrap all functions that are still not wrapped to make operations on them easier | ||
98 | for key in self.effects: | 64 | for key in self.effects: | ||
99 | if not isinstance(self.effects[key], FunctionWrapper): | 65 | if not isinstance(self.effects[key], FunctionWrapper): | ||
100 | wrapped_function = FunctionWrapper(self.effects[key], 1) | 66 | wrapped_function = FunctionWrapper(self.effects[key], 1) | ||
101 | self.effects[key] = wrapped_function | 67 | self.effects[key] = wrapped_function | ||
102 | 68 | ||||
n | n | 69 | def perform_checks(self): | ||
70 | if self.is_depleted: | ||||
71 | raise TypeError("Potion is depleted.") | ||||
72 | if not self.usable: | ||||
73 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
74 | |||||
103 | def __getattribute__(self, item): | 75 | def __getattribute__(self, item): | ||
104 | if item in object.__getattribute__(self, 'effects'): | 76 | if item in object.__getattribute__(self, 'effects'): | ||
n | 105 | if object.__getattribute__(self, 'is_depleted'): | n | 77 | self.perform_checks() |
106 | raise TypeError("Potion is depleted.") | ||||
107 | if not object.__getattribute__(self, 'usable'): | ||||
108 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
109 | if item not in object.__getattribute__(self, 'depleted_effects'): | 78 | if item not in self.depleted_effects: | ||
110 | object.__getattribute__(self, 'depleted_effects').add(item) | 79 | self.depleted_effects.add(item) | ||
80 | if len(self.effects) == len(self.depleted_effects): | ||||
81 | self.is_depleted = True | ||||
111 | return object.__getattribute__(self, 'effects')[item] | 82 | return object.__getattribute__(self, 'effects')[item] | ||
112 | else: | 83 | else: | ||
113 | raise TypeError("Effect is depleted.") | 84 | raise TypeError("Effect is depleted.") | ||
114 | return object.__getattribute__(self, item) | 85 | return object.__getattribute__(self, item) | ||
115 | 86 | ||||
116 | def __add__(self, other): | 87 | def __add__(self, other): | ||
n | 117 | if self.is_depleted: | n | 88 | self.perform_checks() |
118 | raise TypeError("Potion is depleted.") | ||||
119 | if not self.usable: | ||||
120 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
121 | 89 | ||||
122 | max_duration = max(self.duration, other.duration) | 90 | max_duration = max(self.duration, other.duration) | ||
123 | 91 | ||||
124 | # merge the two dictionaries of effects together | 92 | # merge the two dictionaries of effects together | ||
125 | effects_merged = other.effects.copy() | 93 | effects_merged = other.effects.copy() | ||
126 | # if there are common keys, their values will be overwritten | 94 | # if there are common keys, their values will be overwritten | ||
127 | effects_merged.update(self.effects.copy()) | 95 | effects_merged.update(self.effects.copy()) | ||
128 | 96 | ||||
129 | result_potion = Potion(effects_merged, max_duration) | 97 | result_potion = Potion(effects_merged, max_duration) | ||
130 | 98 | ||||
131 | # Get the common functions for the potions | 99 | # Get the common functions for the potions | ||
132 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 100 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
133 | for name in common_names: | 101 | for name in common_names: | ||
134 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | 102 | result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls | ||
135 | 103 | ||||
136 | self.usable = False | 104 | self.usable = False | ||
137 | other.usable = False | 105 | other.usable = False | ||
138 | 106 | ||||
139 | return result_potion | 107 | return result_potion | ||
140 | 108 | ||||
141 | def __mul__(self, number): | 109 | def __mul__(self, number): | ||
n | 142 | if self.is_depleted: | n | 110 | self.perform_checks() |
143 | raise TypeError("Potion is depleted.") | ||||
144 | if not self.usable: | ||||
145 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
146 | 111 | ||||
147 | multiplied_effects = dict(self.effects) | 112 | multiplied_effects = dict(self.effects) | ||
148 | 113 | ||||
149 | for key in multiplied_effects: | 114 | for key in multiplied_effects: | ||
150 | multiplied_effects[key] = multiplied_effects[key] * number | 115 | multiplied_effects[key] = multiplied_effects[key] * number | ||
151 | 116 | ||||
152 | multiplied_potion = Potion(multiplied_effects, self.duration) | 117 | multiplied_potion = Potion(multiplied_effects, self.duration) | ||
153 | 118 | ||||
154 | self.usable = False | 119 | self.usable = False | ||
155 | return multiplied_potion | 120 | return multiplied_potion | ||
156 | 121 | ||||
157 | def __sub__(self, other): | 122 | def __sub__(self, other): | ||
n | 158 | if self.is_depleted: | n | 123 | self.perform_checks() |
159 | raise TypeError("Potion is depleted.") | ||||
160 | if not self.usable: | ||||
161 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
162 | 124 | ||||
163 | # Compare the keys of the two potions using set operations | 125 | # Compare the keys of the two potions using set operations | ||
164 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | 126 | if not set(other.effects.keys()).issubset(set(self.effects.keys())): | ||
165 | raise TypeError("Different effects in right potion") | 127 | raise TypeError("Different effects in right potion") | ||
166 | 128 | ||||
167 | purified = Potion(self.effects, self.duration) | 129 | purified = Potion(self.effects, self.duration) | ||
168 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | 130 | common_names = set(self.effects.keys()) & set(other.effects.keys()) | ||
169 | 131 | ||||
170 | for name in common_names: | 132 | for name in common_names: | ||
171 | left_intensity = self.effects[name].number_of_calls | 133 | left_intensity = self.effects[name].number_of_calls | ||
172 | right_intensity = other.effects[name].number_of_calls | 134 | right_intensity = other.effects[name].number_of_calls | ||
173 | 135 | ||||
174 | if right_intensity >= left_intensity: | 136 | if right_intensity >= left_intensity: | ||
175 | purified.effects.pop(name) | 137 | purified.effects.pop(name) | ||
176 | else: | 138 | else: | ||
177 | purified.effects[name].number_of_calls = left_intensity - right_intensity | 139 | purified.effects[name].number_of_calls = left_intensity - right_intensity | ||
178 | 140 | ||||
179 | self.usable = False | 141 | self.usable = False | ||
180 | other.usable = False | 142 | other.usable = False | ||
n | n | 143 | |||
181 | return purified | 144 | return purified | ||
182 | 145 | ||||
183 | def __truediv__(self, number): | 146 | def __truediv__(self, number): | ||
n | 184 | if self.is_depleted: | n | 147 | self.perform_checks() |
185 | raise TypeError("Potion is depleted.") | ||||
186 | if not self.usable: | ||||
187 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
188 | 148 | ||||
189 | divided_effects = {key: value / number for key, value in self.effects.items()} | 149 | divided_effects = {key: value / number for key, value in self.effects.items()} | ||
190 | self.usable = False | 150 | self.usable = False | ||
191 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | 151 | return tuple(Potion(divided_effects, self.duration) for _ in range(number)) | ||
192 | 152 | ||||
193 | def __eq__(self, other): | 153 | def __eq__(self, other): | ||
194 | for key in self.effects: | 154 | for key in self.effects: | ||
195 | if key not in other.effects: | 155 | if key not in other.effects: | ||
196 | return False | 156 | return False | ||
197 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | 157 | if other.effects[key].number_of_calls != self.effects[key].number_of_calls: | ||
198 | return False | 158 | return False | ||
199 | return True | 159 | return True | ||
200 | 160 | ||||
201 | @staticmethod | 161 | @staticmethod | ||
202 | def calculate_sums(left_potion, right_potion): | 162 | def calculate_sums(left_potion, right_potion): | ||
203 | sum_left = 0 | 163 | sum_left = 0 | ||
204 | sum_right = 0 | 164 | sum_right = 0 | ||
205 | 165 | ||||
206 | for key in left_potion.effects: | 166 | for key in left_potion.effects: | ||
207 | sum_left += left_potion.effects[key].number_of_calls | 167 | sum_left += left_potion.effects[key].number_of_calls | ||
208 | 168 | ||||
209 | for key in right_potion.effects: | 169 | for key in right_potion.effects: | ||
210 | sum_right += right_potion.effects[key].number_of_calls | 170 | sum_right += right_potion.effects[key].number_of_calls | ||
211 | 171 | ||||
212 | return sum_left, sum_right | 172 | return sum_left, sum_right | ||
213 | 173 | ||||
214 | def __lt__(self, other): | 174 | def __lt__(self, other): | ||
n | 215 | sum_self, sum_other = (Potion.calculate_sums(self, other)) | n | 175 | sum_self, sum_other = Potion.calculate_sums(self, other) |
216 | return sum_self < sum_other | 176 | return sum_self < sum_other | ||
217 | 177 | ||||
218 | def __gt__(self, other): | 178 | def __gt__(self, other): | ||
n | 219 | sum_self, sum_other = (Potion.calculate_sums(self, other)) | n | 179 | sum_self, sum_other = Potion.calculate_sums(self, other) |
220 | return sum_self > sum_other | 180 | return sum_self > sum_other | ||
221 | 181 | ||||
222 | def __ge__(self, other): | 182 | def __ge__(self, other): | ||
223 | return NotImplemented | 183 | return NotImplemented | ||
224 | 184 | ||||
225 | def __le__(self, other): | 185 | def __le__(self, other): | ||
226 | return NotImplemented | 186 | return NotImplemented | ||
227 | 187 | ||||
228 | 188 | ||||
229 | class ГоспожатаПоХимия: | 189 | class ГоспожатаПоХимия: | ||
230 | def __init__(self): | 190 | def __init__(self): | ||
n | 231 | self.active_potions = [] | n | 191 | self.time = 0 |
232 | 192 | ||||
n | 233 | @staticmethod | n | 193 | # for each target, store its original attributes |
194 | self.initial_states = dict() | ||||
195 | # original effects of each potion | ||||
196 | self.effects = dict() | ||||
197 | # remaining times for all active potions | ||||
198 | self.times = dict() | ||||
199 | # associate each potion with a target | ||||
200 | self.targets = dict() | ||||
201 | |||||
234 | def restore_target_initial_state(target, initial_state): | 202 | def restore_target_initial_state(self, target): | ||
235 | for attribute, value in initial_state.items(): | 203 | for attribute, value in self.initial_states[id(target)].items(): | ||
236 | setattr(target, attribute, value) | 204 | setattr(target, attribute, value) | ||
237 | 205 | ||||
238 | def apply(self, target, potion): | 206 | def apply(self, target, potion): | ||
n | 239 | attributes_before_change = copy.deepcopy(target.__dict__) | n | ||
240 | |||||
241 | if len(potion.effects) == len(potion.depleted_effects): | ||||
242 | potion.is_depleted = True | ||||
243 | |||||
244 | if potion.is_depleted: | 207 | if potion.is_depleted: | ||
245 | raise TypeError("Potion is depleted.") | 208 | raise TypeError("Potion is depleted.") | ||
246 | 209 | ||||
n | n | 210 | if id(target) not in self.initial_states.keys(): | ||
211 | attributes_before_change = copy.deepcopy(target.__dict__) | ||||
212 | self.initial_states[id(target)] = attributes_before_change | ||||
213 | |||||
214 | self.effects[id(potion)] = [] | ||||
215 | self.times[id(potion)] = potion.duration - 1 | ||||
216 | self.targets[id(potion)] = target | ||||
217 | |||||
247 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | 218 | unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects, | ||
248 | key=lambda x: sum(ord(c) for c in x)) | 219 | key=lambda x: sum(ord(c) for c in x)) | ||
249 | 220 | ||||
250 | for unused_effect in unused_effects: | 221 | for unused_effect in unused_effects: | ||
n | 251 | potion.__getattribute__(unused_effect)(target) | n | 222 | effect = potion.__getattribute__(unused_effect) |
223 | self.effects[id(potion)].append(copy.deepcopy(effect)) | ||||
224 | effect(target) | ||||
252 | 225 | ||||
n | 253 | self.active_potions.append([potion.duration, target, attributes_before_change]) | n | ||
254 | potion.is_depleted = True | 226 | potion.is_depleted = True | ||
255 | 227 | ||||
n | n | 228 | def reapply_active_potions(self, target): | ||
229 | for key in self.effects: | ||||
230 | if self.times[key] == 0: | ||||
231 | continue | ||||
232 | for effect in self.effects[key]: | ||||
233 | number_of_calls = effect.number_of_calls | ||||
234 | effect(target) | ||||
235 | effect.number_of_calls = number_of_calls | ||||
236 | |||||
256 | def tick(self): | 237 | def tick(self): | ||
n | 257 | for active_potion in self.active_potions: | n | 238 | to_remove = [] |
239 | |||||
240 | for key in self.times: | ||||
258 | # restore the old state of 'target' and remove the potion from 'active' | 241 | # restore the old state of 'target' and remove the potion from 'active' | ||
n | 259 | if active_potion[0] - 1 == 0: | n | 242 | if self.times[key] == 0: |
260 | ГоспожатаПоХимия.restore_target_initial_state(active_potion[1], active_potion[2]) | 243 | target = self.targets[key] | ||
244 | self.restore_target_initial_state(target) | ||||
245 | to_remove.append(key) | ||||
246 | self.reapply_active_potions(target) | ||||
261 | else: | 247 | else: | ||
t | 262 | active_potion[0] -= 1 | t | 248 | self.times[key] -= 1 |
249 | |||||
250 | for key in to_remove: | ||||
251 | self.times.pop(key) | ||||
252 | self.effects.pop(key) | ||||
253 | self.targets.pop(key) |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|