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


Работилница за отвари!
Краен срок: 05.12.2023 18:00
Точки: 10

## Увод Разпознавайки многото липси на нашата ASCII-POOP игра, решихме вместо да се погрижим за тях - да ви оставим вие да го направите. :smile: Това, което искаме да имплементирате, са колби, отвари, поушъни или с други думи - магически коктейли с определени ефекти. ## Potion Напишете клас `Potion`, който се инициализира с два аргумента - списък от ефекти и продължителност на действие - `Potion(effects, duration)`. ### Продължителност Вторият аргумент при инициализиране е `duration` - продължителност на ефектите на отварата. Защо говорим за втория аргумент първо - защото е по-кратко, а примери има по-надолу. **Как се използва** - по-нататък. ### Ефекти Първият аргумент при инициализиране на `Potion` - речник с ефекти, трябва да ги описва по следния начин: ``` effects = {'grow': lambda target: setattr(target, 'size', target.size*2)} # Име на ефекта: Функция, която прилага ефекта grow_potion = Potion(effects, duration=2) def elemental_dmg_immunity(target): target.fire_dmg_resistance = 1.0 # Percentage value between 0 and 1 target.water_dmg_resistance = 1.0 target.earth_dmg_resistance = 1.0 target.air_dmg_resistance = 1.0 def physical_dmg_immunity(target): target.bludgeoning_dmg_resistance = 1.0 target.slashing_dmg_resistance = 1.0 target.piercing_dmg_resistance = 1.0 immunity_potion = Potion({'make_elemental_immune': elemental_dmg_immunity, 'make_physical_immune': physical_dmg_immunity}, duration=1) ``` - Всеки ефект е callable. - Всеки ефект се очаква да приема като единствен аргумент обект, върху който ще въздейства (в примера по-горе се казва `target`, но очевидно това е въпрос на дефиниция). Не мислим за неговата имплементация. - Всеки ефект трябва да може да се извика като метод на конкретната отвара - `immunity_potion.make_elemental_immune(target)`. - Ефект може да се прилага само веднъж. Последващо извикване на метода трябва да вдига `TypeError` с текст `"Effect is depleted."`. - **Няма** да даваме грешна комбинация от target и ефекти (с други думи не се притеснявайте, че target.size е несъществуващ атрибут). - Прилагането на отделен ефект върху даден обект има безкрайна продължителност. Единственият случай, в който продължителността е от значение, е когато се приложи цялата отвара и "някой" следи изминалото време *(за продължителността - по-надолу)*. ### Операции с отвари #### Комбиниране Всяка отвара трябва да може да се комбинира с друга отвара, като резултатната отвара трябва да притежава всички ефекти, които отделните отвари имат: ``` # grow_potion и immunity_potion са отварите от горния пример grow_and_immunity_potion = grow_potion + immunity_potion grow_and_immunity_potion.grow(target) grow_and_immunity_potion.make_elemental_immune(target) grow_and_immunity_potion.make_physical_immune(target) ``` - Ефекти с едно и също име се агрегират под формата на "интензитет" - функциите се извикват толкова пъти, колкото е интензитетът им. Как ще пазите информация за това оставяме на вас да решите. - Интензитетът на ефектите няма отношение към продължителността (`duration`) на действие на отварата. - Приемайте, че ефекти с едно и също име ще изпълняват една и съща функция. - Ако комбинирате отвари с различен `duration`, получената отвара трябва да взима по-големият `duration` от двете, които я съставят. Пример за интензитет: ``` effect = {'grow': lambda target: setattr(target, 'size', target.size*2)} # target_1.size = 5 grow_potion = Potion(effect, duration=2) grow_potion.grow(target_1) # target_1.size = 10 # target_2.size = 5 triple_grow_potion = Potion(effect, duration=2) + Potion(effect, duration=2) + Potion(effect, duration=2) triple_grow_potion.grow(target_2) # target_2.size = 40 ``` #### Потенцииране Интензитетът на ефектите на всяка отвара трябва да може да бъде увеличен, чрез процес на потенцииране, изразяващ се в умножение: ``` # target.size = 5 triple_grow_potion = grow_potion * 3 triple_grow_potion.grow(target) # target.size = 40 ``` - Потенциирането се извършва само с естествени числа. #### Разреждане Всяка отвара трябва да може да бъде разреждана с определено количество вода, йегермайстер или сълзи. И в трите случая резултатът е същият - интензитетът на ефектите на отварата намалява спрямо коефициентът на разреждане: ``` # target.size = 5 diluted_grow_potion = triple_grow_potion * 0.33 diluted_grow_potion.grow(target) # target.size = 10 ``` - Разреждането става само с числа в интервала `(0, 1)`. - Интензитет <= x.5 се закръглява надолу. - Интензитет > x.5 се закръглява нагоре. - Интензитет 0 е валиден и означава, че ефектът ще влезе в сила 0 пъти, но не го изтрива. #### Пречистване Всяка отваря трябва да може да бъде пречистена, чрез операция изваждане, като резултатната отвара трябва да бъде с премахнати методите, които се срещат във втората отвара: ``` # grow_and_immunity_potion е отварата от горния пример purified_immunity_potion = grow_and_immunity_potion - grow_potion purified_immunity_potion.make_elemental_immune(target) purified_immunity_potion.make_physical_immune(target) # purified_immunity_potion.grow(target) - няма такъв ефект, AttributeError ``` - Ако отварата от дясната страна на минуса има ефекти, които тази от лявата страна на минуса няма, трябва да се хвърли `TypeError` (съдържанието няма значение). - Ако някой от ефектите на отварата отляво има интензитет, той намалява с толкова, колкото е интензитетът на същия ефект на отварата от дясната страна. - Ако отварата отдясно има по-голям или равен интензитет, не получаваме негативен или нулев такъв а считаме ефектът за изцяло премахнат. Не с интензитет 0, **изцяло** премахнат. - При тази операция `duration`-ът на новата отвара съвпада с този, който е бил на отварата от лявата страна на минуса. #### Разделяне Всяка колба трябва да може да бъде разделена на няколко части, като резултат от разделянето са толкова на брой отвари, колкото е делителят, като интензитетът на всяка от тях също е пропорционален на този брой. ``` # target.size = 5 diluted_grow_potion, _, _ = triple_grow_potion / 3 diluted_grow_potion.grow(target) # target.size = 10 ``` - Правилата за закръгляване на интензитет са същите като при разреждането. #### Преходност При извършване на която и да е от операциите по-горе, отварите, участвали в действието спират да съществуват във формата, в която са били преди това. Следователно е редно те да станат неизползваеми, което ще дефинираме по следния начин: - При опит за прилагане на ефект на дадена отвара, която вече е била използвана, като част от реакция, трябва да бъде вдигнат `TypeError` с текст `"Potion is now part of something bigger than itself."`. *\#deep* #### Оценяване Трябва да можем да оценяваме/сравняваме отвари: ``` # grow_potion и immunity_potion са отварите от първия пример double_grow_potion = Potion(effects, duration=2) * 2 print(grow_potion == grow_potion) # True print(grow_potion < grow_potion) # False print(double_grow_potion > grow_potion) # True print(immunity_potion > grow_potion) # True print(double_grow_potion < immunity_potion) # False print(double_grow_potion > immunity_potion) # False ``` Правилата за сравниение са следните: - Две отвари са еднакви (==), ако съдържат едни и същи ефекти с един и същ интензитет. - Една отвара е превъзходна (>) на друга, ако сумата от интензитетите на всички ѝ ефекти е по-голяма от тази на втората колба. - Не искаме да дефинираме възможност за *"превъзходна или еднаква"*, с други думи нямаме валидно поведение за операторите <= и >=. ## ГоспожатаПоХимия `ГоспожатаПоХимия` е клас, който контролира прилагането на определени отвари върху различни обекти. Може би се казва Димитричка, но всички ѝ казват "Химичка Димитричка". Може би има прякор "Бялата смърт", а може би името ѝ не е от значение, но е важно, че в профил изглежда като банан във вакуум. ### Прилагане на отвари Госпожата по химия трябва да може да приложи дадена отвара върху обект чрез метода `apply(target, potion)`. Тази операция прилага **всички** налични ефекти с техния съответен интензитет върху обекта: ``` dimitrichka = ГоспожатаПоХимия() # grow_potion и immunity_potion са отварите от горния пример grow_and_immunity_potion = grow_potion + immunity_potion # target.size = 5 dimitrichka.apply(target, grow_and_immunity_potion) # target.size = 10 # target.*_resistance = 1.0 (* за да не ги изброяваме) ``` - **Няма** да даваме грешна комбинация от target и ефекти (с други думи не се притеснявайте, че `target.size` е несъществуващ атрибут). - Отвара може да се прилага само веднъж. Последващо извикване на който и да е ефект или на `apply` метода трябва да вдига `TypeError` с текст `"Potion is depleted."`. - При опит за извършване на операциите, описани по-горе, включващ изчерпана отвара, трябва да се възбужда същия `TypeError` като в горната точка. - Ако са приложени само частично някои от ефектите на отварата (с извикване на конкретния метод), `apply` се предполага да приложи всички останали ефекти! За изчерпана се смята само отвара, всеки от чиито ефекти е приложен. - Свойството **Преходност** важи и за пълното прилагане на отвари - не можем да прилагаме отвари, които са участвали в някаква реакция. - Пълното прилагане на отвара прилага ефектите по молекулна маса, от най-голяма към най-малка. Wait what? Добре де, ще дефинираме "молекулна маса" като сума от ASCII / Unicode кодовете на всеки от символите на името на ефекта. Молекулната маса на `'grow'`, например е `447`, което е повече от молекулната маса на `'fast'`, която е `430`. Следователно `grow`, ще се изпълни преди `fast`, ако отварата притежава и двата ефекта. - Ако молекулната маса на два ефекта е еднаква - няма значение в какъв ред ще се изпълнят. - Знаем, че отварите не работят точно така. Искахме да се научим как в действителност се случват подобни реакции, но уви, откритата лекция по химия съвпадаше с лекцията по Пайтън. Заради вас го правим! ### Часовникуване Искаме `ГоспожатаПоХимия` да следи колко е часът *(kind of)* чрез метод `tick()`, който да отчита изминаването на един "игрови ход" *(на този етап бяхме забравили откъде тръгна идеята)*. Единственият ефект на изминал ход е да помага за проследяването на продължителността на ефектите на отварата. След определен брой `tick()`-ове ефектите на изпитата колба се предполага да отминат. ``` narco = ГоспожатаПоХимия() effects = {'grow': lambda target: setattr(target, 'size', target.size*2)} grow_potion = Potion(effects, duration=2) # target.size = 5 narco.apply(target, grow_potion) print(target.size) # 10 narco.tick() print(target.size) # 10 narco.tick() print(target.size) # 5 ``` - За първи ход се брои ходът на изпиването. Т.е. продължителност на отварата със стойност 2 приключва след 2 извиквания на `tick` метода. - Ефектите, с които ще тестваме, няма да пипат частни или защитени атрибути на обектите, така че за да възстановите обектите след изтичане на дадена отвара е достатъчно да следите публичните атрибути на обектите.
 1import unittest
 2
 3from solution import *
 4
 5
 6class SanityTest(unittest.TestCase):
 7    """Sanity tests for Alchemy Workshop."""
 8
 9    def test_potion(self):
