1import math
2
3class Potion:
4 def __init__(self, effects, duration):
5 self.effects = effects
6 self.duration = duration
7 self.intensities = {}
8 self.used_effects = set()
9 self.used = False
10 self.depleted = False
11
12 for name in effects.keys():
13 self.intensities[name] = self.intensities.get(name, 0) + 1
14
15 def __getattr__(self, name):
16 if name not in self.effects.keys():
17 raise AttributeError("Attribute not found.")
18 if self.used:
19 raise TypeError("Potion is now part of something bigger than itself.")
20 if self.depleted:
21 raise TypeError("Potion is depleted.")
22 if name in self.used_effects:
23 raise TypeError("Effect is depleted.")
24
25 def execute_n_times(func):
26 def wrapper(target):
27 for _ in range(int(self.intensities.get(name))):
28 func(target)
29 return wrapper
30 self.used_effects.add(name)
31 return execute_n_times(self.effects.get(name))
32
33 def __add__(self, other):
34 validate(self)
35 validate(other)
36
37 new_duration = max(self.duration, other.duration)
38 combined_effects = {}
39 intensities = {}
40 used_effects = set()
41
42 for name, usage in self.effects.items():
43 combined_effects[name] = usage
44 intensities[name] = intensities.get(name) + 1 if name in intensities else self.intensities.get(name, 0)
45 used_effects = used_effects.union(self.used_effects)
46
47 for name, usage in other.effects.items():
48 combined_effects[name] = usage
49 intensities[name] = intensities.get(name) + 1 if name in intensities else other.intensities.get(name, 0)
50 used_effects = used_effects.union(other.used_effects)
51
52 self.used = True
53 other.used = True
54 potion = Potion(combined_effects, new_duration)
55 potion.intensities = intensities
56 potion.used_effects = used_effects
57 if len(potion.used_effects) == len(potion.effects):
58 potion.depleted = True
59 return potion
60
61 def __mul__(self, other):
62 validate(self)
63
64 if isinstance(other, (int, float)):
65 potion = Potion(self.effects.copy(), self.duration)
66 intensities = {}
67
68 for name, value in self.intensities.items():
69 intensities[name] = value * other
70 if 0 < other < 1:
71 intensities[name] = self._round_value(intensities[name])
72
73 potion.intensities = intensities
74 potion.used_effects = self.used_effects.copy()
75 if len(potion.used_effects) == len(potion.effects):
76 potion.depleted = True
77 self.used = True
78 return potion
79
80 def __sub__(self, other):
81 validate(self)
82 validate(other)
83
84 new_duration = other.duration
85 purified_effects = {}
86 intensities = {}
87 used_effects = set()
88
89 for name, usage in other.effects.items():
90 if name not in self.effects:
91 raise TypeError("Cannot purify potion.")
92 if other.intensities.get(name) >= self.intensities.get(name):
93 continue
94 else:
95 purified_effects[name] = usage
96 intensities[name] = self.intensities.get(name) - other.intensities.get(name)
97 if name in other.used_effects or name in self.used_effects:
98 used_effects.add(name)
99
100 for name, usage in self.effects.items():
101 if name not in other.effects.keys():
102 purified_effects[name] = usage
103 intensities[name] = self.intensities.get(name)
104 if name in self.used_effects:
105 used_effects.add(name)
106
107 self.used = True
108 other.used = True
109 potion = Potion(purified_effects, new_duration)
110 potion.intensities = intensities
111 potion.used_effects = used_effects
112 if len(potion.used_effects) == len(potion.effects):
113 potion.depleted = True
114 return potion
115
116 def __truediv__(self, other):
117 validate(self)
118
119 if isinstance(other, (int, float)):
120 intensities = {}
121
122 for name, intensity in self.intensities.items():
123 intensities[name] = self._round_value(intensity / other)
124
125 result = tuple(Potion(self.effects.copy(), self.duration) for _ in range(other))
126 for potion in result:
127 potion.intensities = intensities.copy()
128 potion.used_effects = self.used_effects.copy()
129 if len(potion.used_effects) == len(potion.effects):
130 potion.depleted = True
131 self.used = True
132 return result
133
134
135 def __eq__(self, other):
136 validate(self)
137 validate(other)
138
139 if not isinstance(other, Potion):
140 return False
141
142 if len(self.effects) != len(other.effects):
143 return False
144
145 for name in self.effects.keys():
146 if name not in other.effects:
147 return False
148 if self.intensities.get(name) != other.intensities.get(name):
149 if name in self.used_effects and name in other.used_effects:
150 continue
151 if (name in self.used_effects and other.intensities.get(name) == 0 or
152 name in other.used_effects and self.intensities.get(name) == 0):
153 continue
154 return False
155 return True
156
157 def __lt__(self, other):
158 validate(self)
159 validate(other)
160
161 if not isinstance(other, Potion):
162 return False
163
164 first_sum = sum([intensity for name, intensity in self.intensities.items() if name not in self.used_effects])
165 second_sum = sum([intensity for name, intensity in other.intensities.items() if name not in other.used_effects])
166 return first_sum < second_sum
167
168 def __gt__(self, other):
169 validate(self)
170 validate(other)
171
172 if not isinstance(other, Potion):
173 return False
174
175 first_sum = sum([intensity for name, intensity in self.intensities.items() if name not in self.used_effects])
176 second_sum = sum([intensity for name, intensity in other.intensities.items() if name not in other.used_effects])
177 return first_sum > second_sum
178
179 def __hash__(self):
180 return super().__hash__()
181
182 def _round_value(self, value):
183 if value % 1 > 0.5:
184 return int(math.ceil(value))
185 return int(math.floor(value))
186
187def validate(potion):
188 if potion.depleted:
189 raise TypeError("Potion is depleted.")
190 if potion.used:
191 raise TypeError("Potion is now part of something bigger than itself.")
192
193def ascii_sum(string):
194 return sum(ord(char) for char in string)
195
196class ГоспожатаПоХимия:
197 # {
198 # target: { initial_state },
199 # target2: { initial_state }
200 # }
201 _target_states = {}
202
203 # {
204 # target: [potion1, potion2],
205 # target2: [potion3]
206 # }
207 _target_potions = {}
208
209 # {
210 # target: {
211 # potion1: time1,
212 # potion2: time2
213 # },
214 # target2: {
215 # potion3: time3
216 # }
217 # }
218 _times = {}
219
220 def apply(self, target, potion):
221 validate(potion)
222
223 if target not in self._target_states:
224 self.save_initial_state(target)
225 if target not in self._times.keys():
226 self._times[target] = {}
227 if target not in self._target_potions.keys():
228 self._target_potions[target] = []
229
230 self._times[target][potion] = potion.duration
231 self._target_potions[target].append(potion)
232 sorted_effects = sorted(list(potion.effects), key=ascii_sum, reverse=True)
233 for effect in sorted_effects:
234 try:
235 getattr(potion, effect)(target)
236 except TypeError:
237 continue
238 potion.depleted = True
239 if potion.duration == 0:
240 self.reset_target(target)
241
242 def _apply_all(self, target):
243 for potion in self._target_potions[target]:
244 if potion.duration <= 0:
245 continue
246 for effect in sorted(list(potion.effects), key=ascii_sum, reverse=True):
247 potion.effects[effect](target)
248
249 def tick(self):
250 for target, potions in self._times.items():
251 for potion, time in potions.items():
252 self._times[target][potion] = time - 1
253
254 for target, potions in (self._times.copy()).items():
255 for potion, time in (potions.copy()).items():
256 if time <= 0:
257 self._times[target].pop(potion, None)
258 self._target_potions[target].remove(potion)
259 if len(self._times[target]) == 0:
260 self._times.pop(target, None)
261 self.reset_target(target)
262 if target not in self._times:
263 self._target_potions.pop(target, None)
264 self._target_states.pop(target, None)
265
266 def save_initial_state(self, target):
267 self._target_states[target] = {attr: getattr(target, attr) for attr in dir(target) if not attr.startswith('_')}
268
269 def reset_target(self, target):
270 for attr, initial_value in self._target_states[target].items():
271 setattr(target, attr, initial_value)
272 for attr in dir(target):
273 if not attr.startswith('_') and attr not in self._target_states[target].keys():
274 try:
275 delattr(target, attr)
276 except AttributeError:
277 continue
278
279 self._apply_all(target)
...................F
======================================================================
FAIL: test_ticking_mutable (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with mutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 446, in test_ticking_mutable
self.assertEqual(self._target.list_attr, [1, 2, 3])
AssertionError: Lists differ: [1, 2, 3, 4] != [1, 2, 3]
First list contains 1 additional elements.
First extra element 3:
4
- [1, 2, 3, 4]
? ---
+ [1, 2, 3]
----------------------------------------------------------------------
Ran 20 tests in 0.002s
FAILED (failures=1)
Георги Кунчев
04.12.2023 11:24Доста чисто решение. Имах някакви дребни коментари, но цялостният стил е супер.
|
f | 1 | import math | f | 1 | import math |
2 | 2 | ||||
3 | class Potion: | 3 | class Potion: | ||
4 | def __init__(self, effects, duration): | 4 | def __init__(self, effects, duration): | ||
5 | self.effects = effects | 5 | self.effects = effects | ||
6 | self.duration = duration | 6 | self.duration = duration | ||
7 | self.intensities = {} | 7 | self.intensities = {} | ||
8 | self.used_effects = set() | 8 | self.used_effects = set() | ||
9 | self.used = False | 9 | self.used = False | ||
10 | self.depleted = False | 10 | self.depleted = False | ||
11 | 11 | ||||
12 | for name in effects.keys(): | 12 | for name in effects.keys(): | ||
13 | self.intensities[name] = self.intensities.get(name, 0) + 1 | 13 | self.intensities[name] = self.intensities.get(name, 0) + 1 | ||
14 | 14 | ||||
15 | def __getattr__(self, name): | 15 | def __getattr__(self, name): | ||
16 | if name not in self.effects.keys(): | 16 | if name not in self.effects.keys(): | ||
17 | raise AttributeError("Attribute not found.") | 17 | raise AttributeError("Attribute not found.") | ||
18 | if self.used: | 18 | if self.used: | ||
19 | raise TypeError("Potion is now part of something bigger than itself.") | 19 | raise TypeError("Potion is now part of something bigger than itself.") | ||
n | n | 20 | if self.depleted: | ||
21 | raise TypeError("Potion is depleted.") | ||||
20 | if name in self.used_effects: | 22 | if name in self.used_effects: | ||
21 | raise TypeError("Effect is depleted.") | 23 | raise TypeError("Effect is depleted.") | ||
22 | 24 | ||||
23 | def execute_n_times(func): | 25 | def execute_n_times(func): | ||
24 | def wrapper(target): | 26 | def wrapper(target): | ||
25 | for _ in range(int(self.intensities.get(name))): | 27 | for _ in range(int(self.intensities.get(name))): | ||
26 | func(target) | 28 | func(target) | ||
27 | return wrapper | 29 | return wrapper | ||
28 | self.used_effects.add(name) | 30 | self.used_effects.add(name) | ||
n | 29 | if len(self.used_effects) == len(self.effects): | n | ||
30 | self.depleted = True | ||||
31 | return execute_n_times(self.effects.get(name)) | 31 | return execute_n_times(self.effects.get(name)) | ||
32 | 32 | ||||
33 | def __add__(self, other): | 33 | def __add__(self, other): | ||
34 | validate(self) | 34 | validate(self) | ||
35 | validate(other) | 35 | validate(other) | ||
36 | 36 | ||||
37 | new_duration = max(self.duration, other.duration) | 37 | new_duration = max(self.duration, other.duration) | ||
38 | combined_effects = {} | 38 | combined_effects = {} | ||
39 | intensities = {} | 39 | intensities = {} | ||
40 | used_effects = set() | 40 | used_effects = set() | ||
41 | 41 | ||||
42 | for name, usage in self.effects.items(): | 42 | for name, usage in self.effects.items(): | ||
43 | combined_effects[name] = usage | 43 | combined_effects[name] = usage | ||
44 | intensities[name] = intensities.get(name) + 1 if name in intensities else self.intensities.get(name, 0) | 44 | intensities[name] = intensities.get(name) + 1 if name in intensities else self.intensities.get(name, 0) | ||
45 | used_effects = used_effects.union(self.used_effects) | 45 | used_effects = used_effects.union(self.used_effects) | ||
46 | 46 | ||||
47 | for name, usage in other.effects.items(): | 47 | for name, usage in other.effects.items(): | ||
48 | combined_effects[name] = usage | 48 | combined_effects[name] = usage | ||
49 | intensities[name] = intensities.get(name) + 1 if name in intensities else other.intensities.get(name, 0) | 49 | intensities[name] = intensities.get(name) + 1 if name in intensities else other.intensities.get(name, 0) | ||
50 | used_effects = used_effects.union(other.used_effects) | 50 | used_effects = used_effects.union(other.used_effects) | ||
51 | 51 | ||||
52 | self.used = True | 52 | self.used = True | ||
53 | other.used = True | 53 | other.used = True | ||
54 | potion = Potion(combined_effects, new_duration) | 54 | potion = Potion(combined_effects, new_duration) | ||
55 | potion.intensities = intensities | 55 | potion.intensities = intensities | ||
56 | potion.used_effects = used_effects | 56 | potion.used_effects = used_effects | ||
57 | if len(potion.used_effects) == len(potion.effects): | 57 | if len(potion.used_effects) == len(potion.effects): | ||
58 | potion.depleted = True | 58 | potion.depleted = True | ||
59 | return potion | 59 | return potion | ||
60 | 60 | ||||
61 | def __mul__(self, other): | 61 | def __mul__(self, other): | ||
62 | validate(self) | 62 | validate(self) | ||
63 | 63 | ||||
64 | if isinstance(other, (int, float)): | 64 | if isinstance(other, (int, float)): | ||
65 | potion = Potion(self.effects.copy(), self.duration) | 65 | potion = Potion(self.effects.copy(), self.duration) | ||
66 | intensities = {} | 66 | intensities = {} | ||
67 | 67 | ||||
68 | for name, value in self.intensities.items(): | 68 | for name, value in self.intensities.items(): | ||
69 | intensities[name] = value * other | 69 | intensities[name] = value * other | ||
70 | if 0 < other < 1: | 70 | if 0 < other < 1: | ||
71 | intensities[name] = self._round_value(intensities[name]) | 71 | intensities[name] = self._round_value(intensities[name]) | ||
72 | 72 | ||||
73 | potion.intensities = intensities | 73 | potion.intensities = intensities | ||
74 | potion.used_effects = self.used_effects.copy() | 74 | potion.used_effects = self.used_effects.copy() | ||
75 | if len(potion.used_effects) == len(potion.effects): | 75 | if len(potion.used_effects) == len(potion.effects): | ||
76 | potion.depleted = True | 76 | potion.depleted = True | ||
77 | self.used = True | 77 | self.used = True | ||
78 | return potion | 78 | return potion | ||
79 | 79 | ||||
80 | def __sub__(self, other): | 80 | def __sub__(self, other): | ||
81 | validate(self) | 81 | validate(self) | ||
82 | validate(other) | 82 | validate(other) | ||
83 | 83 | ||||
84 | new_duration = other.duration | 84 | new_duration = other.duration | ||
85 | purified_effects = {} | 85 | purified_effects = {} | ||
86 | intensities = {} | 86 | intensities = {} | ||
87 | used_effects = set() | 87 | used_effects = set() | ||
88 | 88 | ||||
89 | for name, usage in other.effects.items(): | 89 | for name, usage in other.effects.items(): | ||
90 | if name not in self.effects: | 90 | if name not in self.effects: | ||
91 | raise TypeError("Cannot purify potion.") | 91 | raise TypeError("Cannot purify potion.") | ||
92 | if other.intensities.get(name) >= self.intensities.get(name): | 92 | if other.intensities.get(name) >= self.intensities.get(name): | ||
93 | continue | 93 | continue | ||
94 | else: | 94 | else: | ||
95 | purified_effects[name] = usage | 95 | purified_effects[name] = usage | ||
96 | intensities[name] = self.intensities.get(name) - other.intensities.get(name) | 96 | intensities[name] = self.intensities.get(name) - other.intensities.get(name) | ||
n | 97 | if name in other.used_effects: | n | 97 | if name in other.used_effects or name in self.used_effects: |
98 | used_effects.add(name) | 98 | used_effects.add(name) | ||
99 | 99 | ||||
100 | for name, usage in self.effects.items(): | 100 | for name, usage in self.effects.items(): | ||
101 | if name not in other.effects.keys(): | 101 | if name not in other.effects.keys(): | ||
102 | purified_effects[name] = usage | 102 | purified_effects[name] = usage | ||
103 | intensities[name] = self.intensities.get(name) | 103 | intensities[name] = self.intensities.get(name) | ||
104 | if name in self.used_effects: | 104 | if name in self.used_effects: | ||
105 | used_effects.add(name) | 105 | used_effects.add(name) | ||
106 | 106 | ||||
107 | self.used = True | 107 | self.used = True | ||
108 | other.used = True | 108 | other.used = True | ||
109 | potion = Potion(purified_effects, new_duration) | 109 | potion = Potion(purified_effects, new_duration) | ||
110 | potion.intensities = intensities | 110 | potion.intensities = intensities | ||
111 | potion.used_effects = used_effects | 111 | potion.used_effects = used_effects | ||
112 | if len(potion.used_effects) == len(potion.effects): | 112 | if len(potion.used_effects) == len(potion.effects): | ||
n | 113 | potion.depleted = True | n | 113 | potion.depleted = True |
114 | return potion | 114 | return potion | ||
115 | 115 | ||||
116 | def __truediv__(self, other): | 116 | def __truediv__(self, other): | ||
117 | validate(self) | 117 | validate(self) | ||
118 | 118 | ||||
119 | if isinstance(other, (int, float)): | 119 | if isinstance(other, (int, float)): | ||
120 | intensities = {} | 120 | intensities = {} | ||
121 | 121 | ||||
122 | for name, intensity in self.intensities.items(): | 122 | for name, intensity in self.intensities.items(): | ||
123 | intensities[name] = self._round_value(intensity / other) | 123 | intensities[name] = self._round_value(intensity / other) | ||
124 | 124 | ||||
125 | result = tuple(Potion(self.effects.copy(), self.duration) for _ in range(other)) | 125 | result = tuple(Potion(self.effects.copy(), self.duration) for _ in range(other)) | ||
126 | for potion in result: | 126 | for potion in result: | ||
127 | potion.intensities = intensities.copy() | 127 | potion.intensities = intensities.copy() | ||
128 | potion.used_effects = self.used_effects.copy() | 128 | potion.used_effects = self.used_effects.copy() | ||
129 | if len(potion.used_effects) == len(potion.effects): | 129 | if len(potion.used_effects) == len(potion.effects): | ||
130 | potion.depleted = True | 130 | potion.depleted = True | ||
131 | self.used = True | 131 | self.used = True | ||
132 | return result | 132 | return result | ||
133 | 133 | ||||
134 | 134 | ||||
135 | def __eq__(self, other): | 135 | def __eq__(self, other): | ||
136 | validate(self) | 136 | validate(self) | ||
137 | validate(other) | 137 | validate(other) | ||
138 | 138 | ||||
139 | if not isinstance(other, Potion): | 139 | if not isinstance(other, Potion): | ||
140 | return False | 140 | return False | ||
141 | 141 | ||||
142 | if len(self.effects) != len(other.effects): | 142 | if len(self.effects) != len(other.effects): | ||
143 | return False | 143 | return False | ||
144 | 144 | ||||
145 | for name in self.effects.keys(): | 145 | for name in self.effects.keys(): | ||
146 | if name not in other.effects: | 146 | if name not in other.effects: | ||
147 | return False | 147 | return False | ||
148 | if self.intensities.get(name) != other.intensities.get(name): | 148 | if self.intensities.get(name) != other.intensities.get(name): | ||
n | n | 149 | if name in self.used_effects and name in other.used_effects: | ||
150 | continue | ||||
151 | if (name in self.used_effects and other.intensities.get(name) == 0 or | ||||
152 | name in other.used_effects and self.intensities.get(name) == 0): | ||||
153 | continue | ||||
149 | return False | 154 | return False | ||
150 | return True | 155 | return True | ||
151 | 156 | ||||
152 | def __lt__(self, other): | 157 | def __lt__(self, other): | ||
153 | validate(self) | 158 | validate(self) | ||
154 | validate(other) | 159 | validate(other) | ||
155 | 160 | ||||
156 | if not isinstance(other, Potion): | 161 | if not isinstance(other, Potion): | ||
157 | return False | 162 | return False | ||
158 | 163 | ||||
159 | first_sum = sum([intensity for name, intensity in self.intensities.items() if name not in self.used_effects]) | 164 | first_sum = sum([intensity for name, intensity in self.intensities.items() if name not in self.used_effects]) | ||
160 | second_sum = sum([intensity for name, intensity in other.intensities.items() if name not in other.used_effects]) | 165 | second_sum = sum([intensity for name, intensity in other.intensities.items() if name not in other.used_effects]) | ||
161 | return first_sum < second_sum | 166 | return first_sum < second_sum | ||
162 | 167 | ||||
163 | def __gt__(self, other): | 168 | def __gt__(self, other): | ||
164 | validate(self) | 169 | validate(self) | ||
165 | validate(other) | 170 | validate(other) | ||
166 | 171 | ||||
167 | if not isinstance(other, Potion): | 172 | if not isinstance(other, Potion): | ||
168 | return False | 173 | return False | ||
169 | 174 | ||||
170 | first_sum = sum([intensity for name, intensity in self.intensities.items() if name not in self.used_effects]) | 175 | first_sum = sum([intensity for name, intensity in self.intensities.items() if name not in self.used_effects]) | ||
171 | second_sum = sum([intensity for name, intensity in other.intensities.items() if name not in other.used_effects]) | 176 | second_sum = sum([intensity for name, intensity in other.intensities.items() if name not in other.used_effects]) | ||
172 | return first_sum > second_sum | 177 | return first_sum > second_sum | ||
173 | 178 | ||||
174 | def __hash__(self): | 179 | def __hash__(self): | ||
175 | return super().__hash__() | 180 | return super().__hash__() | ||
176 | 181 | ||||
177 | def _round_value(self, value): | 182 | def _round_value(self, value): | ||
178 | if value % 1 > 0.5: | 183 | if value % 1 > 0.5: | ||
179 | return int(math.ceil(value)) | 184 | return int(math.ceil(value)) | ||
180 | return int(math.floor(value)) | 185 | return int(math.floor(value)) | ||
181 | 186 | ||||
182 | def validate(potion): | 187 | def validate(potion): | ||
183 | if potion.depleted: | 188 | if potion.depleted: | ||
184 | raise TypeError("Potion is depleted.") | 189 | raise TypeError("Potion is depleted.") | ||
185 | if potion.used: | 190 | if potion.used: | ||
186 | raise TypeError("Potion is now part of something bigger than itself.") | 191 | raise TypeError("Potion is now part of something bigger than itself.") | ||
n | n | 192 | |||
193 | def ascii_sum(string): | ||||
194 | return sum(ord(char) for char in string) | ||||
187 | 195 | ||||
188 | class ГоспожатаПоХимия: | 196 | class ГоспожатаПоХимия: | ||
189 | # { | 197 | # { | ||
190 | # target: { initial_state }, | 198 | # target: { initial_state }, | ||
191 | # target2: { initial_state } | 199 | # target2: { initial_state } | ||
192 | # } | 200 | # } | ||
193 | _target_states = {} | 201 | _target_states = {} | ||
194 | 202 | ||||
195 | # { | 203 | # { | ||
196 | # target: [potion1, potion2], | 204 | # target: [potion1, potion2], | ||
197 | # target2: [potion3] | 205 | # target2: [potion3] | ||
198 | # } | 206 | # } | ||
199 | _target_potions = {} | 207 | _target_potions = {} | ||
200 | 208 | ||||
201 | # { | 209 | # { | ||
202 | # target: { | 210 | # target: { | ||
203 | # potion1: time1, | 211 | # potion1: time1, | ||
204 | # potion2: time2 | 212 | # potion2: time2 | ||
205 | # }, | 213 | # }, | ||
206 | # target2: { | 214 | # target2: { | ||
207 | # potion3: time3 | 215 | # potion3: time3 | ||
208 | # } | 216 | # } | ||
209 | # } | 217 | # } | ||
210 | _times = {} | 218 | _times = {} | ||
211 | 219 | ||||
212 | def apply(self, target, potion): | 220 | def apply(self, target, potion): | ||
213 | validate(potion) | 221 | validate(potion) | ||
214 | 222 | ||||
215 | if target not in self._target_states: | 223 | if target not in self._target_states: | ||
216 | self.save_initial_state(target) | 224 | self.save_initial_state(target) | ||
217 | if target not in self._times.keys(): | 225 | if target not in self._times.keys(): | ||
218 | self._times[target] = {} | 226 | self._times[target] = {} | ||
219 | if target not in self._target_potions.keys(): | 227 | if target not in self._target_potions.keys(): | ||
220 | self._target_potions[target] = [] | 228 | self._target_potions[target] = [] | ||
221 | 229 | ||||
222 | self._times[target][potion] = potion.duration | 230 | self._times[target][potion] = potion.duration | ||
223 | self._target_potions[target].append(potion) | 231 | self._target_potions[target].append(potion) | ||
n | 224 | sorted_effects = list(sorted(potion.effects)) | n | 232 | sorted_effects = sorted(list(potion.effects), key=ascii_sum, reverse=True) |
225 | for effect in sorted_effects: | 233 | for effect in sorted_effects: | ||
226 | try: | 234 | try: | ||
227 | getattr(potion, effect)(target) | 235 | getattr(potion, effect)(target) | ||
228 | except TypeError: | 236 | except TypeError: | ||
229 | continue | 237 | continue | ||
n | n | 238 | potion.depleted = True | ||
239 | if potion.duration == 0: | ||||
240 | self.reset_target(target) | ||||
230 | 241 | ||||
231 | def _apply_all(self, target): | 242 | def _apply_all(self, target): | ||
232 | for potion in self._target_potions[target]: | 243 | for potion in self._target_potions[target]: | ||
n | 233 | for effect in list(sorted(potion.effects)): | n | 244 | if potion.duration <= 0: |
245 | continue | ||||
246 | for effect in sorted(list(potion.effects), key=ascii_sum, reverse=True): | ||||
234 | potion.effects[effect](target) | 247 | potion.effects[effect](target) | ||
235 | 248 | ||||
236 | def tick(self): | 249 | def tick(self): | ||
237 | for target, potions in self._times.items(): | 250 | for target, potions in self._times.items(): | ||
238 | for potion, time in potions.items(): | 251 | for potion, time in potions.items(): | ||
239 | self._times[target][potion] = time - 1 | 252 | self._times[target][potion] = time - 1 | ||
240 | 253 | ||||
241 | for target, potions in (self._times.copy()).items(): | 254 | for target, potions in (self._times.copy()).items(): | ||
242 | for potion, time in (potions.copy()).items(): | 255 | for potion, time in (potions.copy()).items(): | ||
n | 243 | if time == 0: | n | 256 | if time <= 0: |
244 | self._times[target].pop(potion, None) | 257 | self._times[target].pop(potion, None) | ||
245 | self._target_potions[target].remove(potion) | 258 | self._target_potions[target].remove(potion) | ||
246 | if len(self._times[target]) == 0: | 259 | if len(self._times[target]) == 0: | ||
247 | self._times.pop(target, None) | 260 | self._times.pop(target, None) | ||
248 | self.reset_target(target) | 261 | self.reset_target(target) | ||
249 | if target not in self._times: | 262 | if target not in self._times: | ||
250 | self._target_potions.pop(target, None) | 263 | self._target_potions.pop(target, None) | ||
251 | self._target_states.pop(target, None) | 264 | self._target_states.pop(target, None) | ||
252 | 265 | ||||
253 | def save_initial_state(self, target): | 266 | def save_initial_state(self, target): | ||
254 | self._target_states[target] = {attr: getattr(target, attr) for attr in dir(target) if not attr.startswith('_')} | 267 | self._target_states[target] = {attr: getattr(target, attr) for attr in dir(target) if not attr.startswith('_')} | ||
255 | 268 | ||||
256 | def reset_target(self, target): | 269 | def reset_target(self, target): | ||
257 | for attr, initial_value in self._target_states[target].items(): | 270 | for attr, initial_value in self._target_states[target].items(): | ||
258 | setattr(target, attr, initial_value) | 271 | setattr(target, attr, initial_value) | ||
259 | for attr in dir(target): | 272 | for attr in dir(target): | ||
260 | if not attr.startswith('_') and attr not in self._target_states[target].keys(): | 273 | if not attr.startswith('_') and attr not in self._target_states[target].keys(): | ||
261 | try: | 274 | try: | ||
262 | delattr(target, attr) | 275 | delattr(target, attr) | ||
263 | except AttributeError: | 276 | except AttributeError: | ||
264 | continue | 277 | continue | ||
265 | 278 | ||||
266 | self._apply_all(target) | 279 | self._apply_all(target) | ||
t | 267 | t | |||
268 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
f | 1 | import math | f | 1 | import math |
2 | 2 | ||||
3 | class Potion: | 3 | class Potion: | ||
4 | def __init__(self, effects, duration): | 4 | def __init__(self, effects, duration): | ||
5 | self.effects = effects | 5 | self.effects = effects | ||
6 | self.duration = duration | 6 | self.duration = duration | ||
7 | self.intensities = {} | 7 | self.intensities = {} | ||
8 | self.used_effects = set() | 8 | self.used_effects = set() | ||
9 | self.used = False | 9 | self.used = False | ||
10 | self.depleted = False | 10 | self.depleted = False | ||
11 | 11 | ||||
12 | for name in effects.keys(): | 12 | for name in effects.keys(): | ||
13 | self.intensities[name] = self.intensities.get(name, 0) + 1 | 13 | self.intensities[name] = self.intensities.get(name, 0) + 1 | ||
n | 14 | n | 14 | ||
15 | def __getattr__(self, name): | 15 | def __getattr__(self, name): | ||
n | n | 16 | if name not in self.effects.keys(): | ||
17 | raise AttributeError("Attribute not found.") | ||||
16 | if self.used: | 18 | if self.used: | ||
17 | raise TypeError("Potion is now part of something bigger than itself.") | 19 | raise TypeError("Potion is now part of something bigger than itself.") | ||
n | 18 | elif name in self.used_effects: | n | 20 | if name in self.used_effects: |
19 | raise TypeError("Effect is depleted.") | 21 | raise TypeError("Effect is depleted.") | ||
n | 20 | elif name in self.effects.keys(): | n | 22 | |
21 | def execute_n_times(func): | 23 | def execute_n_times(func): | ||
22 | def wrapper(target): | 24 | def wrapper(target): | ||
23 | for _ in range(self.intensities.get(name)): | 25 | for _ in range(int(self.intensities.get(name))): | ||
24 | func(target) | 26 | func(target) | ||
25 | return wrapper | 27 | return wrapper | ||
26 | self.used_effects.add(name) | 28 | self.used_effects.add(name) | ||
27 | if len(self.used_effects) == len(self.effects): | 29 | if len(self.used_effects) == len(self.effects): | ||
28 | self.depleted = True | 30 | self.depleted = True | ||
29 | return execute_n_times(self.effects.get(name)) | 31 | return execute_n_times(self.effects.get(name)) | ||
30 | else: | 32 | |||
31 | raise AttributeError("Attribute not found") | ||||
32 | |||||
33 | def __add__(self, other): | 33 | def __add__(self, other): | ||
n | 34 | if self.depleted: | n | 34 | validate(self) |
35 | raise TypeError("Potion is depleted.") | 35 | validate(other) | ||
36 | if self.used or other.used: | ||||
37 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
38 | 36 | ||||
n | 39 | new_duration = other.duration if other.duration > self.duration else self.duration | n | 37 | new_duration = max(self.duration, other.duration) |
40 | combined_effects = {} | 38 | combined_effects = {} | ||
41 | intensities = {} | 39 | intensities = {} | ||
42 | used_effects = set() | 40 | used_effects = set() | ||
43 | 41 | ||||
44 | for name, usage in self.effects.items(): | 42 | for name, usage in self.effects.items(): | ||
45 | combined_effects[name] = usage | 43 | combined_effects[name] = usage | ||
46 | intensities[name] = intensities.get(name) + 1 if name in intensities else self.intensities.get(name, 0) | 44 | intensities[name] = intensities.get(name) + 1 if name in intensities else self.intensities.get(name, 0) | ||
47 | used_effects = used_effects.union(self.used_effects) | 45 | used_effects = used_effects.union(self.used_effects) | ||
48 | 46 | ||||
49 | for name, usage in other.effects.items(): | 47 | for name, usage in other.effects.items(): | ||
50 | combined_effects[name] = usage | 48 | combined_effects[name] = usage | ||
51 | intensities[name] = intensities.get(name) + 1 if name in intensities else other.intensities.get(name, 0) | 49 | intensities[name] = intensities.get(name) + 1 if name in intensities else other.intensities.get(name, 0) | ||
n | 52 | used_effects = used_effects.union(self.used_effects) | n | 50 | used_effects = used_effects.union(other.used_effects) |
53 | 51 | ||||
54 | self.used = True | 52 | self.used = True | ||
55 | other.used = True | 53 | other.used = True | ||
56 | potion = Potion(combined_effects, new_duration) | 54 | potion = Potion(combined_effects, new_duration) | ||
57 | potion.intensities = intensities | 55 | potion.intensities = intensities | ||
58 | potion.used_effects = used_effects | 56 | potion.used_effects = used_effects | ||
59 | if len(potion.used_effects) == len(potion.effects): | 57 | if len(potion.used_effects) == len(potion.effects): | ||
n | n | 58 | potion.depleted = True | ||
59 | return potion | ||||
60 | |||||
61 | def __mul__(self, other): | ||||
62 | validate(self) | ||||
63 | |||||
64 | if isinstance(other, (int, float)): | ||||
65 | potion = Potion(self.effects.copy(), self.duration) | ||||
66 | intensities = {} | ||||
67 | |||||
68 | for name, value in self.intensities.items(): | ||||
69 | intensities[name] = value * other | ||||
70 | if 0 < other < 1: | ||||
71 | intensities[name] = self._round_value(intensities[name]) | ||||
72 | |||||
73 | potion.intensities = intensities | ||||
74 | potion.used_effects = self.used_effects.copy() | ||||
75 | if len(potion.used_effects) == len(potion.effects): | ||||
60 | potion.depleted = True | 76 | potion.depleted = True | ||
n | 61 | return potion | n | ||
62 | |||||
63 | def __mul__(self, other): | ||||
64 | if self.depleted: | ||||
65 | raise TypeError("Potion is depleted.") | ||||
66 | if self.used: | ||||
67 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
68 | |||||
69 | potion = Potion(self.effects, self.duration) | ||||
70 | intensities = {} | ||||
71 | |||||
72 | if isinstance(other, (int, float)): | ||||
73 | for name, value in self.intensities.items(): | ||||
74 | intensities[name] = value * other | ||||
75 | if other > 0 and other < 1: | ||||
76 | if intensities[name] % 1 > 0.5: | ||||
77 | intensities[name]= int(math.ceil(intensities[name])) | ||||
78 | else: | ||||
79 | intensities[name] = int(math.floor(intensities[name])) | ||||
80 | |||||
81 | potion.intensities = intensities | ||||
82 | potion.used_effects = self.used_effects.copy() | ||||
83 | if len(potion.used_effects) == len(potion.effects): | ||||
84 | potion.depleted = True | ||||
85 | self.used = True | 77 | self.used = True | ||
86 | return potion | 78 | return potion | ||
87 | 79 | ||||
88 | def __sub__(self, other): | 80 | def __sub__(self, other): | ||
n | 89 | if self.depleted: | n | 81 | validate(self) |
90 | raise TypeError("Potion is depleted.") | 82 | validate(other) | ||
91 | if self.used or other.used: | ||||
92 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
93 | 83 | ||||
94 | new_duration = other.duration | 84 | new_duration = other.duration | ||
n | 95 | combined_effects = {} | n | 85 | purified_effects = {} |
96 | intensities = {} | 86 | intensities = {} | ||
97 | used_effects = set() | 87 | used_effects = set() | ||
98 | 88 | ||||
99 | for name, usage in other.effects.items(): | 89 | for name, usage in other.effects.items(): | ||
100 | if name not in self.effects: | 90 | if name not in self.effects: | ||
n | 101 | raise TypeError("Cannot subtract") | n | 91 | raise TypeError("Cannot purify potion.") |
102 | if other.intensities.get(name) >= self.intensities.get(name): | 92 | if other.intensities.get(name) >= self.intensities.get(name): | ||
103 | continue | 93 | continue | ||
104 | else: | 94 | else: | ||
n | 105 | combined_effects[name] = usage | n | 95 | purified_effects[name] = usage |
106 | intensities[name] = self.intensities.get(name) - other.intensities.get(name) | 96 | intensities[name] = self.intensities.get(name) - other.intensities.get(name) | ||
n | 107 | used_effects = used_effects.union(other.used_effects) | n | 97 | if name in other.used_effects: |
98 | used_effects.add(name) | ||||
108 | 99 | ||||
109 | for name, usage in self.effects.items(): | 100 | for name, usage in self.effects.items(): | ||
110 | if name not in other.effects.keys(): | 101 | if name not in other.effects.keys(): | ||
n | 111 | combined_effects[name] = usage | n | 102 | purified_effects[name] = usage |
112 | intensities[name] = self.intensities.get(name) | 103 | intensities[name] = self.intensities.get(name) | ||
n | 113 | used_effects = used_effects.union(self.used_effects) | n | 104 | if name in self.used_effects: |
105 | used_effects.add(name) | ||||
114 | 106 | ||||
115 | self.used = True | 107 | self.used = True | ||
116 | other.used = True | 108 | other.used = True | ||
n | 117 | potion = Potion(combined_effects, new_duration) | n | 109 | potion = Potion(purified_effects, new_duration) |
118 | potion.intensities = intensities | 110 | potion.intensities = intensities | ||
119 | potion.used_effects = used_effects | 111 | potion.used_effects = used_effects | ||
120 | if len(potion.used_effects) == len(potion.effects): | 112 | if len(potion.used_effects) == len(potion.effects): | ||
121 | potion.depleted = True | 113 | potion.depleted = True | ||
122 | return potion | 114 | return potion | ||
n | 123 | n | 115 | ||
124 | def __truediv__(self, other): | 116 | def __truediv__(self, other): | ||
n | 125 | if self.depleted: | n | 117 | validate(self) |
126 | raise TypeError("Potion is depleted.") | ||||
127 | if self.used: | ||||
128 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
129 | 118 | ||||
130 | if isinstance(other, (int, float)): | 119 | if isinstance(other, (int, float)): | ||
131 | intensities = {} | 120 | intensities = {} | ||
132 | 121 | ||||
133 | for name, intensity in self.intensities.items(): | 122 | for name, intensity in self.intensities.items(): | ||
n | 134 | new_intensity = intensity / other | n | ||
135 | if other > 0 and other < 1: | ||||
136 | if new_intensity % 1 > 0.5: | ||||
137 | new_intensity = math.ceil(new_intensity) | ||||
138 | else: | ||||
139 | new_intensity = math.floor(new_intensity) | ||||
140 | intensities[name] = int(new_intensity) | 123 | intensities[name] = self._round_value(intensity / other) | ||
141 | 124 | ||||
142 | result = tuple(Potion(self.effects.copy(), self.duration) for _ in range(other)) | 125 | result = tuple(Potion(self.effects.copy(), self.duration) for _ in range(other)) | ||
143 | for potion in result: | 126 | for potion in result: | ||
144 | potion.intensities = intensities.copy() | 127 | potion.intensities = intensities.copy() | ||
145 | potion.used_effects = self.used_effects.copy() | 128 | potion.used_effects = self.used_effects.copy() | ||
146 | if len(potion.used_effects) == len(potion.effects): | 129 | if len(potion.used_effects) == len(potion.effects): | ||
147 | potion.depleted = True | 130 | potion.depleted = True | ||
n | n | 131 | self.used = True | ||
148 | return result | 132 | return result | ||
149 | 133 | ||||
150 | 134 | ||||
151 | def __eq__(self, other): | 135 | def __eq__(self, other): | ||
n | n | 136 | validate(self) | ||
137 | validate(other) | ||||
138 | |||||
152 | if not isinstance(other, Potion): | 139 | if not isinstance(other, Potion): | ||
153 | return False | 140 | return False | ||
154 | 141 | ||||
155 | if len(self.effects) != len(other.effects): | 142 | if len(self.effects) != len(other.effects): | ||
156 | return False | 143 | return False | ||
157 | 144 | ||||
158 | for name in self.effects.keys(): | 145 | for name in self.effects.keys(): | ||
159 | if name not in other.effects: | 146 | if name not in other.effects: | ||
160 | return False | 147 | return False | ||
161 | if self.intensities.get(name) != other.intensities.get(name): | 148 | if self.intensities.get(name) != other.intensities.get(name): | ||
162 | return False | 149 | return False | ||
163 | return True | 150 | return True | ||
164 | 151 | ||||
165 | def __lt__(self, other): | 152 | def __lt__(self, other): | ||
n | n | 153 | validate(self) | ||
154 | validate(other) | ||||
155 | |||||
156 | if not isinstance(other, Potion): | ||||
157 | return False | ||||
158 | |||||
166 | first_sum = sum([intensity for intensity in self.intensities.values()]) | 159 | first_sum = sum([intensity for name, intensity in self.intensities.items() if name not in self.used_effects]) | ||
167 | second_sum = sum([intensity for intensity in other.intensities.values()]) | 160 | second_sum = sum([intensity for name, intensity in other.intensities.items() if name not in other.used_effects]) | ||
168 | return first_sum < second_sum | 161 | return first_sum < second_sum | ||
169 | 162 | ||||
170 | def __gt__(self, other): | 163 | def __gt__(self, other): | ||
n | n | 164 | validate(self) | ||
165 | validate(other) | ||||
166 | |||||
167 | if not isinstance(other, Potion): | ||||
168 | return False | ||||
169 | |||||
171 | first_sum = sum([intensity for intensity in self.intensities.values()]) | 170 | first_sum = sum([intensity for name, intensity in self.intensities.items() if name not in self.used_effects]) | ||
172 | second_sum = sum([intensity for intensity in other.intensities.values()]) | 171 | second_sum = sum([intensity for name, intensity in other.intensities.items() if name not in other.used_effects]) | ||
173 | return first_sum > second_sum | 172 | return first_sum > second_sum | ||
174 | 173 | ||||
175 | def __hash__(self): | 174 | def __hash__(self): | ||
176 | return super().__hash__() | 175 | return super().__hash__() | ||
n | n | 176 | |||
177 | def _round_value(self, value): | ||||
178 | if value % 1 > 0.5: | ||||
179 | return int(math.ceil(value)) | ||||
180 | return int(math.floor(value)) | ||||
177 | 181 | ||||
n | n | 182 | def validate(potion): | ||
183 | if potion.depleted: | ||||
184 | raise TypeError("Potion is depleted.") | ||||
185 | if potion.used: | ||||
186 | raise TypeError("Potion is now part of something bigger than itself.") | ||||
178 | 187 | ||||
179 | class ГоспожатаПоХимия: | 188 | class ГоспожатаПоХимия: | ||
180 | # { | 189 | # { | ||
181 | # target: { initial_state }, | 190 | # target: { initial_state }, | ||
182 | # target2: { initial_state } | 191 | # target2: { initial_state } | ||
183 | # } | 192 | # } | ||
184 | _target_states = {} | 193 | _target_states = {} | ||
185 | 194 | ||||
186 | # { | 195 | # { | ||
187 | # target: [potion1, potion2], | 196 | # target: [potion1, potion2], | ||
188 | # target2: [potion3] | 197 | # target2: [potion3] | ||
189 | # } | 198 | # } | ||
190 | _target_potions = {} | 199 | _target_potions = {} | ||
191 | 200 | ||||
192 | # { | 201 | # { | ||
193 | # target: { | 202 | # target: { | ||
194 | # potion1: time1, | 203 | # potion1: time1, | ||
195 | # potion2: time2 | 204 | # potion2: time2 | ||
196 | # }, | 205 | # }, | ||
197 | # target2: { | 206 | # target2: { | ||
198 | # potion3: time3 | 207 | # potion3: time3 | ||
199 | # } | 208 | # } | ||
200 | # } | 209 | # } | ||
201 | _times = {} | 210 | _times = {} | ||
202 | 211 | ||||
203 | def apply(self, target, potion): | 212 | def apply(self, target, potion): | ||
n | 204 | if potion.depleted: | n | 213 | validate(potion) |
205 | raise TypeError("Potion is depleted.") | ||||
206 | 214 | ||||
207 | if target not in self._target_states: | 215 | if target not in self._target_states: | ||
208 | self.save_initial_state(target) | 216 | self.save_initial_state(target) | ||
209 | if target not in self._times.keys(): | 217 | if target not in self._times.keys(): | ||
210 | self._times[target] = {} | 218 | self._times[target] = {} | ||
211 | if target not in self._target_potions.keys(): | 219 | if target not in self._target_potions.keys(): | ||
212 | self._target_potions[target] = [] | 220 | self._target_potions[target] = [] | ||
213 | 221 | ||||
214 | self._times[target][potion] = potion.duration | 222 | self._times[target][potion] = potion.duration | ||
215 | self._target_potions[target].append(potion) | 223 | self._target_potions[target].append(potion) | ||
216 | sorted_effects = list(sorted(potion.effects)) | 224 | sorted_effects = list(sorted(potion.effects)) | ||
217 | for effect in sorted_effects: | 225 | for effect in sorted_effects: | ||
218 | try: | 226 | try: | ||
219 | getattr(potion, effect)(target) | 227 | getattr(potion, effect)(target) | ||
220 | except TypeError: | 228 | except TypeError: | ||
n | 221 | pass | n | 229 | continue |
222 | 230 | ||||
223 | def _apply_all(self, target): | 231 | def _apply_all(self, target): | ||
224 | for potion in self._target_potions[target]: | 232 | for potion in self._target_potions[target]: | ||
225 | for effect in list(sorted(potion.effects)): | 233 | for effect in list(sorted(potion.effects)): | ||
n | 226 | potion.effects[effect](target) | n | 234 | potion.effects[effect](target) |
227 | 235 | ||||
228 | def tick(self): | 236 | def tick(self): | ||
229 | for target, potions in self._times.items(): | 237 | for target, potions in self._times.items(): | ||
230 | for potion, time in potions.items(): | 238 | for potion, time in potions.items(): | ||
231 | self._times[target][potion] = time - 1 | 239 | self._times[target][potion] = time - 1 | ||
232 | 240 | ||||
233 | for target, potions in (self._times.copy()).items(): | 241 | for target, potions in (self._times.copy()).items(): | ||
234 | for potion, time in (potions.copy()).items(): | 242 | for potion, time in (potions.copy()).items(): | ||
235 | if time == 0: | 243 | if time == 0: | ||
236 | self._times[target].pop(potion, None) | 244 | self._times[target].pop(potion, None) | ||
237 | self._target_potions[target].remove(potion) | 245 | self._target_potions[target].remove(potion) | ||
238 | if len(self._times[target]) == 0: | 246 | if len(self._times[target]) == 0: | ||
239 | self._times.pop(target, None) | 247 | self._times.pop(target, None) | ||
240 | self.reset_target(target) | 248 | self.reset_target(target) | ||
241 | if target not in self._times: | 249 | if target not in self._times: | ||
242 | self._target_potions.pop(target, None) | 250 | self._target_potions.pop(target, None) | ||
n | 243 | n | 251 | self._target_states.pop(target, None) | |
244 | 252 | ||||
245 | def save_initial_state(self, target): | 253 | def save_initial_state(self, target): | ||
246 | self._target_states[target] = {attr: getattr(target, attr) for attr in dir(target) if not attr.startswith('_')} | 254 | self._target_states[target] = {attr: getattr(target, attr) for attr in dir(target) if not attr.startswith('_')} | ||
247 | 255 | ||||
248 | def reset_target(self, target): | 256 | def reset_target(self, target): | ||
249 | for attr, initial_value in self._target_states[target].items(): | 257 | for attr, initial_value in self._target_states[target].items(): | ||
250 | setattr(target, attr, initial_value) | 258 | setattr(target, attr, initial_value) | ||
n | n | 259 | for attr in dir(target): | ||
260 | if not attr.startswith('_') and attr not in self._target_states[target].keys(): | ||||
261 | try: | ||||
262 | delattr(target, attr) | ||||
263 | except AttributeError: | ||||
264 | continue | ||||
265 | |||||
251 | self._apply_all(target) | 266 | self._apply_all(target) | ||
252 | 267 | ||||
t | t | 268 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|