Домашни > Работилница за отвари! > Решения > Решението на Рая Григорова

Резултати
8 точки от тестове
1 точки от учител

9 точки общо

17 успешни теста
3 неуспешни теста
Код

  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

Като цяло - хубаво, поне за мен четимо решение, браво. :)
История

t1import matht1import math
2import copy2import copy
33
44
5def custom_round(result):5def custom_round(result):
6    fractional_part = result % 16    fractional_part = result % 1
77
8    if fractional_part == 0:8    if fractional_part == 0:
9        rounded_result = result9        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)
1414
15    return int(rounded_result)15    return int(rounded_result)
1616
1717
18class FunctionWrapper:18class FunctionWrapper:
19    """A class used to implement function intensities"""19    """A class used to implement function intensities"""
2020
21    def __init__(self, func, number_of_calls):21    def __init__(self, func, number_of_calls):
22        self.func = func22        self.func = func
23        self.number_of_calls = number_of_calls23        self.number_of_calls = number_of_calls
2424
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)
2828
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_function33        return result_function
3434
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)
3838
39        # if the other function hasn't been wrapped yet39        # 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 += 141            result_function.number_of_calls += 1
42        else:42        else:
43            result_function.number_of_calls += other.number_of_calls43            result_function.number_of_calls += other.number_of_calls
44        return result_function44        return result_function
4545
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_function50        return result_function
5151
5252
53class Potion:53class 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 it55        # 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 = duration57        self.duration = duration
5858
59        self.usable = True59        self.usable = True
60        self.is_depleted = False60        self.is_depleted = False
61        # Store keys of depleted effects here61        # Store keys of depleted effects here
62        self.depleted_effects = set()62        self.depleted_effects = set()
6363
64        # Wrap all functions that are still not wrapped to make operations on them easier64        # 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_function68                self.effects[key] = wrapped_function
6969
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.")
7575
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 = True82                    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)
8787
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()
9191
92        max_duration = max(self.duration, other.duration)92        max_duration = max(self.duration, other.duration)
9393
94        # merge the two dictionaries of effects together94        # 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 overwritten96        # if there are common keys, their values will be overwritten
97        effects_merged.update(self.effects.copy())97        effects_merged.update(self.effects.copy())
9898
99        result_potion = Potion(effects_merged, max_duration)99        result_potion = Potion(effects_merged, max_duration)
100100
101        # get the common functions for the potions101        # 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_calls104            result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls
105105
106        self.usable = False106        self.usable = False
107        other.usable = False107        other.usable = False
108        return result_potion108        return result_potion
109109
110    def __mul__(self, number):110    def __mul__(self, number):
111        self.perform_checks()111        self.perform_checks()
112112
113        multiplied_effects = dict(self.effects)113        multiplied_effects = dict(self.effects)
114114
115        for key in multiplied_effects:115        for key in multiplied_effects:
116            multiplied_effects[key] = multiplied_effects[key] * number116            multiplied_effects[key] = multiplied_effects[key] * number
117117
118        multiplied_potion = Potion(multiplied_effects, self.duration)118        multiplied_potion = Potion(multiplied_effects, self.duration)
119119
120        self.usable = False120        self.usable = False
121        return multiplied_potion121        return multiplied_potion
122122
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()
126126
127        # Compare the keys of the two potions using set operations127        # 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")
130130
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())
133133
134        for name in common_names:134        for name in common_names:
135            left_intensity = self.effects[name].number_of_calls135            left_intensity = self.effects[name].number_of_calls
136            right_intensity = other.effects[name].number_of_calls136            right_intensity = other.effects[name].number_of_calls
137137
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_intensity141                purified.effects[name].number_of_calls = left_intensity - right_intensity
142142
143        self.usable = False143        self.usable = False
144        other.usable = False144        other.usable = False
145        return purified145        return purified
146146
147    def __truediv__(self, number):147    def __truediv__(self, number):
148        self.perform_checks()148        self.perform_checks()
149149
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 = False151        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))
153153
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()
157157
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 False160                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 False162                return False
163        return True163        return True
164164
165    @staticmethod165    @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 = 0168        sum_left = 0
169        sum_right = 0169        sum_right = 0
170170
171        for key in left_potion.effects:171        for key in left_potion.effects:
172            sum_left += left_potion.effects[key].number_of_calls172            sum_left += left_potion.effects[key].number_of_calls
173173
174        for key in right_potion.effects:174        for key in right_potion.effects:
175            sum_right += right_potion.effects[key].number_of_calls175            sum_right += right_potion.effects[key].number_of_calls
176176
177        return sum_left, sum_right177        return sum_left, sum_right
178178
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()
182182
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_other184        return sum_self < sum_other
185185
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()
189189
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_other191        return sum_self > sum_other
192192
193    def __ge__(self, other):193    def __ge__(self, other):
194        return NotImplemented194        return NotImplemented
195195
196    def __le__(self, other):196    def __le__(self, other):
197        return NotImplemented197        return NotImplemented
198198
199199
200class ГоспожатаПоХимия:200class ГоспожатаПоХимия:
201    def __init__(self):201    def __init__(self):
202        # for each target, store its original attributes202        # for each target, store its original attributes
203        self.initial_states = dict()203        self.initial_states = dict()
204        # original effects of each potion204        # original effects of each potion
205        self.effects = dict()205        self.effects = dict()
206        # remaining times for all active potions206        # remaining times for all active potions
207        self.times = dict()207        self.times = dict()
208        # associate each potion with a target208        # associate each potion with a target
209        self.targets = dict()209        self.targets = dict()
210210
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)
214214
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.")
218218
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_change221            self.initial_states[id(target)] = attributes_before_change
222222
223        self.effects[id(potion)] = []223        self.effects[id(potion)] = []
224        self.times[id(potion)] = potion.duration224        self.times[id(potion)] = potion.duration
225        self.targets[id(potion)] = target225        self.targets[id(potion)] = target
226226
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)
230230
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)
235235
236        potion.is_depleted = True236        potion.is_depleted = True
237237
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                continue241                continue
242            for effect in self.effects[key]:242            for effect in self.effects[key]:
243                number_of_calls = effect.number_of_calls243                number_of_calls = effect.number_of_calls
244                effect(target)244                effect(target)
245                effect.number_of_calls = number_of_calls245                effect.number_of_calls = number_of_calls
246246
247    def tick(self):247    def tick(self):
248        to_remove = []248        to_remove = []
249249
250        for key in self.times:250        for key in self.times:
251            self.times[key] -= 1251            self.times[key] -= 1
252252
253        for key in self.times:253        for key in self.times:
254            # restore the old state of 'target' and remove the potion254            # 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)
260260
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
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import mathf1import math
2import copy2import copy
33
44
5def custom_round(result):5def custom_round(result):
6    fractional_part = result % 16    fractional_part = result % 1
77
8    if fractional_part == 0:8    if fractional_part == 0:
9        rounded_result = result9        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)
1414
15    return int(rounded_result)15    return int(rounded_result)
1616
1717
18class FunctionWrapper:18class FunctionWrapper:
19    """A class used to implement function intensities"""19    """A class used to implement function intensities"""
2020
21    def __init__(self, func, number_of_calls):21    def __init__(self, func, number_of_calls):
22        self.func = func22        self.func = func
23        self.number_of_calls = number_of_calls23        self.number_of_calls = number_of_calls
2424
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)
2828
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_function33        return result_function
3434
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)
3838
39        # if the other function hasn't been wrapped yet39        # 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 += 141            result_function.number_of_calls += 1
42        else:42        else:
43            result_function.number_of_calls += other.number_of_calls43            result_function.number_of_calls += other.number_of_calls
44        return result_function44        return result_function
4545
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_function50        return result_function
5151
5252
53class Potion:53class 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 it55        # 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 = duration57        self.duration = duration
5858
59        self.usable = True59        self.usable = True
60        self.is_depleted = False60        self.is_depleted = False
61        # Store keys of depleted effects here61        # Store keys of depleted effects here
62        self.depleted_effects = set()62        self.depleted_effects = set()
6363
64        # Wrap all functions that are still not wrapped to make operations on them easier64        # 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_function68                self.effects[key] = wrapped_function
6969
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.")
7575
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 = True82                    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)
8787
88    def __add__(self, other):88    def __add__(self, other):
89        self.perform_checks()89        self.perform_checks()
nn90        other.perform_checks()
9091
91        max_duration = max(self.duration, other.duration)92        max_duration = max(self.duration, other.duration)
9293
93        # merge the two dictionaries of effects together94        # 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 overwritten96        # if there are common keys, their values will be overwritten
96        effects_merged.update(self.effects.copy())97        effects_merged.update(self.effects.copy())
9798
98        result_potion = Potion(effects_merged, max_duration)99        result_potion = Potion(effects_merged, max_duration)
99100
100        # get the common functions for the potions101        # 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_calls104            result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls
104105
105        self.usable = False106        self.usable = False
106        other.usable = False107        other.usable = False
107        return result_potion108        return result_potion
108109
109    def __mul__(self, number):110    def __mul__(self, number):
110        self.perform_checks()111        self.perform_checks()
111112
112        multiplied_effects = dict(self.effects)113        multiplied_effects = dict(self.effects)
113114
114        for key in multiplied_effects:115        for key in multiplied_effects:
115            multiplied_effects[key] = multiplied_effects[key] * number116            multiplied_effects[key] = multiplied_effects[key] * number
116117
117        multiplied_potion = Potion(multiplied_effects, self.duration)118        multiplied_potion = Potion(multiplied_effects, self.duration)
118119
119        self.usable = False120        self.usable = False
120        return multiplied_potion121        return multiplied_potion
121122
122    def __sub__(self, other):123    def __sub__(self, other):
123        self.perform_checks()124        self.perform_checks()
nn125        other.perform_checks()
124126
125        # Compare the keys of the two potions using set operations127        # 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")
128130
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())
131133
132        for name in common_names:134        for name in common_names:
133            left_intensity = self.effects[name].number_of_calls135            left_intensity = self.effects[name].number_of_calls
134            right_intensity = other.effects[name].number_of_calls136            right_intensity = other.effects[name].number_of_calls
135137
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_intensity141                purified.effects[name].number_of_calls = left_intensity - right_intensity
140142
141        self.usable = False143        self.usable = False
142        other.usable = False144        other.usable = False
143        return purified145        return purified
144146
145    def __truediv__(self, number):147    def __truediv__(self, number):
146        self.perform_checks()148        self.perform_checks()
147149
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 = False151        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))
151153
152    def __eq__(self, other):154    def __eq__(self, other):
nn155        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 False160                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 False162                return False
158        return True163        return True
159164
160    @staticmethod165    @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 = 0168        sum_left = 0
164        sum_right = 0169        sum_right = 0
165170
166        for key in left_potion.effects:171        for key in left_potion.effects:
167            sum_left += left_potion.effects[key].number_of_calls172            sum_left += left_potion.effects[key].number_of_calls
168173
169        for key in right_potion.effects:174        for key in right_potion.effects:
170            sum_right += right_potion.effects[key].number_of_calls175            sum_right += right_potion.effects[key].number_of_calls
171176
172        return sum_left, sum_right177        return sum_left, sum_right
173178
174    def __lt__(self, other):179    def __lt__(self, other):
nn180        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_other184        return sum_self < sum_other
177185
178    def __gt__(self, other):186    def __gt__(self, other):
tt187        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_other191        return sum_self > sum_other
181192
182    def __ge__(self, other):193    def __ge__(self, other):
183        return NotImplemented194        return NotImplemented
184195
185    def __le__(self, other):196    def __le__(self, other):
186        return NotImplemented197        return NotImplemented
187198
188199
189class ГоспожатаПоХимия:200class ГоспожатаПоХимия:
190    def __init__(self):201    def __init__(self):
191        # for each target, store its original attributes202        # for each target, store its original attributes
192        self.initial_states = dict()203        self.initial_states = dict()
193        # original effects of each potion204        # original effects of each potion
194        self.effects = dict()205        self.effects = dict()
195        # remaining times for all active potions206        # remaining times for all active potions
196        self.times = dict()207        self.times = dict()
197        # associate each potion with a target208        # associate each potion with a target
198        self.targets = dict()209        self.targets = dict()
199210
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)
203214
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.")
207218
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_change221            self.initial_states[id(target)] = attributes_before_change
211222
212        self.effects[id(potion)] = []223        self.effects[id(potion)] = []
213        self.times[id(potion)] = potion.duration224        self.times[id(potion)] = potion.duration
214        self.targets[id(potion)] = target225        self.targets[id(potion)] = target
215226
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)
219230
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)
224235
225        potion.is_depleted = True236        potion.is_depleted = True
226237
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                continue241                continue
231            for effect in self.effects[key]:242            for effect in self.effects[key]:
232                number_of_calls = effect.number_of_calls243                number_of_calls = effect.number_of_calls
233                effect(target)244                effect(target)
234                effect.number_of_calls = number_of_calls245                effect.number_of_calls = number_of_calls
235246
236    def tick(self):247    def tick(self):
237        to_remove = []248        to_remove = []
238249
239        for key in self.times:250        for key in self.times:
240            self.times[key] -= 1251            self.times[key] -= 1
241252
242        for key in self.times:253        for key in self.times:
243            # restore the old state of 'target' and remove the potion254            # 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)
249260
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
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import mathf1import math
2import copy2import copy
33
44
5def custom_round(result):5def custom_round(result):
6    fractional_part = result % 16    fractional_part = result % 1
77
8    if fractional_part == 0:8    if fractional_part == 0:
9        rounded_result = result9        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)
1414
15    return int(rounded_result)15    return int(rounded_result)
1616
1717
18class FunctionWrapper:18class FunctionWrapper:
19    """A class used to implement function intensities"""19    """A class used to implement function intensities"""
2020
21    def __init__(self, func, number_of_calls):21    def __init__(self, func, number_of_calls):
22        self.func = func22        self.func = func
23        self.number_of_calls = number_of_calls23        self.number_of_calls = number_of_calls
2424
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)
2828
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_function33        return result_function
3434
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)
3838
39        # if the other function hasn't been wrapped yet39        # 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 += 141            result_function.number_of_calls += 1
42        else:42        else:
43            result_function.number_of_calls += other.number_of_calls43            result_function.number_of_calls += other.number_of_calls
44        return result_function44        return result_function
4545
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_function50        return result_function
5151
5252
53class Potion:53class Potion:
54    def __init__(self, effects, duration):54    def __init__(self, effects, duration):
nn55        # 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 = duration57        self.duration = duration
5758
58        self.usable = True59        self.usable = True
59        self.is_depleted = False60        self.is_depleted = False
60        # Store keys of depleted effects here61        # Store keys of depleted effects here
61        self.depleted_effects = set()62        self.depleted_effects = set()
6263
63        # Wrap all functions that are still not wrapped to make operations on them easier64        # 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_function68                self.effects[key] = wrapped_function
6869
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.")
7475
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 = True82                    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)
8687
87    def __add__(self, other):88    def __add__(self, other):
88        self.perform_checks()89        self.perform_checks()
8990
90        max_duration = max(self.duration, other.duration)91        max_duration = max(self.duration, other.duration)
9192
92        # merge the two dictionaries of effects together93        # 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 overwritten95        # if there are common keys, their values will be overwritten
95        effects_merged.update(self.effects.copy())96        effects_merged.update(self.effects.copy())
9697
97        result_potion = Potion(effects_merged, max_duration)98        result_potion = Potion(effects_merged, max_duration)
9899
n99        # Get the common functions for the potionsn100        # 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_calls103            result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls
103104
104        self.usable = False105        self.usable = False
105        other.usable = False106        other.usable = False
n106 n
107        return result_potion107        return result_potion
108108
109    def __mul__(self, number):109    def __mul__(self, number):
110        self.perform_checks()110        self.perform_checks()
111111
112        multiplied_effects = dict(self.effects)112        multiplied_effects = dict(self.effects)
113113
114        for key in multiplied_effects:114        for key in multiplied_effects:
115            multiplied_effects[key] = multiplied_effects[key] * number115            multiplied_effects[key] = multiplied_effects[key] * number
116116
117        multiplied_potion = Potion(multiplied_effects, self.duration)117        multiplied_potion = Potion(multiplied_effects, self.duration)
118118
119        self.usable = False119        self.usable = False
120        return multiplied_potion120        return multiplied_potion
121121
122    def __sub__(self, other):122    def __sub__(self, other):
123        self.perform_checks()123        self.perform_checks()
124124
125        # Compare the keys of the two potions using set operations125        # 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")
128128
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())
131131
132        for name in common_names:132        for name in common_names:
133            left_intensity = self.effects[name].number_of_calls133            left_intensity = self.effects[name].number_of_calls
134            right_intensity = other.effects[name].number_of_calls134            right_intensity = other.effects[name].number_of_calls
135135
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_intensity139                purified.effects[name].number_of_calls = left_intensity - right_intensity
140140
141        self.usable = False141        self.usable = False
142        other.usable = False142        other.usable = False
n143 n
144        return purified143        return purified
145144
146    def __truediv__(self, number):145    def __truediv__(self, number):
147        self.perform_checks()146        self.perform_checks()
148147
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 = False149        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))
152151
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 False155                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 False157                return False
159        return True158        return True
160159
161    @staticmethod160    @staticmethod
162    def calculate_sums(left_potion, right_potion):161    def calculate_sums(left_potion, right_potion):
nn162        """Calculate sums of effect intensities for two potions"""
163        sum_left = 0163        sum_left = 0
164        sum_right = 0164        sum_right = 0
165165
166        for key in left_potion.effects:166        for key in left_potion.effects:
167            sum_left += left_potion.effects[key].number_of_calls167            sum_left += left_potion.effects[key].number_of_calls
168168
169        for key in right_potion.effects:169        for key in right_potion.effects:
170            sum_right += right_potion.effects[key].number_of_calls170            sum_right += right_potion.effects[key].number_of_calls
171171
172        return sum_left, sum_right172        return sum_left, sum_right
173173
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_other176        return sum_self < sum_other
177177
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_other180        return sum_self > sum_other
181181
182    def __ge__(self, other):182    def __ge__(self, other):
183        return NotImplemented183        return NotImplemented
184184
185    def __le__(self, other):185    def __le__(self, other):
186        return NotImplemented186        return NotImplemented
187187
188188
189class ГоспожатаПоХимия:189class ГоспожатаПоХимия:
190    def __init__(self):190    def __init__(self):
n191        self.time = 0n
192 
193        # for each target, store its original attributes191        # for each target, store its original attributes
194        self.initial_states = dict()192        self.initial_states = dict()
195        # original effects of each potion193        # original effects of each potion
196        self.effects = dict()194        self.effects = dict()
197        # remaining times for all active potions195        # remaining times for all active potions
198        self.times = dict()196        self.times = dict()
199        # associate each potion with a target197        # associate each potion with a target
200        self.targets = dict()198        self.targets = dict()
201199
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)
205203
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.")
209207
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_change210            self.initial_states[id(target)] = attributes_before_change
213211
214        self.effects[id(potion)] = []212        self.effects[id(potion)] = []
n215        self.times[id(potion)] = potion.duration - 1n213        self.times[id(potion)] = potion.duration
216        self.targets[id(potion)] = target214        self.targets[id(potion)] = target
217215
218        unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects,216        unused_effects = sorted(set(potion.effects.keys()) - potion.depleted_effects,
n219                                key=lambda x: sum(ord(c) for c in x))n217                                key=lambda x: sum(ord(c) for c in x),
218                                reverse=True)
220219
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)
225224
226        potion.is_depleted = True225        potion.is_depleted = True
227226
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                continue230                continue
232            for effect in self.effects[key]:231            for effect in self.effects[key]:
233                number_of_calls = effect.number_of_calls232                number_of_calls = effect.number_of_calls
234                effect(target)233                effect(target)
235                effect.number_of_calls = number_of_calls234                effect.number_of_calls = number_of_calls
236235
237    def tick(self):236    def tick(self):
238        to_remove = []237        to_remove = []
239238
240        for key in self.times:239        for key in self.times:
nn240            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)
t247            else:t
248                self.times[key] -= 1
249249
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
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import mathf1import math
2import copy2import copy
33
44
5def custom_round(result):5def custom_round(result):
6    fractional_part = result % 16    fractional_part = result % 1
77
8    if fractional_part == 0:8    if fractional_part == 0:
9        rounded_result = result9        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)
1414
t15    return rounded_resultt15    return int(rounded_result)
1616
1717
18class FunctionWrapper:18class FunctionWrapper:
19    """A class used to implement function intensities"""19    """A class used to implement function intensities"""
2020
21    def __init__(self, func, number_of_calls):21    def __init__(self, func, number_of_calls):
22        self.func = func22        self.func = func
23        self.number_of_calls = number_of_calls23        self.number_of_calls = number_of_calls
2424
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)
2828
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_function33        return result_function
3434
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)
3838
39        # if the other function hasn't been wrapped yet39        # 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 += 141            result_function.number_of_calls += 1
42        else:42        else:
43            result_function.number_of_calls += other.number_of_calls43            result_function.number_of_calls += other.number_of_calls
44        return result_function44        return result_function
4545
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_function50        return result_function
5151
5252
53class Potion:53class 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 = duration56        self.duration = duration
5757
58        self.usable = True58        self.usable = True
59        self.is_depleted = False59        self.is_depleted = False
60        # Store keys of depleted effects here60        # Store keys of depleted effects here
61        self.depleted_effects = set()61        self.depleted_effects = set()
6262
63        # Wrap all functions that are still not wrapped to make operations on them easier63        # 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_function67                self.effects[key] = wrapped_function
6868
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.")
7474
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 = True81                    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)
8686
87    def __add__(self, other):87    def __add__(self, other):
88        self.perform_checks()88        self.perform_checks()
8989
90        max_duration = max(self.duration, other.duration)90        max_duration = max(self.duration, other.duration)
9191
92        # merge the two dictionaries of effects together92        # 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 overwritten94        # if there are common keys, their values will be overwritten
95        effects_merged.update(self.effects.copy())95        effects_merged.update(self.effects.copy())
9696
97        result_potion = Potion(effects_merged, max_duration)97        result_potion = Potion(effects_merged, max_duration)
9898
99        # Get the common functions for the potions99        # 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_calls102            result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls
103103
104        self.usable = False104        self.usable = False
105        other.usable = False105        other.usable = False
106106
107        return result_potion107        return result_potion
108108
109    def __mul__(self, number):109    def __mul__(self, number):
110        self.perform_checks()110        self.perform_checks()
111111
112        multiplied_effects = dict(self.effects)112        multiplied_effects = dict(self.effects)
113113
114        for key in multiplied_effects:114        for key in multiplied_effects:
115            multiplied_effects[key] = multiplied_effects[key] * number115            multiplied_effects[key] = multiplied_effects[key] * number
116116
117        multiplied_potion = Potion(multiplied_effects, self.duration)117        multiplied_potion = Potion(multiplied_effects, self.duration)
118118
119        self.usable = False119        self.usable = False
120        return multiplied_potion120        return multiplied_potion
121121
122    def __sub__(self, other):122    def __sub__(self, other):
123        self.perform_checks()123        self.perform_checks()
124124
125        # Compare the keys of the two potions using set operations125        # 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")
128128
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())
131131
132        for name in common_names:132        for name in common_names:
133            left_intensity = self.effects[name].number_of_calls133            left_intensity = self.effects[name].number_of_calls
134            right_intensity = other.effects[name].number_of_calls134            right_intensity = other.effects[name].number_of_calls
135135
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_intensity139                purified.effects[name].number_of_calls = left_intensity - right_intensity
140140
141        self.usable = False141        self.usable = False
142        other.usable = False142        other.usable = False
143143
144        return purified144        return purified
145145
146    def __truediv__(self, number):146    def __truediv__(self, number):
147        self.perform_checks()147        self.perform_checks()
148148
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 = False150        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))
152152
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 False156                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 False158                return False
159        return True159        return True
160160
161    @staticmethod161    @staticmethod
162    def calculate_sums(left_potion, right_potion):162    def calculate_sums(left_potion, right_potion):
163        sum_left = 0163        sum_left = 0
164        sum_right = 0164        sum_right = 0
165165
166        for key in left_potion.effects:166        for key in left_potion.effects:
167            sum_left += left_potion.effects[key].number_of_calls167            sum_left += left_potion.effects[key].number_of_calls
168168
169        for key in right_potion.effects:169        for key in right_potion.effects:
170            sum_right += right_potion.effects[key].number_of_calls170            sum_right += right_potion.effects[key].number_of_calls
171171
172        return sum_left, sum_right172        return sum_left, sum_right
173173
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_other176        return sum_self < sum_other
177177
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_other180        return sum_self > sum_other
181181
182    def __ge__(self, other):182    def __ge__(self, other):
183        return NotImplemented183        return NotImplemented
184184
185    def __le__(self, other):185    def __le__(self, other):
186        return NotImplemented186        return NotImplemented
187187
188188
189class ГоспожатаПоХимия:189class ГоспожатаПоХимия:
190    def __init__(self):190    def __init__(self):
191        self.time = 0191        self.time = 0
192192
193        # for each target, store its original attributes193        # for each target, store its original attributes
194        self.initial_states = dict()194        self.initial_states = dict()
195        # original effects of each potion195        # original effects of each potion
196        self.effects = dict()196        self.effects = dict()
197        # remaining times for all active potions197        # remaining times for all active potions
198        self.times = dict()198        self.times = dict()
199        # associate each potion with a target199        # associate each potion with a target
200        self.targets = dict()200        self.targets = dict()
201201
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)
205205
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.")
209209
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_change212            self.initial_states[id(target)] = attributes_before_change
213213
214        self.effects[id(potion)] = []214        self.effects[id(potion)] = []
215        self.times[id(potion)] = potion.duration - 1215        self.times[id(potion)] = potion.duration - 1
216        self.targets[id(potion)] = target216        self.targets[id(potion)] = target
217217
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))
220220
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)
225225
226        potion.is_depleted = True226        potion.is_depleted = True
227227
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                continue231                continue
232            for effect in self.effects[key]:232            for effect in self.effects[key]:
233                number_of_calls = effect.number_of_calls233                number_of_calls = effect.number_of_calls
234                effect(target)234                effect(target)
235                effect.number_of_calls = number_of_calls235                effect.number_of_calls = number_of_calls
236236
237    def tick(self):237    def tick(self):
238        to_remove = []238        to_remove = []
239239
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] -= 1248                self.times[key] -= 1
249249
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
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op