10        """Test Potion class."""
11        self.assertIsInstance(Potion({'do_nothing': lambda target: None}, duration=2), Potion)
12
13    def test_госпожата_по_химия(self):
14        """Test ГоспожатаПоХимия class."""
15        self.assertIsInstance(ГоспожатаПоХимия(), ГоспожатаПоХимия)
16
17
18if __name__ == '__main__':
19    unittest.main()
  1import copy
  2import unittest
  3
  4from solution import *
  5
  6
  7class Target:
  8    """Test target."""
  9
 10    def __init__(self, **kwargs):
 11        """Initializator."""
 12        self._cache = kwargs
 13        for key, val in kwargs.items():
 14            setattr(self, key, val)
 15    
 16    def _refresh(self):
 17        """Refresh all values to their initial state."""
 18        for key, val in self._cache.items():
 19            setattr(self, key, val)
 20
 21
 22def int_attr_fun(target):
 23    """Test function for altering int attribute."""
 24    target.int_attr *= 10
 25
 26
 27def float_attr_fun(target):
 28    """Test function for altering float attribute."""
 29    target.float_attr += 1
 30
 31
 32def list_attr_fun(target):
 33    """Test function for altering list attribute."""
 34    target.list_attr.append(4)
 35
 36
 37def dict_attr_fun(target):
 38    """Test function for altering dict attribute."""
 39    target.dict_attr = {val:key for key, val in target.dict_attr.items()}
 40
 41
 42class TestBasicPotion(unittest.TestCase):
 43    """Test Potion class for basic functionality."""
 44
 45    def setUp(self):
 46        """Set up a test target."""
 47        self._target = Target(int_attr=5, float_attr=3.14,
 48                              list_attr=[1, 2, 3],
 49                              dict_attr={'name': 'Борис', 'професия': 'жалбар'})
 50
 51    def test_empty(self):
 52        """Test initialization with empty effects."""
 53        potion = Potion({}, duration=0)
 54        self.assertIsInstance(potion, Potion)
 55
 56    def test_applying(self):
 57        """Test applying a potion to a target."""
 58        potion = Potion({'int_attr_fun': int_attr_fun,
 59                         'float_attr_fun': float_attr_fun,
 60                         'list_attr_fun': list_attr_fun,
 61                         'dict_attr_fun': dict_attr_fun},
 62                        duration=1)
 63        potion.int_attr_fun(self._target)
 64        self.assertEqual(self._target.int_attr, 50)
 65        potion.float_attr_fun(self._target)
 66        self.assertAlmostEqual(self._target.float_attr, 4.14)
 67        potion.list_attr_fun(self._target)
 68        self.assertEqual(self._target.list_attr, [1, 2, 3, 4])
 69        potion.dict_attr_fun(self._target)
 70        self.assertEqual(self._target.dict_attr, {'Борис': 'name', 'жалбар': 'професия'})
 71
 72    def test_depletion(self):
 73        """Test depletion of a potion effect."""
 74        potion = Potion({'int_attr_fun': int_attr_fun},
 75                        duration=2)
 76        potion.int_attr_fun(self._target)
 77        self.assertEqual(self._target.int_attr, 50)
 78        with self.assertRaisesRegex(TypeError, 'Effect is depleted\.'):
 79            potion.int_attr_fun(self._target)
 80 
 81
 82class TestPotionOperations(unittest.TestCase):
 83    """Test operations for Potion class."""
 84
 85    def setUp(self):
 86        """Set up a test target."""
 87        self._target = Target(int_attr=5, float_attr=3.14)
 88
 89    def test_combination_no_overlap(self):
 90        """Test combining potions with no overlap."""
 91        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
 92        potion2 = Potion({'float_attr_fun': float_attr_fun}, duration=2)
 93        potion = potion1 + potion2
 94        potion.int_attr_fun(self._target)
 95        potion.float_attr_fun(self._target)
 96        self.assertEqual(self._target.int_attr, 50)
 97        self.assertAlmostEqual(self._target.float_attr, 4.14)
 98
 99    def test_combination_with_overlap(self):
