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 | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| 
 | 
 | |||||||||