f1import mathf1import math
2import copy2import copy
33
44
n5def custom_round_mul(a, k):n5def custom_round(result):
6    x = a * k
7    fractional_part = x % 16    fractional_part = result % 1
87
9    if fractional_part == 0:8    if fractional_part == 0:
n10        result = xn9        rounded_result = result
11    elif fractional_part <= 0.5:10    elif fractional_part <= 0.5:
n12        result = math.floor(x)n
13    else:
14        result = math.ceil(x)
15 
16    return result
17 
18 
19def 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)
2714
28    return rounded_result15    return rounded_result
2916
3017
31class FunctionWrapper:18class FunctionWrapper:
n32    """This class is used to implement function intensities"""n19    """A class used to implement function intensities"""
3320
34    def __init__(self, func, number_of_calls):21    def __init__(self, func, number_of_calls):
35        self.func = func22        self.func = func
36        self.number_of_calls = number_of_calls23        self.number_of_calls = number_of_calls
3724
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)
4128
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)
n45        result_function.number_of_calls = custom_round_mul(self.number_of_calls, number)n32        result_function.number_of_calls = custom_round(self.number_of_calls * number)
46        return result_function33        return result_function
4734
48    def __add__(self, other):35    def __add__(self, other):
n49        """Combine effects"""n36        """Combine effects of functions"""
50        result_function = FunctionWrapper(self.func, self.number_of_calls)37        result_function = FunctionWrapper(self.func, self.number_of_calls)
5138
52        # if the other function hasn't been wrapped yet39        # 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 += 141            result_function.number_of_calls += 1
55        else:42        else:
56            result_function.number_of_calls += other.number_of_calls43            result_function.number_of_calls += other.number_of_calls
57        return result_function44        return result_function
5845
59    def __truediv__(self, number):46    def __truediv__(self, number):
n60        """Divide effects"""n47        """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)
n62        result_function.number_of_calls = custom_round_div(result_function.number_of_calls, number)n49        result_function.number_of_calls = custom_round(result_function.number_of_calls / number)
63        return result_function50        return result_function
n64 n
65 
66class 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
8551
8652
87class Potion:53class Potion:
88    def __init__(self, effects, duration):54    def __init__(self, effects, duration):
n89        self.effects = effectsn55        self.effects = copy.deepcopy(effects)
90        self.duration = duration56        self.duration = duration
9157
92        self.usable = True58        self.usable = True
93        self.is_depleted = False59        self.is_depleted = False
94        # Store keys of depleted effects here60        # Store keys of depleted effects here
95        self.depleted_effects = set()61        self.depleted_effects = set()
9662
97        # Wrap all functions that are still not wrapped to make operations on them easier63        # 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_function67                self.effects[key] = wrapped_function
10268
nn69    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'):
n105            if object.__getattribute__(self, 'is_depleted'):n77            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)
11586
116    def __add__(self, other):87    def __add__(self, other):
n117        if self.is_depleted:n88        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.")
12189
122        max_duration = max(self.duration, other.duration)90        max_duration = max(self.duration, other.duration)
12391
124        # merge the two dictionaries of effects together92        # 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 overwritten94        # if there are common keys, their values will be overwritten
127        effects_merged.update(self.effects.copy())95        effects_merged.update(self.effects.copy())
12896
129        result_potion = Potion(effects_merged, max_duration)97        result_potion = Potion(effects_merged, max_duration)
13098
131        # Get the common functions for the potions99        # 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_calls102            result_potion.effects[name].number_of_calls += other.effects[name].number_of_calls
135103
136        self.usable = False104        self.usable = False
137        other.usable = False105        other.usable = False
138106
139        return result_potion107        return result_potion
140108
141    def __mul__(self, number):109    def __mul__(self, number):
n142        if self.is_depleted:n110        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.")
146111
147        multiplied_effects = dict(self.effects)112        multiplied_effects = dict(self.effects)
148113
149        for key in multiplied_effects:114        for key in multiplied_effects:
150            multiplied_effects[key] = multiplied_effects[key] * number115            multiplied_effects[key] = multiplied_effects[key] * number
151116
152        multiplied_potion = Potion(multiplied_effects, self.duration)117        multiplied_potion = Potion(multiplied_effects, self.duration)
153118
154        self.usable = False119        self.usable = False
155        return multiplied_potion120        return multiplied_potion
156121
157    def __sub__(self, other):122    def __sub__(self, other):
n158        if self.is_depleted:n123        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.")
162124
163        # Compare the keys of the two potions using set operations125        # 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")
166128
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())
169131
170        for name in common_names:132        for name in common_names:
171            left_intensity = self.effects[name].number_of_calls133            left_intensity = self.effects[name].number_of_calls
172            right_intensity = other.effects[name].number_of_calls134            right_intensity = other.effects[name].number_of_calls
173135
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_intensity139                purified.effects[name].number_of_calls = left_intensity - right_intensity
178140
179        self.usable = False141        self.usable = False
180        other.usable = False142        other.usable = False
nn143 
181        return purified144        return purified
182145
183    def __truediv__(self, number):146    def __truediv__(self, number):
n184        if self.is_depleted:n147        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.")
188148
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 = False150        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))
192152
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 False156                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 False158                return False
199        return True159        return True
200160
201    @staticmethod161    @staticmethod
202    def calculate_sums(left_potion, right_potion):162    def calculate_sums(left_potion, right_potion):
203        sum_left = 0163        sum_left = 0
204        sum_right = 0164        sum_right = 0
205165
206        for key in left_potion.effects:166        for key in left_potion.effects:
207            sum_left += left_potion.effects[key].number_of_calls167            sum_left += left_potion.effects[key].number_of_calls
208168
209        for key in right_potion.effects:169        for key in right_potion.effects:
210            sum_right += right_potion.effects[key].number_of_calls170            sum_right += right_potion.effects[key].number_of_calls
211171
212        return sum_left, sum_right172        return sum_left, sum_right
213173
214    def __lt__(self, other):174    def __lt__(self, other):
n215        sum_self, sum_other = (Potion.calculate_sums(self, other))n175        sum_self, sum_other = Potion.calculate_sums(self, other)
216        return sum_self < sum_other176        return sum_self < sum_other
217177
218    def __gt__(self, other):178    def __gt__(self, other):
n219        sum_self, sum_other = (Potion.calculate_sums(self, other))n179        sum_self, sum_other = Potion.calculate_sums(self, other)
220        return sum_self > sum_other180        return sum_self > sum_other
221181
222    def __ge__(self, other):182    def __ge__(self, other):
223        return NotImplemented183        return NotImplemented
224184
225    def __le__(self, other):185    def __le__(self, other):
226        return NotImplemented186        return NotImplemented
227187
228188
229class ГоспожатаПоХимия:189class ГоспожатаПоХимия:
230    def __init__(self):190    def __init__(self):
n231        self.active_potions = []n191        self.time = 0
232192
n233    @staticmethodn193        # 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)
237205
238    def apply(self, target, potion):206    def apply(self, target, potion):
n239        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.")
246209
nn210        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))
249220
250        for unused_effect in unused_effects:221        for unused_effect in unused_effects:
n251            potion.__getattribute__(unused_effect)(target)n222            effect = potion.__getattribute__(unused_effect)
223            self.effects[id(potion)].append(copy.deepcopy(effect))
224            effect(target)
252225
n253        self.active_potions.append([potion.duration, target, attributes_before_change])n
254        potion.is_depleted = True226        potion.is_depleted = True
255227
nn228    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):
n257        for active_potion in self.active_potions:n238        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'
n259            if active_potion[0] - 1 == 0:n242            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:
t262                active_potion[0] -= 1t248                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
Colors
 Added 
Changed
Deleted
Links
(f)irst change
(n)ext change
(t)op