100        """Test combining potions with overlap."""
101        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
102        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=2)
103        potion = potion1 + potion2
104        potion.int_attr_fun(self._target)
105        self.assertEqual(self._target.int_attr, 500)
106
107    def test_potentiation(self):
108        """Test potentiation of a potion."""
109        potion = Potion({'int_attr_fun': int_attr_fun}, duration=1)
110        potion = potion * 3
111        potion.int_attr_fun(self._target)
112        self.assertEqual(self._target.int_attr, 5 * (10 ** 3))
113
114    def test_dilution(self):
115        """Test dilution of a potion."""
116        # Test at half
117        base_potion = Potion({'int_attr_fun': int_attr_fun}, duration=1)
118        half_potion = base_potion * 0.5
119        half_potion.int_attr_fun(self._target)
120        self.assertEqual(self._target.int_attr, 5)
121        # Test above half
122        self._target = copy.deepcopy(self._target)
123        self._target._refresh()
124        base_potion = Potion({'int_attr_fun': int_attr_fun}, duration=1)
125        half_potion = base_potion * 0.51
126        half_potion.int_attr_fun(self._target)
127        self.assertEqual(self._target.int_attr, 50)
128        # Test below half
129        self._target = copy.deepcopy(self._target)
130        self._target._refresh()
131        base_potion = Potion({'int_attr_fun': int_attr_fun}, duration=1)
132        half_potion = base_potion * 0.49
133        half_potion.int_attr_fun(self._target)
134        self.assertEqual(self._target.int_attr, 5)
135        # Test around zero
136        self._target = copy.deepcopy(self._target)
137        self._target._refresh()
138        base_potion = Potion({'int_attr_fun': int_attr_fun}, duration=1)
139        half_potion = base_potion * 0.0001
140        half_potion.int_attr_fun(self._target)
141        self.assertEqual(self._target.int_attr, 5)
142        # Test actual division
143        self._target = copy.deepcopy(self._target)
144        self._target._refresh()
145        base_potion = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
146        half_potion = base_potion * 0.5
147        half_potion.int_attr_fun(self._target)
148        self.assertEqual(self._target.int_attr, 50)
149        # Test rounding to odd number (built-in round() always goes to an even number)
150        self._target = copy.deepcopy(self._target)
151        self._target._refresh()
152        base_potion = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 3
153        half_potion = base_potion * 0.5
154        half_potion.int_attr_fun(self._target)
155        self.assertEqual(self._target.int_attr, 50)
156
157    def test_purification(self):
158        """Test purification of a potion."""
159        # Test normal behaviour
160        potion1 = Potion({'int_attr_fun': int_attr_fun,
161                          'float_attr_fun': float_attr_fun},
162                         duration=1)
163        potion2 = Potion({'int_attr_fun': int_attr_fun},
164                         duration=1)
165        potion = potion1 - potion2
166        potion.float_attr_fun(self._target)
167        self.assertAlmostEqual(self._target.float_attr, 4.14)
168        with self.assertRaises(AttributeError):
169            potion.int_attr_fun(self._target)
170        # Test mismatching effects
171        self._target = copy.deepcopy(self._target)
172        self._target._refresh()
173        potion1 = Potion({'int_attr_fun': int_attr_fun,
174                          'float_attr_fun': float_attr_fun},
175                         duration=1)
176        potion2 = Potion({'int_attr_fun': int_attr_fun},
177                         duration=1)
178        with self.assertRaises(TypeError):
179            potion2 - potion1
180        # Test with intensity
181        self._target = copy.deepcopy(self._target)
182        self._target._refresh()
183        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 3
184        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
185        potion_decorator =  Potion({'float_attr_fun': float_attr_fun}, duration=1)
186        potion1 = potion1 + potion_decorator
187        potion = potion1 - potion2
188        potion.float_attr_fun(self._target)
189        self.assertAlmostEqual(self._target.float_attr, 4.14)
190        potion.int_attr_fun(self._target)
191        self.assertEqual(self._target.int_attr, 50)
192        # Test with intensity resulting in zero
193        self._target = copy.deepcopy(self._target)
194        self._target._refresh()
195        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
196        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
197        potion = potion1 - potion2
198        with self.assertRaises(AttributeError):
199            potion.int_attr_fun(self._target)
200        # Test with higher intensity on the right
201        self._target = copy.deepcopy(self._target)
202        self._target._refresh()
203        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
204        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 3
205        potion = potion1 - potion2
206        with self.assertRaises(AttributeError):
207            potion.int_attr_fun(self._target)
208
209    def test_separation(self):
210        """Test separation of a potion."""
211        # Test normal case
212        potion = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 9
213        potion1, potion2, potion3 = potion / 3
214        potion1.int_attr_fun(self._target)
215        self.assertEqual(self._target.int_attr, 5 * (10 ** 3))
216        self._target = copy.deepcopy(self._target)
217        self._target._refresh()
218        potion2.int_attr_fun(self._target)
219        self.assertEqual(self._target.int_attr, 5 * (10 ** 3))
220        self._target = copy.deepcopy(self._target)
221        self._target._refresh()
222        potion3.int_attr_fun(self._target)
223        self.assertEqual(self._target.int_attr, 5 * (10 ** 3))
224        # Test resulting in one
225        self._target = copy.deepcopy(self._target)
226        self._target._refresh()
227        potion = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 3
228        potion1, potion2, potion3 = potion / 3
229        potion1.int_attr_fun(self._target)
230        self.assertEqual(self._target.int_attr, 50)
231        self._target = copy.deepcopy(self._target)
232        self._target._refresh()
233        potion2.int_attr_fun(self._target)
234        self.assertEqual(self._target.int_attr, 50)
235        self._target = copy.deepcopy(self._target)
236        self._target._refresh()
237        potion3.int_attr_fun(self._target)
238        self.assertEqual(self._target.int_attr, 50)
239        # Test rounding to odd
240        self._target = copy.deepcopy(self._target)
241        self._target._refresh()
242        potion = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 3
243        potion1, potion2 = potion / 2
244        potion1.int_attr_fun(self._target)
245        self.assertEqual(self._target.int_attr, 50)
246        self._target = copy.deepcopy(self._target)
247        self._target._refresh()
248        potion2.int_attr_fun(self._target)
249        self.assertEqual(self._target.int_attr, 50)
250
251    def test_deprecation(self):
252        """Test deprecation of a potion."""
253        # Addition
254        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
255        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
256        potion = potion1 + potion2
257        with self.assertRaisesRegex(TypeError, 'Potion is now part of something bigger than itself\.'):
258            potion1.int_attr_fun(self._target)
259        with self.assertRaisesRegex(TypeError, 'Potion is now part of something bigger than itself\.'):
260            potion2.int_attr_fun(self._target)
261        # Multiplication
262        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
263        potion = potion1 * 2
264        with self.assertRaisesRegex(TypeError, 'Potion is now part of something bigger than itself\.'):
265            potion1.int_attr_fun(self._target)
266        # Subtraction
267        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
268        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
269        potion = potion1 - potion2
270        with self.assertRaisesRegex(TypeError, 'Potion is now part of something bigger than itself\.'):
271            potion1.int_attr_fun(self._target)
272        with self.assertRaisesRegex(TypeError, 'Potion is now part of something bigger than itself\.'):
273            potion2.int_attr_fun(self._target)
274        # Division
275        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
276        potion = potion1 / 1
277        with self.assertRaisesRegex(TypeError, 'Potion is now part of something bigger than itself\.'):
278            potion1.int_attr_fun(self._target)
279
280
281class TestPotionComparison(unittest.TestCase):
282    """Test comparisons for Potion class."""
283
284    def test_equal(self):
285        """Test equality of potions."""
286        # Normal case
287        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
288        potion2 = Potion({'float_attr_fun': float_attr_fun}, duration=1)
289        potion3 = Potion({'int_attr_fun': int_attr_fun,
290                          'float_attr_fun': float_attr_fun},
291                         duration=1)
292        self.assertEqual(potion1 + potion2, potion3)
293        # With intensity
294        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
295        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
296        potion3 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
297        potion4 = potion1 + potion2
298        self.assertEqual(potion4, potion3)
299        # Not equal due to different methods
300        potion1 = Potion({'float_attr_fun': float_attr_fun}, duration=1)
301        potion2 = Potion({'int_attr_fun': int_attr_fun,
302                          'float_attr_fun': float_attr_fun},
303                         duration=1)
304        self.assertNotEqual(potion1, potion2)
305        # Not equal due to different intensity
306        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
307        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
308        potion3 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 3
309        potion4 = potion1 + potion2
310        self.assertNotEqual(potion4, potion3)
311
312    def test_superbness(self):
313        """Test superbness of potions."""
314        # Normal case
315        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
316        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
317        self.assertLess(potion1, potion2)
318        self.assertGreater(potion2, potion1)
319        self.assertNotEqual(potion1, potion2)
320        # Diffetent intensity for different methods
321        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
322        potion2 = Potion({'float_attr_fun': float_attr_fun}, duration=1) * 3
323        self.assertLess(potion1, potion2)
324        self.assertGreater(potion2, potion1)
325        self.assertNotEqual(potion1, potion2)
326        # Equal intensity for different methods
327        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
328        potion2 = Potion({'float_attr_fun': float_attr_fun}, duration=1) * 2
329        self.assertFalse(potion1 > potion2)
330        self.assertFalse(potion1 < potion2)
331
332
333class TestГоспожатаПоХимия(unittest.TestCase):
334    """Test ГоспожатаПоХимия."""
335
336    def setUp(self):
337        """Set up a test target."""
338        self._target = Target(int_attr=5, float_attr=3.14,
339                              list_attr=[1, 2, 3],
340                              dict_attr={'name': 'Борис', 'професия': 'жалбар'})
341        self._dimitrichka = ГоспожатаПоХимия()
342    
343    def test_applying_normal_case(self):
344        """Test applying a normal potion."""
345        potion = Potion({'int_attr_fun': int_attr_fun,
346                         'float_attr_fun': float_attr_fun,
347                         'list_attr_fun': list_attr_fun,
348                         'dict_attr_fun': dict_attr_fun},
349                        duration=1)
350        self._dimitrichka.apply(self._target, potion)
351        self.assertEqual(self._target.int_attr, 50)
352        self.assertAlmostEqual(self._target.float_attr, 4.14)
353        self.assertEqual(self._target.list_attr, [1, 2, 3, 4])
354        self.assertEqual(self._target.dict_attr, {'Борис': 'name', 'жалбар': 'професия'})
355    
356    def test_applying_part_of_potion(self):
357        """Test applying only a part of a potion."""
358        potion = Potion({'int_attr_fun': int_attr_fun,
359                         'float_attr_fun': float_attr_fun,
360                         'list_attr_fun': list_attr_fun,
361                         'dict_attr_fun': dict_attr_fun},
362                        duration=1)
363        temp_target = Target(int_attr=5)
364        potion.int_attr_fun(temp_target)
365        self._dimitrichka.apply(self._target, potion)
366        self.assertEqual(self._target.int_attr, 5) # This should be the original value
367        self.assertAlmostEqual(self._target.float_attr, 4.14)
368        self.assertEqual(self._target.list_attr, [1, 2, 3, 4])
369        self.assertEqual(self._target.dict_attr, {'Борис': 'name', 'жалбар': 'професия'})
370
371    def test_applying_depleted_potion(self):
372        """Test applying a depleted potion or a potion that was used in a reaction."""
373        # Apply a depleted potion
374        potion = Potion({'int_attr_fun': int_attr_fun,
375                         'float_attr_fun': float_attr_fun,
376                         'list_attr_fun': list_attr_fun,
377                         'dict_attr_fun': dict_attr_fun},
378                        duration=1)
379        self._dimitrichka.apply(self._target, potion)
380        with self.assertRaisesRegex(TypeError, 'Potion is depleted\.'):
381            self._dimitrichka.apply(self._target, potion)
382        with self.assertRaisesRegex(TypeError, 'Potion is depleted\.'):
383            potion = potion * 2
384        # Apply a potion that was used in a reaction
385        potion = Potion({'int_attr_fun': int_attr_fun,
386                         'float_attr_fun': float_attr_fun,
387                         'list_attr_fun': list_attr_fun,
388                         'dict_attr_fun': dict_attr_fun},
389                        duration=1)
390        _ = potion * 2
391        with self.assertRaisesRegex(TypeError, 'Potion is now part of something bigger than itself\.'):
392            self._dimitrichka.apply(self._target, potion)
393
394    def test_applying_order(self):
395        """Test applying order of a potion."""
396        # aa_name should have precedence
397        def z_name(target):
398            target.int_attr += 2
399        def aa_name(target):
400            target.int_attr *= 2
401        potion = Potion({'z_name': z_name,
402                         'aa_name': aa_name},
403                        duration=1)
404        self._dimitrichka.apply(self._target, potion)
405        self.assertEqual(self._target.int_attr, 12)
406        # z_name should have precedence
407        self._target = copy.deepcopy(self._target)
408        self._target._refresh()
409        def z_name(target):
410            target.int_attr += 2
411        def a_name(target):
412            target.int_attr *= 2
413        potion = Potion({'z_name': z_name,
414                         'a_name': a_name},
415                        duration=1)
416        self._dimitrichka.apply(self._target, potion)
417        self.assertEqual(self._target.int_attr, 14)
418
419    def test_ticking_immutable(self):
420        """Test ticking after applying a potion with immutable attributes."""
421        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
422        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=2)
423        potion = potion1 + potion2 # Excepted duration is 2 with intensity of 2
424        self._dimitrichka.apply(self._target, potion)
425        self.assertEqual(self._target.int_attr, 500)
426        self._dimitrichka.tick()
427        self.assertEqual(self._target.int_attr, 500)
428        self._dimitrichka.tick()
429        self.assertEqual(self._target.int_attr, 5)
430
431    def test_ticking_mutable(self):
432        """Test ticking after applying a potion with mutable attributes."""
433        potion = Potion({'int_attr_fun': int_attr_fun,
434                         'float_attr_fun': float_attr_fun,
435                         'list_attr_fun': list_attr_fun,
436                         'dict_attr_fun': dict_attr_fun},
437                        duration=1)
438        self._dimitrichka.apply(self._target, potion)
439        self.assertEqual(self._target.int_attr, 50)
440        self.assertAlmostEqual(self._target.float_attr, 4.14)
441        self.assertEqual(self._target.list_attr, [1, 2, 3, 4])
442        self.assertEqual(self._target.dict_attr, {'Борис': 'name', 'жалбар': 'професия'})
443        self._dimitrichka.tick()
444        self.assertEqual(self._target.int_attr, 5)
445        self.assertAlmostEqual(self._target.float_attr, 3.14)
446        self.assertEqual(self._target.list_attr, [1, 2, 3])
447        self.assertEqual(self._target.dict_attr, {'name': 'Борис', 'професия': 'жалбар'})
448
449    def test_ticking_multiple_potions(self):
450        """Test ticking after applying multiple potions which affect the same attribute."""
451        # Same attribute
452        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
453        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=2)
454        self._dimitrichka.apply(self._target, potion1)
455        self._dimitrichka.apply(self._target, potion2)
456        self.assertEqual(self._target.int_attr, 500)
457        self._dimitrichka.tick()
458        self.assertEqual(self._target.int_attr, 50)
459        self._dimitrichka.tick()
460        self.assertEqual(self._target.int_attr, 5)
461        # Different attributes
462        self._target = copy.deepcopy(self._target)
463        self._target._refresh()
464        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1)
465        potion2 = Potion({'float_attr_fun': float_attr_fun}, duration=2)
466        self._dimitrichka.apply(self._target, potion1)
467        self._dimitrichka.apply(self._target, potion2)
468        self.assertEqual(self._target.int_attr, 50)
469        self.assertAlmostEqual(self._target.float_attr, 4.14)
470        self._dimitrichka.tick()
471        self.assertEqual(self._target.int_attr, 5)
472        self.assertAlmostEqual(self._target.float_attr, 4.14)
473        self._dimitrichka.tick()
474        self.assertEqual(self._target.int_attr, 5)
475        self.assertAlmostEqual(self._target.float_attr, 3.14)
476
477    def test_ticking_multiple_targets(self):
478        """Test ticking after applying a potion with mutable attributes."""
479        potion1 = Potion({'int_attr_fun': int_attr_fun}, duration=1) * 2
480        potion2 = Potion({'int_attr_fun': int_attr_fun}, duration=2)
481        target1 = self._target
482        target2 = Target(int_attr=5, float_attr=3.14,
483                         list_attr=[1, 2, 3],
484                         dict_attr={'name': 'Борис', 'професия': 'жалбар'})
485        self._dimitrichka.apply(target1, potion1)
486        self._dimitrichka.apply(target2, potion2)
487        self.assertEqual(target1.int_attr, 500)
488        self.assertEqual(target2.int_attr, 50)
489        self._dimitrichka.tick()
490        self.assertEqual(target1.int_attr, 5)
491        self.assertEqual(target2.int_attr, 50)
492        self._dimitrichka.tick()
493        self.assertEqual(target1.int_attr, 5)
494        self.assertEqual(target2.int_attr, 5)
495
496
497if __name__ == '__main__':
498    unittest.main()
Дискусия
Георги Кунчев
05.12.2023 11:18

