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