1#!/usr/bin/python3
2import unittest
3
4class Egg:
5
6 def __init__(self):
7 self._colors = []
8 self._painted_so_far = 0
9 self._broken_up = False
10 self._broken_down = False
11 self._tournament = None
12
13 @property
14 def colors(self):
15 return tuple(self._colors)
16
17 @property
18 def painted_so_far(self):
19 return self._painted_so_far
20
21 @property
22 def broken_up(self):
23 return self._broken_up
24
25 @property
26 def broken_down(self):
27 return self._broken_down
28
29 @property
30 def tournament(self):
31 return self._tournament
32
33 @colors.setter
34 def colors(self, value):
35 print("You can't change colors like this!")
36 raise ValueError("Wrong command!")
37
38 @painted_so_far.setter
39 def painted_so_far(self, value):
40 print("You can't change colors like this!")
41 raise ValueError("Wrong command!")
42
43 @broken_up.setter
44 def broken_up(self, value):
45 print("You can't change broken status like this!")
46 raise ValueError("Wrong command!")
47
48 @broken_down.setter
49 def broken_down(self, value):
50 print("You can't change broken status like this!")
51 raise ValueError("Wrong command!")
52
53 @tournament.setter
54 def tournament(self, value):
55 print("You can't change tournament like this!")
56 raise ValueError("Wrong command!")
57
58 def paint(self, *color_data):
59 """
60 Records the coloring process of the egg based on the provided color pairs.
61
62 Args:
63 *color_pairs: Arbitrary number of tuples in the format (hex_color, percentage),
64 where hex_color is a string and percentage is a positive float.
65
66 Raises:
67 ValueError: If the total percentage after the update exceeds 100%.
68 No changes are applied to the egg's state if an error is raised.
69
70 Notes:
71 - The method can be called multiple times until the cumulative percentage
72 reaches exactly 100%.
73 - Hex colors are case-insensitive (e.g., "aabcd0" is treated the same as "AABCD0").
74 - Percentages are always real numbers greater than 0.
75 - Pairs are processed in the order they are provided.
76 - Invalid inputs (colors or percentages <= 0) are not expected and not handled.
77 """
78
79 colors_to_append = []
80 percentages_to_append = 0
81 for hex_color, percentage in color_data:
82 colors_to_append.append((hex_color.upper(), percentage))
83 percentages_to_append += percentage
84
85 if percentages_to_append + self._painted_so_far > 100:
86 raise ValueError("Percentages above 100!")
87
88 self._colors.extend(colors_to_append)
89 self._painted_so_far += percentages_to_append
90
91 @property
92 def strength_up(self):
93 total_strength = 0
94 current_pos = 0
95
96 for color, percentage in self.colors:
97 color_start = current_pos
98 color_end = current_pos + percentage
99
100 overlap_start = max(color_start, 0)
101 overlap_end = min(color_end, 50)
102
103 if overlap_end > overlap_start:
104 active_part = overlap_end - overlap_start
105
106 R = int(color[0:2], 16)
107 G = int(color[2:4], 16)
108 B = int(color[4:6], 16)
109
110 total_strength += (R + G + B) * ( active_part / 50 )
111
112 current_pos += percentage
113
114 return total_strength
115
116
117 @property
118 def strength_down(self):
119 total_strength = 0
120 current_pos = 0
121
122 for hex_color, percentage in self._colors:
123 color_start = current_pos
124 color_end = current_pos + percentage
125
126 overlap_start = max(color_start, 50)
127 overlap_end = min(color_end, 100)
128
129 if overlap_end > overlap_start:
130 active_part = overlap_end - overlap_start
131
132 R = int(hex_color[0:2], 16)
133 G = int(hex_color[2:4], 16)
134 B = int(hex_color[4:6], 16)
135
136 total_strength += (R + G + B) * (active_part / 50)
137
138 current_pos += percentage
139
140 return total_strength
141
142 def __mul__(self, other): # *
143 """Simulates a collision between eggs from the top side."""
144 if self.broken_up:
145 raise TypeError("The first egg is broken!")
146
147 if other.broken_up:
148 raise TypeError("The second egg is broken!")
149
150 if self._tournament != None and other._tournament != None and self._tournament is other._tournament:
151 validTournament = True
152 else:
153 validTournament = False
154
155 if len(self.colors) == 0:
156 self._broken_up = True
157
158 if validTournament:
159 self._tournament.battles.append((self, other, other, "top"))
160
161 return other
162 elif len(other.colors) == 0:
163 other._broken_up = True
164
165 if validTournament:
166 self._tournament.battles.append((self, other, self, "top"))
167
168 return self
169
170 if self.strength_up < other.strength_up:
171 self._broken_up = True
172
173 if validTournament:
174 self._tournament.battles.append((self, other, other, "top"))
175
176 return other
177 else:
178 other._broken_up = True
179
180 if validTournament:
181 self._tournament.battles.append((self, other, self, "top"))
182
183 return self
184
185
186 def __matmul__(self, other): # @
187 """Simulates a collision between eggs from the bottom side."""
188
189 if self.broken_down:
190 raise TypeError("The first egg is broken!")
191
192 if other.broken_down:
193 raise TypeError("The second egg is broken!")
194
195 if self._tournament != None and other._tournament != None and self._tournament is other._tournament:
196 validTournament = True
197 else:
198 validTournament = False
199
200 if self.strength_down == 0 and self.painted_so_far <= 50:
201 self._broken_down = True
202
203 if validTournament:
204 self._tournament.battles.append((self, other, other, "bottom"))
205
206 return other
207 elif other.strength_down == 0 and other.painted_so_far <= 50:
208 other._broken_down = True
209
210 if validTournament:
211 self._tournament.battles.append((self, other, self, "bottom"))
212
213 return self
214
215 if self.strength_down < other.strength_down:
216 self._broken_down = True
217
218 if validTournament:
219 self._tournament.battles.append((self, other, other, "bottom"))
220
221 return other
222 else:
223 other._broken_down = True
224
225 if validTournament:
226 self._tournament.battles.append((self, other, self, "bottom"))
227
228 return self
229
230 def __hash__(self):
231 return id(self)
232
233 def __eq__(self, other):
234 return self is other
235
236
237def valid_name(name):
238 for symbol in name:
239 if symbol.isupper():
240 return False
241
242 return True
243
244
245class EggTournament:
246 """
247 Manages and orchestrates collisions and duels between registered eggs.
248
249 This class acts as a controller that monitors interactions, maintains
250 a detailed history of all collisions, and tracks egg rankings. It provides
251 functionality to query collision data and rankings based on specific criteria.
252
253 Key Responsibilities:
254 - Monitoring duels between registered egg instances.
255 - Storing a comprehensive log of all collision events.
256 - Maintaining up-to-date leaderboards and statistics.
257 - Facilitating data retrieval through various search filters.
258 """
259
260 def __init__(self):
261 self._eggs = {}
262 self._nicknames = {}
263 self.battles = []
264
265 def register(self, egg, nickname):
266
267 if egg.tournament != None:
268 raise ValueError("An egg cannot be registered in multiple tournaments")
269
270 if not valid_name(nickname):
271 raise ValueError("Invalid registration name")
272
273 if nickname in self._nicknames:
274 raise ValueError(f"Egg with name {nickname} has already been registered")
275
276 self._eggs[egg] = nickname
277 self._nicknames[nickname] = egg
278 egg._tournament = self
279
280 def __getitem__(self, key):
281 if isinstance(key, tuple):
282 egg1, egg2, battle_way = key
283 elif isinstance(key, slice):
284 egg1, egg2, battle_way = key.start, key.stop, key.step
285
286 for curr_egg1, curr_egg2, curr_result, curr_battle_way in self.battles:
287 if {curr_egg1, curr_egg2} == {egg1, egg2} and curr_battle_way == battle_way:
288 return curr_result
289
290 raise KeyError("Battle not found")
291
292 def __ranks(self):
293 win_counts = {egg: 0 for egg in self._eggs}
294
295 for match in self.battles:
296 winner = match[2]
297 win_counts[winner] += 1
298
299 list_to_sort = []
300 for egg, wins in win_counts.items():
301 nickname = self._eggs[egg]
302 list_to_sort.append((wins, nickname, egg))
303
304 sorted_list = sorted(list_to_sort, key = lambda x: (-x[0],x[1]))
305
306 rank = 0
307 last_wins = -1
308 final_ranks = {}
309
310 for wins, nick, egg in sorted_list:
311 if wins != last_wins:
312 rank += 1
313 final_ranks[egg] = rank
314 last_wins = wins
315
316 return final_ranks
317
318 def ranking(self):
319 ranks_dict = self.__ranks()
320 res = []
321 for egg, r in ranks_dict.items():
322 res.append((r, self._eggs[egg]))
323 return sorted(res, key=lambda x: (x[0], x[1]))
324
325 def __rmatmul__(self, rank):
326
327 final_ranks = self.__ranks()
328
329 matched_eggs = [eg for eg, r in final_ranks.items() if r == rank]
330
331 if not matched_eggs:
332 raise IndexError(f"No eggs at rank {rank}")
333
334 if len(matched_eggs) == 1:
335 return matched_eggs[0]
336 return set(matched_eggs)
337
338 def __getattr__(self, name):
339 if name in self._nicknames:
340 egg = self._nicknames[name]
341
342 victories = sum(1 for match in self.battles if match[2] == egg)
343
344 all_ranks = self.__ranks()
345 position = all_ranks[egg]
346
347 return {"position": position, "victories": victories}
348
349 raise AttributeError("Apologies, there is no such egg registered")
350
351 def __contains__(self, egg):
352 return egg in self._eggs
353
354
355class TestEggsMethods(unittest.TestCase):
356
357 def test_init(self):
358 egg = Egg()
359 self.assertEqual(egg.painted_so_far, 0)
360 self.assertEqual(len(egg.colors), 0)
361 self.assertFalse(egg.broken_up)
362 self.assertFalse(egg.broken_down)
363 self.assertIsNone(egg.tournament)
364
365 def test_setter(self):
366 egg = Egg()
367 with self.assertRaises(ValueError):
368 egg.colors = [("FFFFFF", 100)]
369 with self.assertRaises(ValueError):
370 egg.painted_so_far = 30
371 with self.assertRaises(ValueError):
372 egg.broken_up = True
373 with self.assertRaises(ValueError):
374 egg.broken_down = True
375 with self.assertRaises(ValueError):
376 egg.tournament = 'Искам бонус точки за unittest-овете'
377
378 def test_paint(self):
379 egg = Egg()
380 egg.paint(("FFFFFF", 99))
381 self.assertIn(("FFFFFF", 99), egg.colors)
382 egg.paint(("FF0000", 0.3),("00FF00", 0.7))
383 self.assertIn(("FF0000", 0.3), egg.colors)
384 self.assertIn(("00FF00", 0.7), egg.colors)
385
386 with self.assertRaises(ValueError):
387 egg.paint(("FF0000", 20))
388
389 def test_strength_up(self):
390 egg1 = Egg()
391 egg1.paint(("AA2C00", 50.0))
392 egg1.paint(("FFFF00", 50.0))
393 self.assertEqual(egg1.strength_up, 170 + 44)
394
395 egg2 = Egg()
396 egg2.paint(("0000FF", 25.0), ("DD0000", 25.0))
397 self.assertEqual(egg2.strength_up, 255 * 0.5 + 221 * 0.5)
398
399 def test_strength_down(self):
400 egg1 = Egg()
401 egg1.paint(("AA2C00", 50.0))
402 egg1.paint(("FFFF00", 50.0))
403 self.assertEqual(egg1.strength_down, 255 + 255)
404
405 egg2 = Egg()
406 egg2.paint(("0000FF", 25.0), ("DD0000", 25.0), ("FFFE00", 50.0))
407 self.assertEqual(egg2.strength_down, 255 + 254)
408
409 def test_mul(self):
410 egg1 = Egg()
411 egg2 = Egg()
412
413 egg1.paint(("AA2C00", 50.0))
414 egg1.paint(("FFFF00", 50.0))
415 egg2.paint(("0000FF", 25.0), ("DD0000", 25.0), ("FFFE00", 50.0))
416
417 self.assertIs(egg1 * egg2, egg2)
418
419 with self.assertRaises(TypeError):
420 egg1 * egg2
421
422 egg1 = Egg()
423 egg2 = Egg()
424 egg2.paint(("000000", 50.0))
425 self.assertIs(egg1 * egg2, egg2)
426
427 def test_matmul(self):
428 egg1 = Egg()
429 egg2 = Egg()
430
431 egg1.paint(("AA2C00", 50.0))
432 egg1.paint(("FFFF00", 50.0))
433 egg2.paint(("0000FF", 25.0), ("DD0000", 25.0), ("FFFE00", 50.0))
434
435 self.assertIs(egg1 @ egg2, egg1)
436
437 with self.assertRaises(TypeError):
438 egg1 @ egg2
439
440 egg1 = Egg()
441 egg2 = Egg()
442 egg2.paint(("000000", 100.0))
443 self.assertIs(egg1 @ egg2, egg2)
444
445
446class TestEggTournament(unittest.TestCase):
447
448 def test_init(self):
449 tournament = EggTournament()
450 self.assertEqual(len(tournament._eggs), 0)
451 self.assertEqual(len(tournament._nicknames), 0)
452 self.assertEqual(len(tournament.battles), 0)
453
454 def test_register(self):
455 egg = Egg()
456 tournament = EggTournament()
457
458 tournament.register(egg, "biqch")
459 self.assertIn(egg, tournament._eggs)
460 self.assertIn("biqch", tournament._nicknames)
461
462 tournament2 = EggTournament()
463
464 with self.assertRaises(ValueError):
465 tournament2.register(egg, "biqch")
466
467 def test_battles_with_valid_data(self):
468 egg1 = Egg()
469 egg2 = Egg()
470
471 tournament = EggTournament()
472 tournament.register(egg1, "biqch")
473 tournament.register(egg2, "nebiqch")
474
475 egg1.paint(("FFFFFF", 100.0 ))
476 egg2.paint(("F0F0F0", 100.0))
477
478 res_egg = egg1 * egg2
479
480 self.assertIs(res_egg, egg1)
481 self.assertIn((egg1, egg2, res_egg, "top"), tournament.battles)
482
483 def test_battles_with_wrong_tournaments_for_second_egg(self):
484 egg1 = Egg()
485 egg2 = Egg()
486
487 tournament = EggTournament()
488 egg1.paint(("FFFFFF", 100.0 ))
489 egg2.paint(("F0F0F0", 100.0))
490 tournament.register(egg1, "biqch")
491
492 res_egg = egg1 * egg2
493
494 self.assertIs(res_egg, egg1)
495 self.assertNotIn((egg1, egg2, res_egg, "top"), tournament.battles)
496
497 def test_battles_with_wrong_tournaments_for_first_egg(self):
498 egg1 = Egg()
499 egg2 = Egg()
500
501 tournament = EggTournament()
502 egg1.paint(("FFFFFF", 100.0 ))
503 egg2.paint(("F0F0F0", 100.0))
504 tournament.register(egg2, "biqch")
505
506 res_egg = egg1 * egg2
507
508 self.assertIs(res_egg, egg1)
509 self.assertNotIn((egg1, egg2, res_egg, "top"), tournament.battles)
510
511 def test_battles_with_wrong_tournaments_for_first_and_second_egg(self):
512 egg1 = Egg()
513 egg2 = Egg()
514
515 tournament = EggTournament()
516 egg1.paint(("FFFFFF", 100.0 ))
517 egg2.paint(("F0F0F0", 100.0))
518
519 res_egg = egg1 * egg2
520
521 self.assertIs(res_egg, egg1)
522 self.assertNotIn((egg1, egg2, res_egg, "top"), tournament.battles)
523
524 def test_register_with_wrong_nickname(self):
525 egg = Egg()
526 nickanme = "TheBestNicknameInTheWorld"
527 tournament = EggTournament()
528
529 with self.assertRaises(ValueError):
530 tournament.register(egg, nickanme)
531
532 def test_register_with_occupied_nickname(self):
533 egg1 = Egg()
534 nickname = "not_the_best_nickname_in_the_world"
535 tournament = EggTournament()
536 tournament.register(egg1,nickname)
537
538 egg2 = Egg()
539 with self.assertRaises(ValueError):
540 tournament.register(egg2, nickname)
541
542 def test_getitem_with_valid_data(self):
543 egg1 = Egg()
544 egg2 = Egg()
545
546 egg1.paint(("FFFFFF", 100.0))
547 egg2.paint(("F0F0F0", 100.0))
548
549 tournament = EggTournament()
550
551 tournament.register(egg1, "winner")
552 tournament.register(egg2, "loser")
553
554 egg1 * egg2
555
556 result = tournament[(egg1, egg2, "top")]
557 self.assertIs(egg1, result)
558
559 result = tournament[egg1:egg2:"top"]
560 self.assertIs(egg1, result)
561
562 def test_getitem_with_wrong_data(self):
563 tournament = EggTournament()
564 egg1 = Egg()
565 egg2 = Egg()
566 with self.assertRaises(KeyError):
567 result = tournament[egg1:egg2:"bottom"]
568
569 def test_ranks(self):
570 tournament = EggTournament()
571
572 egg1 = Egg()
573 egg2 = Egg()
574 egg3 = Egg()
575 egg4 = Egg()
576
577 tournament.register(egg1, "alpha")
578 tournament.register(egg2, "beta")
579 tournament.register(egg3, "gamma")
580 tournament.register(egg4, "delta")
581
582 tournament.battles.append((egg1, egg2, egg1, "top"))
583 tournament.battles.append((egg1, egg3, egg1, "top"))
584 tournament.battles.append((egg2, egg3, egg2, "top"))
585 tournament.battles.append((egg4, egg3, egg4, "top"))
586
587 expected_ranking = [(1, "alpha"), (2, "beta"), (2, "delta"), (3, "gamma")]
588 self.assertEqual(tournament.ranking(), expected_ranking)
589
590 self.assertEqual(1 @ tournament, egg1)
591
592 rank2_result = 2 @ tournament
593 self.assertIsInstance(rank2_result, set)
594 self.assertEqual(rank2_result, {egg2, egg4})
595
596 with self.assertRaises(IndexError):
597 _ = 10 @ tournament
598
599 def test_contains(self):
600 tournament = EggTournament()
601 egg = Egg()
602
603 self.assertFalse(egg in tournament)
604
605 tournament.register(egg, "tester")
606 self.assertTrue(egg in tournament)
607 self.assertIn(egg, tournament)
608
609 def test_attribute_access_success(self):
610 tournament = EggTournament()
611 egg = Egg()
612 tournament.register(egg, "the_monster")
613
614 egg_other = Egg()
615 tournament.battles.append((egg, egg_other, egg, "top"))
616 tournament.battles.append((egg, egg_other, egg, "bottom"))
617
618 expected = {"position": 1, "victories": 2}
619 self.assertEqual(tournament.the_monster, expected)
620
621 def test_attribute_access_failure(self):
622 tournament = EggTournament()
623
624 with self.assertRaises(AttributeError) as cm:
625 _ = tournament.missing_egg
626
627 self.assertEqual(str(cm.exception), "Apologies, there is no such egg registered")
628
629 def test_attribute_access_real_attribute(self):
630 tournament = EggTournament()
631 self.assertEqual(tournament.battles, [])
632
633
634class TestValidName(unittest.TestCase):
635
636 def test_valid_name(self):
637 first_nickname = 'valid_word'
638 second_nickname = 'InValid_word'
639 third_nickname = 'invalid_Word'
640 fourth_nickname = 'valid_word_1'
641 fifth_nickname = 'InValid_Word'
642
643 self.assertTrue(valid_name(first_nickname))
644 self.assertFalse(valid_name(second_nickname))
645 self.assertFalse(valid_name(third_nickname))
646 self.assertTrue(valid_name(fourth_nickname))
647 self.assertFalse(valid_name(fifth_nickname))
648
649if __name__ == "__main__":
650 unittest.main()
...........................................F..........
======================================================================
FAIL: test_register_invalid_name_raises_value_error (test.TestEggTournament.test_register_invalid_name_raises_value_error)
Registering an egg with an invalid name should raise a ValueError.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 312, in test_register_invalid_name_raises_value_error
with self.assertRaises(ValueError) as context:
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
AssertionError: ValueError not raised
----------------------------------------------------------------------
Ran 54 tests in 0.002s
FAILED (failures=1)
Виктор Бечев
15.04.2026 14:14И моля те не качвай повече тестове така, че объркват бройката на test suite-а. 😅
Ако си написал тестове и искаш обратна връзка - качи ги в gist и ни сподели линка.
|
Виктор Бечев
15.04.2026 13:54За някои от тестовете на яйцата - хубаво е да имаш edge case-ове. Например - какво става когато боядисаме яйце на 40%, после 20%, после още 40%. Само един пример, има много такива. Но разбира се, kudos за това, че изобщо си написал тестове, не очакваме от вас да пишете десетки тестове, както ние правим. Споменавам го като идея, когато започнеш да пишеш повечко тестове за неща, за които наистина е важно това покритие.
|
15.04.2026 13:35
15.04.2026 13:36
15.04.2026 13:37
15.04.2026 13:40
15.04.2026 13:41
15.04.2026 13:38
15.04.2026 13:42
15.04.2026 13:43
15.04.2026 13:46
15.04.2026 13:50