Ако ползваш отварата - potion. Ако ползваш само ефект от нея - effect.
Веселина Велкова
05.12.2023 11:07

В случая на grow_potion едновременно и ефектът и отварата се изчерпват. Каква грешка трябва да хвърлим effect is depleted or potion is depleted?
Михаил Цанков
05.12.2023 08:05

Ето още малко тестове https://pastebin.com/HPSDewDa Ако някой нещо му гърми 🤙
Костадин Русалов
05.12.2023 03:16

Ето малко тестове, като тук-там може да има някой изпуснат кейс или някой недефиниран, или някой грешен (дано няма такива) ``` import unittest grow = {'grow': lambda _: setattr(_, 'size', _.size * 2)} heal = {'heal': lambda _: setattr(_, 'health', _.health + 10)} strengthen = {'strengthen': lambda _: setattr(_, 'strength', _.strength + 20)} fly = {'fly': lambda _: setattr(_, 'fly', True)} invisible = {'invisible': lambda _: setattr(_, 'invisible', True)} glow = {'glow': lambda _: setattr(_, 'glow', _.glow * 3)} armor = {'armor': lambda _: setattr(_, 'armor', _.armor + 5)} speed = {'speed': lambda _: setattr(_, 'speed', _.speed * 2)} power = {'power': lambda _: setattr(_, 'power', 200)} TRANSCENDED = 'Potion is now part of something bigger than itself.' DEPLETED_P = 'Potion is depleted.' DEPLETED_E = 'Effect is depleted.' class Target: def __init__(self, **kwargs): for prop, val in kwargs.items(): setattr(self, prop, val) class PotionTest(unittest.TestCase): def test_use_potion_effect_once(self): grow_potion = Potion(grow, 1) target = Target(size=10) grow_potion.grow(target) self.assertEqual(20, target.size) with self.assertRaises(TypeError) as e: grow_potion.grow(target) self.assertEqual(DEPLETED_E, str(e.exception)) def test_combined_potion_has_all_effects(self): grow_heal_potion = Potion(grow | heal, 1) fly_invis_potion = Potion(fly | invisible, 1) all_potion = grow_heal_potion + fly_invis_potion self.assertTrue('grow' in dir(all_potion)) self.assertTrue('heal' in dir(all_potion)) self.assertTrue('invisible' in dir(all_potion)) self.assertTrue('fly' in dir(all_potion)) def test_combined_potion_has_increased_intensity(self): triple_grow_potion = Potion(grow, 1) + Potion(grow, 1) + Potion(grow, 1) target = Target(size=8) triple_grow_potion.grow(target) self.assertEqual(64, target.size) def test_combined_potion_has_longer_duration(self): heal_potion = Potion(heal, 10) grow_potion = Potion(grow, 5) fly_potion = Potion(fly, 7) triple_potion = heal_potion + grow_potion + fly_potion self.assertEqual(10, triple_potion.duration) def test_potented_potion_has_increased_intensity(self): heal_grow_potion = Potion(heal | grow, 1) potented_potion = heal_grow_potion * 5 target = Target(size=1, health=19) potented_potion.heal(target) potented_potion.grow(target) self.assertEqual(69, target.health) self.assertEqual(32, target.size) def test_diluted_potion_has_decreased_intensity_rounded_half_down(self): heal_2_grow_4_potion = Potion(heal, 1) * 2 + Potion(grow, 1) * 4 diluted_grow_potion = heal_2_grow_4_potion * 0.625 target = Target(size=25, health=10) diluted_grow_potion.grow(target) diluted_grow_potion.heal(target) self.assertEqual(100, target.size) self.assertEqual(20, target.health) def test_diluted_potion_has_decreased_intensity_rounded_half_up(self): triple_heal_potion = Potion(heal, 1) * 3 diluted_potion = triple_heal_potion * 0.6 target = Target(health=10) diluted_potion.heal(target) self.assertEqual(30, target.health) def test_diluted_potion_with_zero_intensity(self): fly_potion = Potion(fly, 1) * 0 target = Target(fly=False) self.assertTrue('fly' in dir(fly_potion)) fly_potion.fly(target) self.assertFalse(target.fly) def test_purify_with_potion_that_has_more_effects(self): heal_grow_potion = Potion(heal | grow, 1) heal_fly_potion = Potion(heal | fly, 1) with self.assertRaises(TypeError): heal_grow_potion - heal_fly_potion def test_purify_potion_decreases_intensities_removes_non_positive_effects(self): heal_grow_power_armor_speed_potion = (Potion(armor, 1) * 3 + Potion(grow, 1) * 4 + Potion(heal, 1) * 3 + Potion(power, 1) + Potion(speed, 8) * 2) heal_grow_power_armor_potion = (Potion(grow, 1) * 2 + Potion(heal, 1) * 3 + Potion(power, 1) * 3 + Potion(armor, 1) * 0) grow_armor_speed_potion = heal_grow_power_armor_speed_potion - heal_grow_power_armor_potion with self.assertRaises(AttributeError): grow_armor_speed_potion.heal with self.assertRaises(AttributeError): grow_armor_speed_potion.power target = Target(armor=10, size=20, speed=10) grow_armor_speed_potion.grow(target) grow_armor_speed_potion.armor(target) grow_armor_speed_potion.speed(target) self.assertEqual(80, target.size) self.assertEqual(25, target.armor) self.assertEqual(40, target.speed) def test_purify_potion_that_has_used_effect(self): heal_grow_potion = Potion(heal | grow, 1) * 3 heal_grow_potion.grow(Target(size=0)) grow_potion = Potion(grow, 2) * 2 heal_potion = heal_grow_potion - grow_potion with self.assertRaises(AttributeError): heal_potion.grow target = Target(health=60) heal_potion.heal(target) self.assertEqual(90, target.health) def test_purify_potion_with_one_that_has_used_effects(self): power_speed_potion = Potion(power, 1) * 3 + Potion(speed, 1) speed_potion = Potion(speed, 1) speed_potion.speed(Target(speed=0)) purified_power_speed_potion = power_speed_potion - speed_potion target = Target(power=10, speed=40) purified_power_speed_potion.power(target) purified_power_speed_potion.speed(target) self.assertEqual(200, target.power) self.assertEqual(80, target.speed) @unittest.skip def test_purify_potion_undefined_case_vol_1(self): heal_grow_potion = Potion(heal, 1) * 2 + Potion(grow, 2) * 0 used_grow_potion = Potion(grow, 1) used_grow_potion.grow(Target()) target = Target(size=10) (heal_grow_potion - used_grow_potion).grow(target) self.assertEqual(10, target.size) @unittest.skip def test_purify_potion_undefined_case_vol_2(self): heal_fly_potion = Potion(heal | fly, 2) * 3 heal_fly_potion.heal(Target()) fly_potion = Potion(fly, 2) * 10 with self.assertRaises(AttributeError): (heal_fly_potion - fly_potion).heal(Target()) def test_purify_potion_removes_all_effects(self): heal_potion = Potion(heal, 1) double_heal_potion = Potion(heal, 2) * 2 empty_potion = heal_potion - double_heal_potion with self.assertRaises(AttributeError): empty_potion.heal(Target()) def test_divide_potion_has_divided_intensity(self): fly_heal_grow_strengthen = (Potion(fly, 1) * 4 + Potion(heal, 1) * 10 + Potion(grow, 1) * 5 + Potion(strengthen, 1) * 15) fly_heal_grow_strengthen.fly(Target()) one_fourth_1, one_fourth_2, one_fourth_3, one_fourth_4 = fly_heal_grow_strengthen / 4 with self.assertRaises(AttributeError): one_fourth_2.fly(Target()) target1 = Target(health=0) one_fourth_1.heal(target1) self.assertEqual(20, target1.health) target2 = Target(size=10, strength=20, health=10) one_fourth_2.heal(target2) one_fourth_2.grow(target2) one_fourth_2.strengthen(target2) self.assertEqual(30, target2.health) self.assertEqual(20, target2.size) self.assertEqual(100, target2.strength) target3 = Target(size=20) one_fourth_3.grow(target3) self.assertEqual(40, target3.size) target4 = Target(strength=100) one_fourth_4.strengthen(target4) self.assertEqual(180, target4.strength) def test_potion_transcends_after_sum(self): heal_potion = Potion(heal, 1) power_grow_potion = Potion(power | grow, 3) fly_potion = Potion(fly, 1) heal_potion + power_grow_potion + fly_potion with self.assertRaises(TypeError) as e: heal_potion.heal(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) with self.assertRaises(TypeError) as e: power_grow_potion.power(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) with self.assertRaises(TypeError) as e: power_grow_potion.grow(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) with self.assertRaises(TypeError) as e: fly_potion.fly(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) def test_transcends_after_potention(self): power_grow_potion = Potion(power | grow, 1) power_grow_potion * 69 with self.assertRaises(TypeError) as e: power_grow_potion.grow(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) with self.assertRaises(TypeError) as e: power_grow_potion.power(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) def test_transcends_after_dilution(self): speed_potion = Potion(speed, 0) speed_potion * 0.1 with self.assertRaises(TypeError) as e: speed_potion.speed(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) def test_transcends_after_purification(self): heal_grow_potion = Potion(heal | grow, 1) grow_potion = Potion(grow, 1) heal_grow_potion - grow_potion with self.assertRaises(TypeError) as e: heal_grow_potion.heal(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) with self.assertRaises(TypeError) as e: heal_grow_potion.grow(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) with self.assertRaises(TypeError) as e: grow_potion.grow(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) def test_transcends_after_division(self): power_armor_potion = Potion(power | armor, 1) power_armor_potion / 10 with self.assertRaises(TypeError) as e: power_armor_potion.power(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) with self.assertRaises(TypeError) as e: power_armor_potion.armor(Target()) self.assertEqual(TRANSCENDED, str(e.exception)) def test_combine_potions(self): heal_grow_speed_glow = (Potion(heal, 1) * 2 + Potion(grow, 1) * 5 + Potion(speed, 1) + Potion(glow, 1)) heal_grow_speed_glow.heal(Target(health=0)) heal_grow_speed_glow.glow(Target(glow=0)) heal_speed_grow_glow_armor = (Potion(heal, 1) * 3 + Potion(speed, 1) * 2 + Potion(grow, 1) * 2 + Potion(glow, 2) * 2 + Potion(armor, 2) * 0) heal_speed_grow_glow_armor.speed(Target(speed=0)) heal_speed_grow_glow_armor.glow(Target(glow=0)) combined = heal_grow_speed_glow + heal_speed_grow_glow_armor with self.assertRaises(AttributeError): combined.glow target = Target(health=10, size=1, speed=10, armor=20) combined.heal(target) combined.grow(target) combined.speed(target) combined.armor(target) self.assertEqual(40, target.health) self.assertEqual(128, target.size) self.assertEqual(20, target.speed) self.assertEqual(20, target.armor) def test_potent_potion(self): heal_grow_speed = Potion(heal | grow | speed, 1) heal_grow_speed.heal(Target(health=0)) eleventh = heal_grow_speed * 11 with self.assertRaises(AttributeError): eleventh.heal(Target(health=10)) target = Target(size=1, speed=1) eleventh.speed(target) eleventh.grow(target) self.assertEqual((2048, 2048), (target.speed, target.size)) def test_comparing_potions(self): grow_potion = Potion(grow, 1) double_grow_potion = Potion(grow, 1) * 2 invisible_fly_potion = Potion(invisible | fly, 1) self.assertTrue(grow_potion == grow_potion) self.assertFalse(grow_potion < grow_potion) self.assertTrue(double_grow_potion > grow_potion) self.assertTrue(invisible_fly_potion > grow_potion) self.assertFalse(double_grow_potion < invisible_fly_potion) self.assertFalse(double_grow_potion > invisible_fly_potion) def test_compare_potions_with_used_effects(self): grow_heal_speed_potion = Potion(grow, 1) * 0 + Potion(heal, 1) * 2 + Potion(speed, 2) used_grow_heal_speed_potion = Potion(grow | speed, 4) + Potion(heal, 2) * 2 used_grow_heal_speed_potion.grow(Target(size=0)) self.assertTrue(grow_heal_speed_potion == used_grow_heal_speed_potion) grow_heal_speed_potion -= Potion(grow, 1) self.assertFalse(grow_heal_speed_potion == used_grow_heal_speed_potion) def test_apply_potion_sorted_by_mass(self): grow_potion = Potion(grow | {'little_grow': lambda _: setattr(_, 'size', _.size + 20), 'shr': lambda _: setattr(_, 'size', _.size - 10)}, 1) target = Target(size=10) ГоспожатаПоХимия().apply(target, grow_potion) self.assertEqual(50, target.size) def test_deplete_potion_after_appy(self): heal_potion = Potion(heal, 1) target = Target(health=90) ГоспожатаПоХимия().apply(target, heal_potion) with self.assertRaises(TypeError) as e: heal_potion.heal(target) self.assertEqual(100, target.health) self.assertEqual(DEPLETED_P, str(e.exception)) with self.assertRaises(TypeError) as e: heal_potion + Potion({}, 1) self.assertEqual(DEPLETED_P, str(e.exception)) with self.assertRaises(TypeError) as e: heal_potion - Potion({}, 1) self.assertEqual(DEPLETED_P, str(e.exception)) with self.assertRaises(TypeError) as e: heal_potion * Potion({}, 1) self.assertEqual(DEPLETED_P, str(e.exception)) with self.assertRaises(TypeError) as e: heal_potion / 2 self.assertEqual(DEPLETED_P, str(e.exception)) with self.assertRaises(TypeError) as e: heal_potion == Potion({}, 1) self.assertEqual(DEPLETED_P, str(e.exception)) with self.assertRaises(TypeError) as e: heal_potion < Potion({}, 1) self.assertEqual(DEPLETED_P, str(e.exception)) with self.assertRaises(TypeError) as e: ГоспожатаПоХимия().apply(Target(), heal_potion) self.assertEqual(DEPLETED_P, str(e.exception)) def test_dimitrichka_with_zero_duration_potion(self): grow_potion = Potion(grow, 0) target = Target(size=10) димитричка = ГоспожатаПоХимия() димитричка.apply(target, grow_potion) self.assertEqual(10, target.size) def test_dimitrichka_with_used_effects(self): heal_grow_power_potion = Potion(heal | grow | power, 1) heal_grow_power_potion.power(Target()) pastichka = ГоспожатаПоХимия() target = Target(health=10, size=10, power=10) pastichka.apply(target, heal_grow_power_potion) self.assertEqual(20, target.health) self.assertEqual(20, target.size) self.assertEqual(10, target.power) def test_dimitrichka_with_one_poor_addict(self): heal_invisible_fly_potion = Potion(heal, 2) * 3 + Potion(fly | invisible, 3) target = Target(health=30, fly=False, invisible=False) пастичка = ГоспожатаПоХимия() пастичка.apply(target, heal_invisible_fly_potion) self.assertEqual(60, target.health) self.assertTrue(target.fly) self.assertTrue(target.invisible) пастичка.tick() self.assertEqual(60, target.health) self.assertTrue(target.fly) self.assertTrue(target.invisible) пастичка.tick() self.assertEqual(60, target.health) self.assertTrue(target.fly) self.assertTrue(target.invisible) пастичка.tick() self.assertEqual(30, target.health) self.assertFalse(target.fly) self.assertFalse(target.invisible) def test_dimitrichka_with_strong_drugs(self): power_potion = Potion(power, 2) target = Target() power_dimitrichka = ГоспожатаПоХимия() power_dimitrichka.apply(target, power_potion) self.assertEqual(200, target.power) power_dimitrichka.tick() power_dimitrichka.tick() self.assertFalse(hasattr(target, 'power')) def test_dimitrichka_with_one_rich_addict(self): heal_strengthen_potion = Potion(heal, 2) * 2 + Potion(strengthen, 1) grow_potion = Potion(grow, 2) target = Target(size=10, health=10, strength=10) narco = ГоспожатаПоХимия() narco.apply(target, heal_strengthen_potion) narco.tick() narco.apply(target, grow_potion) self.assertEqual(20, target.size) self.assertEqual(30, target.health) self.assertEqual(30, target.strength) narco.tick() self.assertEqual(20, target.size) self.assertEqual(10, target.health) self.assertEqual(10, target.strength) narco.tick() self.assertEqual(10, target.size) self.assertEqual(10, target.health) self.assertEqual(10, target.strength) def test_dimitrichka_with_potion_that_add_attributes(self): power_potion = Potion(power, 5) target = Target() narco = ГоспожатаПоХимия() narco.apply(target, power_potion) self.assertEqual(200, target.power) narco.tick() narco.tick() narco.tick() narco.tick() narco.tick() self.assertFalse(hasattr(target, 'power')) def test_dimitrichka_with_potion_that_removes_attributes(self): fmi_potion = Potion({'insomnia': lambda _: delattr(_, 'sleep')}, 3) student = Target(sleep='lovely') narco = ГоспожатаПоХимия() narco.apply(student, fmi_potion) self.assertFalse(hasattr(student, 'sleep')) narco.tick() narco.tick() narco.tick() self.assertTrue('lovely', student.sleep) ```
Мирослав Стояновски
05.12.2023 01:39

