1import math
2from copy import deepcopy
3from functools import reduce
4
5TRANSCENDED = 'Potion is now part of something bigger than itself.'
6DEPLETED_P = 'Potion is depleted.'
7DEPLETED_E = 'Effect is depleted.'
8
9class Potion:
10 def __init__(self, effects, duration):
11 self.effects = { func_name: [ func, 1 ] if not isinstance(func, list) else func for func_name, func in effects.items() }
12 self.duration = duration
13 self.is_used_for_other_potions = False
14 self.applied_effects = set()
15
16 def _is_depleated(self):
17 return len(self.applied_effects) == len(self.effects)
18
19 def __add__(self, other):
20 if self._is_depleated() or other._is_depleated():
21 raise TypeError(DEPLETED_P)
22 combined_effects = deepcopy(self.effects)
23 for effect_name, effect in other.effects.items():
24 if effect_name not in combined_effects:
25 combined_effects[effect_name] = effect
26 else:
27 combined_effects[effect_name][1] += 1
28 combined_duration = max(self.duration, other.duration)
29 self.is_used_for_other_potions = other.is_used_for_other_potions = True
30 return Potion(combined_effects, combined_duration)
31
32 def __mul__(self, other):
33 if self._is_depleated():
34 raise TypeError(DEPLETED_P)
35 dict_copy = deepcopy(self.effects)
36 for _, effect in dict_copy.items():
37 effect[1] *= other
38 number = effect[1] % 1
39 if number <= 0.5:
40 effect[1] = math.floor(effect[1])
41 else:
42 effect[1] = math.ceil(effect[1])
43 self.is_used_for_other_potions = True
44 return Potion(dict_copy, self.duration)
45
46 def __sub__(self, other):
47 if self._is_depleated() or other._is_depleated():
48 raise TypeError(DEPLETED_P)
49 for key in other.effects.keys():
50 if key not in self.effects:
51 raise TypeError(TRANSCENDED)
52 dict_copy = deepcopy(self.effects)
53 for func_name, func_and_times in other.effects.items():
54 if func_name in dict_copy:
55 if dict_copy[func_name][1] <= func_and_times[1]:
56 dict_copy.pop(func_name)
57 else:
58 dict_copy[func_name][1] -= func_and_times[1]
59 self.is_used_for_other_potions = other.is_used_for_other_potions = True
60 return Potion(dict_copy, self.duration)
61
62 def __truediv__(self, other):
63 if self._is_depleated():
64 raise TypeError(DEPLETED_P)
65 dict_copy = deepcopy(self.effects)
66 to_pop = []
67 for key, effect in dict_copy.items():
68 effect[1] /= other
69 number = effect[1] % 1
70 if number <= 0.5:
71 effect[1] = math.floor(effect[1])
72 else:
73 effect[1] = math.ceil(effect[1])
74 if not effect[1]:
75 to_pop.append(key)
76 for key in to_pop:
77 dict_copy.pop(key)
78 self.is_used_for_other_potions = True
79 return [ Potion(deepcopy(dict_copy), self.duration) for _ in range(other) ]
80
81 def __eq__(self, other):
82 if self.is_used_for_other_potions or other.is_used_for_other_potions:
83 raise TypeError(TRANSCENDED)
84 if self._is_depleated():
85 raise TypeError(DEPLETED_P)
86 return self.effects == other.effects
87
88 def __lt__(self, other):
89 if self.is_used_for_other_potions or other.is_used_for_other_potions:
90 raise TypeError(TRANSCENDED)
91 if self._is_depleated():
92 raise TypeError(DEPLETED_P)
93 self_intensity = reduce(lambda acc, l: acc + l[1], self.effects.values(), 0)
94 other_intensity = reduce(lambda acc, l: acc + l[1], other.effects.values(), 0)
95 return self_intensity < other_intensity
96
97 def __gt__(self, other):
98 if self.is_used_for_other_potions or other.is_used_for_other_potions:
99 raise TypeError(TRANSCENDED)
100 if self._is_depleated():
101 raise TypeError(DEPLETED_P)
102 self_intensity = reduce(lambda acc, l: acc + l[1], self.effects.values(), 0)
103 other_intensity = reduce(lambda acc, l: acc + l[1], other.effects.values(), 0)
104 return self_intensity > other_intensity
105
106 def _apply_effect(self, effect, target):
107 for _ in range(self.effects[effect][1]):
108 self.effects[effect][0](target)
109 self.effects[effect][1] = 0
110
111 def __getattr__(self, effect):
112 if effect not in self.effects:
113 raise AttributeError("The potion has no such effect")
114 elif effect in self.applied_effects:
115 raise TypeError(DEPLETED_E)
116 elif self.is_used_for_other_potions:
117 raise TypeError(TRANSCENDED)
118 elif len(self.effects) == len(self.applied_effects):
119 raise TypeError(DEPLETED_P)
120
121 self.applied_effects.add(effect)
122 return lambda target: self._apply_effect(effect, target)
123
124
125class ГоспожатаПоХимия:
126 def __init__(self):
127 self.applied_potions = set()
128 self.target = None
129
130 def apply(self, target, potion):
131 self.target = target
132 if id(potion) in self.applied_potions:
133 raise TypeError(DEPLETED_P)
134 effect_names = sorted(potion.effects.keys(), key=lambda name: sum(ord(char) for char in name), reverse=True)
135 for effect in effect_names:
136 if potion.duration > 0:
137 getattr(potion, effect)(target)
138 self.applied_potions.add(id(potion))
139
140 def tick(self):
141 for _, _, i in self.applied_potions:
142 if i > 0:
143 i -= 1
...............EEEEE
======================================================================
ERROR: test_applying_part_of_potion (test.TestГоспожатаПоХимия)
Test applying only a part of a potion.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 365, in test_applying_part_of_potion
self._dimitrichka.apply(self._target, potion)
File "/tmp/solution.py", line 137, in apply
getattr(potion, effect)(target)
File "/tmp/solution.py", line 115, in __getattr__
raise TypeError(DEPLETED_E)
TypeError: Effect is depleted.
======================================================================
ERROR: test_ticking_immutable (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with immutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 426, in test_ticking_immutable
self._dimitrichka.tick()
File "/tmp/solution.py", line 141, in tick
for _, _, i in self.applied_potions:
TypeError: cannot unpack non-iterable int object
======================================================================
ERROR: test_ticking_multiple_potions (test.TestГоспожатаПоХимия)
Test ticking after applying multiple potions which affect the same attribute.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 457, in test_ticking_multiple_potions
self._dimitrichka.tick()
File "/tmp/solution.py", line 141, in tick
for _, _, i in self.applied_potions:
TypeError: cannot unpack non-iterable int object
======================================================================
ERROR: test_ticking_multiple_targets (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with mutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 489, in test_ticking_multiple_targets
self._dimitrichka.tick()
File "/tmp/solution.py", line 141, in tick
for _, _, i in self.applied_potions:
TypeError: cannot unpack non-iterable int object
======================================================================
ERROR: test_ticking_mutable (test.TestГоспожатаПоХимия)
Test ticking after applying a potion with mutable attributes.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 443, in test_ticking_mutable
self._dimitrichka.tick()
File "/tmp/solution.py", line 141, in tick
for _, _, i in self.applied_potions:
TypeError: cannot unpack non-iterable int object
----------------------------------------------------------------------
Ran 20 tests in 0.002s
FAILED (errors=5)
f | 1 | import math | f | 1 | import math |
2 | from copy import deepcopy | 2 | from copy import deepcopy | ||
3 | from functools import reduce | 3 | from functools import reduce | ||
4 | 4 | ||||
5 | TRANSCENDED = 'Potion is now part of something bigger than itself.' | 5 | TRANSCENDED = 'Potion is now part of something bigger than itself.' | ||
6 | DEPLETED_P = 'Potion is depleted.' | 6 | DEPLETED_P = 'Potion is depleted.' | ||
7 | DEPLETED_E = 'Effect is depleted.' | 7 | DEPLETED_E = 'Effect is depleted.' | ||
8 | 8 | ||||
9 | class Potion: | 9 | class Potion: | ||
10 | def __init__(self, effects, duration): | 10 | def __init__(self, effects, duration): | ||
11 | self.effects = { func_name: [ func, 1 ] if not isinstance(func, list) else func for func_name, func in effects.items() } | 11 | self.effects = { func_name: [ func, 1 ] if not isinstance(func, list) else func for func_name, func in effects.items() } | ||
12 | self.duration = duration | 12 | self.duration = duration | ||
13 | self.is_used_for_other_potions = False | 13 | self.is_used_for_other_potions = False | ||
14 | self.applied_effects = set() | 14 | self.applied_effects = set() | ||
15 | 15 | ||||
16 | def _is_depleated(self): | 16 | def _is_depleated(self): | ||
17 | return len(self.applied_effects) == len(self.effects) | 17 | return len(self.applied_effects) == len(self.effects) | ||
18 | 18 | ||||
19 | def __add__(self, other): | 19 | def __add__(self, other): | ||
20 | if self._is_depleated() or other._is_depleated(): | 20 | if self._is_depleated() or other._is_depleated(): | ||
21 | raise TypeError(DEPLETED_P) | 21 | raise TypeError(DEPLETED_P) | ||
22 | combined_effects = deepcopy(self.effects) | 22 | combined_effects = deepcopy(self.effects) | ||
23 | for effect_name, effect in other.effects.items(): | 23 | for effect_name, effect in other.effects.items(): | ||
24 | if effect_name not in combined_effects: | 24 | if effect_name not in combined_effects: | ||
25 | combined_effects[effect_name] = effect | 25 | combined_effects[effect_name] = effect | ||
26 | else: | 26 | else: | ||
27 | combined_effects[effect_name][1] += 1 | 27 | combined_effects[effect_name][1] += 1 | ||
28 | combined_duration = max(self.duration, other.duration) | 28 | combined_duration = max(self.duration, other.duration) | ||
29 | self.is_used_for_other_potions = other.is_used_for_other_potions = True | 29 | self.is_used_for_other_potions = other.is_used_for_other_potions = True | ||
30 | return Potion(combined_effects, combined_duration) | 30 | return Potion(combined_effects, combined_duration) | ||
31 | 31 | ||||
32 | def __mul__(self, other): | 32 | def __mul__(self, other): | ||
33 | if self._is_depleated(): | 33 | if self._is_depleated(): | ||
34 | raise TypeError(DEPLETED_P) | 34 | raise TypeError(DEPLETED_P) | ||
35 | dict_copy = deepcopy(self.effects) | 35 | dict_copy = deepcopy(self.effects) | ||
36 | for _, effect in dict_copy.items(): | 36 | for _, effect in dict_copy.items(): | ||
37 | effect[1] *= other | 37 | effect[1] *= other | ||
38 | number = effect[1] % 1 | 38 | number = effect[1] % 1 | ||
39 | if number <= 0.5: | 39 | if number <= 0.5: | ||
40 | effect[1] = math.floor(effect[1]) | 40 | effect[1] = math.floor(effect[1]) | ||
41 | else: | 41 | else: | ||
42 | effect[1] = math.ceil(effect[1]) | 42 | effect[1] = math.ceil(effect[1]) | ||
43 | self.is_used_for_other_potions = True | 43 | self.is_used_for_other_potions = True | ||
44 | return Potion(dict_copy, self.duration) | 44 | return Potion(dict_copy, self.duration) | ||
45 | 45 | ||||
46 | def __sub__(self, other): | 46 | def __sub__(self, other): | ||
47 | if self._is_depleated() or other._is_depleated(): | 47 | if self._is_depleated() or other._is_depleated(): | ||
48 | raise TypeError(DEPLETED_P) | 48 | raise TypeError(DEPLETED_P) | ||
49 | for key in other.effects.keys(): | 49 | for key in other.effects.keys(): | ||
50 | if key not in self.effects: | 50 | if key not in self.effects: | ||
51 | raise TypeError(TRANSCENDED) | 51 | raise TypeError(TRANSCENDED) | ||
52 | dict_copy = deepcopy(self.effects) | 52 | dict_copy = deepcopy(self.effects) | ||
53 | for func_name, func_and_times in other.effects.items(): | 53 | for func_name, func_and_times in other.effects.items(): | ||
54 | if func_name in dict_copy: | 54 | if func_name in dict_copy: | ||
55 | if dict_copy[func_name][1] <= func_and_times[1]: | 55 | if dict_copy[func_name][1] <= func_and_times[1]: | ||
56 | dict_copy.pop(func_name) | 56 | dict_copy.pop(func_name) | ||
57 | else: | 57 | else: | ||
58 | dict_copy[func_name][1] -= func_and_times[1] | 58 | dict_copy[func_name][1] -= func_and_times[1] | ||
59 | self.is_used_for_other_potions = other.is_used_for_other_potions = True | 59 | self.is_used_for_other_potions = other.is_used_for_other_potions = True | ||
60 | return Potion(dict_copy, self.duration) | 60 | return Potion(dict_copy, self.duration) | ||
61 | 61 | ||||
62 | def __truediv__(self, other): | 62 | def __truediv__(self, other): | ||
63 | if self._is_depleated(): | 63 | if self._is_depleated(): | ||
64 | raise TypeError(DEPLETED_P) | 64 | raise TypeError(DEPLETED_P) | ||
65 | dict_copy = deepcopy(self.effects) | 65 | dict_copy = deepcopy(self.effects) | ||
66 | to_pop = [] | 66 | to_pop = [] | ||
67 | for key, effect in dict_copy.items(): | 67 | for key, effect in dict_copy.items(): | ||
68 | effect[1] /= other | 68 | effect[1] /= other | ||
69 | number = effect[1] % 1 | 69 | number = effect[1] % 1 | ||
70 | if number <= 0.5: | 70 | if number <= 0.5: | ||
71 | effect[1] = math.floor(effect[1]) | 71 | effect[1] = math.floor(effect[1]) | ||
72 | else: | 72 | else: | ||
73 | effect[1] = math.ceil(effect[1]) | 73 | effect[1] = math.ceil(effect[1]) | ||
74 | if not effect[1]: | 74 | if not effect[1]: | ||
75 | to_pop.append(key) | 75 | to_pop.append(key) | ||
76 | for key in to_pop: | 76 | for key in to_pop: | ||
77 | dict_copy.pop(key) | 77 | dict_copy.pop(key) | ||
78 | self.is_used_for_other_potions = True | 78 | self.is_used_for_other_potions = True | ||
79 | return [ Potion(deepcopy(dict_copy), self.duration) for _ in range(other) ] | 79 | return [ Potion(deepcopy(dict_copy), self.duration) for _ in range(other) ] | ||
80 | 80 | ||||
81 | def __eq__(self, other): | 81 | def __eq__(self, other): | ||
82 | if self.is_used_for_other_potions or other.is_used_for_other_potions: | 82 | if self.is_used_for_other_potions or other.is_used_for_other_potions: | ||
83 | raise TypeError(TRANSCENDED) | 83 | raise TypeError(TRANSCENDED) | ||
84 | if self._is_depleated(): | 84 | if self._is_depleated(): | ||
85 | raise TypeError(DEPLETED_P) | 85 | raise TypeError(DEPLETED_P) | ||
86 | return self.effects == other.effects | 86 | return self.effects == other.effects | ||
87 | 87 | ||||
88 | def __lt__(self, other): | 88 | def __lt__(self, other): | ||
89 | if self.is_used_for_other_potions or other.is_used_for_other_potions: | 89 | if self.is_used_for_other_potions or other.is_used_for_other_potions: | ||
90 | raise TypeError(TRANSCENDED) | 90 | raise TypeError(TRANSCENDED) | ||
91 | if self._is_depleated(): | 91 | if self._is_depleated(): | ||
92 | raise TypeError(DEPLETED_P) | 92 | raise TypeError(DEPLETED_P) | ||
93 | self_intensity = reduce(lambda acc, l: acc + l[1], self.effects.values(), 0) | 93 | self_intensity = reduce(lambda acc, l: acc + l[1], self.effects.values(), 0) | ||
94 | other_intensity = reduce(lambda acc, l: acc + l[1], other.effects.values(), 0) | 94 | other_intensity = reduce(lambda acc, l: acc + l[1], other.effects.values(), 0) | ||
95 | return self_intensity < other_intensity | 95 | return self_intensity < other_intensity | ||
96 | 96 | ||||
97 | def __gt__(self, other): | 97 | def __gt__(self, other): | ||
98 | if self.is_used_for_other_potions or other.is_used_for_other_potions: | 98 | if self.is_used_for_other_potions or other.is_used_for_other_potions: | ||
99 | raise TypeError(TRANSCENDED) | 99 | raise TypeError(TRANSCENDED) | ||
100 | if self._is_depleated(): | 100 | if self._is_depleated(): | ||
101 | raise TypeError(DEPLETED_P) | 101 | raise TypeError(DEPLETED_P) | ||
102 | self_intensity = reduce(lambda acc, l: acc + l[1], self.effects.values(), 0) | 102 | self_intensity = reduce(lambda acc, l: acc + l[1], self.effects.values(), 0) | ||
103 | other_intensity = reduce(lambda acc, l: acc + l[1], other.effects.values(), 0) | 103 | other_intensity = reduce(lambda acc, l: acc + l[1], other.effects.values(), 0) | ||
104 | return self_intensity > other_intensity | 104 | return self_intensity > other_intensity | ||
105 | 105 | ||||
106 | def _apply_effect(self, effect, target): | 106 | def _apply_effect(self, effect, target): | ||
107 | for _ in range(self.effects[effect][1]): | 107 | for _ in range(self.effects[effect][1]): | ||
108 | self.effects[effect][0](target) | 108 | self.effects[effect][0](target) | ||
109 | self.effects[effect][1] = 0 | 109 | self.effects[effect][1] = 0 | ||
110 | 110 | ||||
111 | def __getattr__(self, effect): | 111 | def __getattr__(self, effect): | ||
112 | if effect not in self.effects: | 112 | if effect not in self.effects: | ||
113 | raise AttributeError("The potion has no such effect") | 113 | raise AttributeError("The potion has no such effect") | ||
114 | elif effect in self.applied_effects: | 114 | elif effect in self.applied_effects: | ||
115 | raise TypeError(DEPLETED_E) | 115 | raise TypeError(DEPLETED_E) | ||
116 | elif self.is_used_for_other_potions: | 116 | elif self.is_used_for_other_potions: | ||
117 | raise TypeError(TRANSCENDED) | 117 | raise TypeError(TRANSCENDED) | ||
118 | elif len(self.effects) == len(self.applied_effects): | 118 | elif len(self.effects) == len(self.applied_effects): | ||
119 | raise TypeError(DEPLETED_P) | 119 | raise TypeError(DEPLETED_P) | ||
120 | 120 | ||||
121 | self.applied_effects.add(effect) | 121 | self.applied_effects.add(effect) | ||
122 | return lambda target: self._apply_effect(effect, target) | 122 | return lambda target: self._apply_effect(effect, target) | ||
123 | 123 | ||||
124 | 124 | ||||
t | 125 | class GospojataPoHimiq: | t | 125 | class ГоспожатаПоХимия: |
126 | def __init__(self): | 126 | def __init__(self): | ||
127 | self.applied_potions = set() | 127 | self.applied_potions = set() | ||
128 | self.target = None | 128 | self.target = None | ||
129 | 129 | ||||
130 | def apply(self, target, potion): | 130 | def apply(self, target, potion): | ||
131 | self.target = target | 131 | self.target = target | ||
132 | if id(potion) in self.applied_potions: | 132 | if id(potion) in self.applied_potions: | ||
133 | raise TypeError(DEPLETED_P) | 133 | raise TypeError(DEPLETED_P) | ||
134 | effect_names = sorted(potion.effects.keys(), key=lambda name: sum(ord(char) for char in name), reverse=True) | 134 | effect_names = sorted(potion.effects.keys(), key=lambda name: sum(ord(char) for char in name), reverse=True) | ||
135 | for effect in effect_names: | 135 | for effect in effect_names: | ||
136 | if potion.duration > 0: | 136 | if potion.duration > 0: | ||
137 | getattr(potion, effect)(target) | 137 | getattr(potion, effect)(target) | ||
138 | self.applied_potions.add(id(potion)) | 138 | self.applied_potions.add(id(potion)) | ||
139 | 139 | ||||
140 | def tick(self): | 140 | def tick(self): | ||
141 | for _, _, i in self.applied_potions: | 141 | for _, _, i in self.applied_potions: | ||
142 | if i > 0: | 142 | if i > 0: | ||
143 | i -= 1 | 143 | i -= 1 |
Legends | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|
|
|
05.12.2023 18:25
05.12.2023 18:26