https://ibb.co/vm2T7bK В този пример, защо на последния ред пише, че се хвърля Attribute Error ? Тъй като на първия ред, когато правим пречистването, grow_and_immunity_potion и grow_potion и двете отвари имат grow ефект в себе си, и на двете grow ефекта е с интензитет 1, когато се извадят в получената отвара би трябвало да има grow ефект с интензитет 0(По условие, само ефекти с интензитети < 0 се трият, другите остават). След това, когато извикаме purified_immunity_potion.grow(target), се вика ефект с интензитет 0. Тогава би трябвало да не се хвърля никаква грешка, а да се изпълни ефекта 0 пъти. Защо се хвърля Attribute Error ?
Димитър Христов
04.12.2023 23:40

https://www.youtube.com/watch?v=R_FQU4KzN7A&ab_channel=JustinKuritzkes
Даниел Йорданов
04.12.2023 22:57

При операцията пречистване, ако и двете страни на '-' съдържат един и същ вече използван ефект, то трябва ли да сравняваме техните интензитети(както правим при неизползваните ефекти), за да решим дали да изтрием дадения такъв ("Ако отварата отдясно има по-голям интензитет, не получаваме негативен такъв а считаме ефектът за изцяло премахнат. Не с интензитет 0, изцяло премахнат."), тъй като говорихме, че интензитетът на използваните ефекти може да бъде разгледан като нула. Или винаги използваните ефекти не подлежат на сравнение и биват премахвани при тази операция?
Виктор Бечев
04.12.2023 22:49

Напълно възможно е.
Мирослав Стояновски
04.12.2023 22:35

Възможно ли е модифицираните от Potion-ки атрибути да бъдат от тип, различен от число ? Тоест възможно ли е target да има нечислен атрибут, и някой Potion да го модифицира ?
Георги Кунчев
04.12.2023 22:33

В този сценарий правилният вариант е вариант 1.