1import keyword
2
3
4class Egg:
5 def __init__(self):
6 self.paint_data = []
7 self._percentage_painted = 0.0
8 self.broken_top = False
9 self.broken_bottom = False
10 self.tournament = None
11
12 def __mul__(self, other): # сблъсък от горна страна
13 if self.broken_top:
14 raise TypeError("This egg's top side is broken and cannot compete again.")
15 if other.broken_top:
16 raise TypeError("The other egg's top side is broken and cannot compete again.")
17
18 self_strength = self.half_strength(0, 50)
19 other_strength = other.half_strength(0, 50)
20
21 winner = self if self_strength > other_strength else other
22 loser = other if winner is self else self
23
24 winner.broken_top = False
25 loser.broken_top = True
26
27 if (self.tournament is not None
28 and self.tournament is other.tournament):
29 self.tournament.record_battle(self, other, "top", winner)
30
31 return winner
32
33 def __matmul__(self, other): # сблъсък от долна страна
34 if self.broken_bottom:
35 raise TypeError("This egg's bottom side is broken and cannot compete again.")
36 if other.broken_bottom:
37 raise TypeError("The other egg's bottom side is broken and cannot compete again.")
38
39 self_strength = self.half_strength(50, 100)
40 other_strength = other.half_strength(50, 100)
41
42 winner = self if self_strength > other_strength else other
43 loser = other if winner is self else self
44
45 winner.broken_bottom = False
46 loser.broken_bottom = True
47
48 if (self.tournament is not None
49 and self.tournament is other.tournament):
50 self.tournament.record_battle(self, other, "bottom", winner)
51
52 return winner
53
54 def half_strength(self, half_start, half_end):
55 current_start = 0.0
56 strength = 0.0
57
58 for hex_color, percentage in self.paint_data:
59 segment_start = current_start
60 segment_end = current_start + percentage
61
62 overlap = (max(0.0, min(segment_end, half_end) - max(segment_start, half_start)))
63
64 if overlap > 0:
65 pigment_value = Egg.pigment_value(hex_color)
66 strength += pigment_value * (overlap / 50)
67
68 current_start = segment_end
69
70 return strength
71
72 def paint(self, *hex_color_data):
73 total_paint_percentage = sum(percentage for _, percentage in hex_color_data)
74
75 if total_paint_percentage + self._percentage_painted > 100.0:
76 raise ValueError("The egg is overpainted.")
77
78 for hex_color, percentage in hex_color_data:
79 self.paint_data.append((hex_color, percentage))
80 self._percentage_painted += percentage
81
82 @staticmethod
83 def convert_hex_to_rgb(hex_color):
84 return tuple(int(hex_color[i:i + 2], 16) for i in (0, 2, 4))
85
86 @staticmethod
87 def pigment_value(hex_color):
88 r, g, b = Egg.convert_hex_to_rgb(hex_color)
89 return r + g + b
90
91
92class EggTournament:
93 def __init__(self):
94 self._eggs = {}
95 self._egg_to_name = {}
96 self._victories = {}
97 self._history = {}
98
99 def register(self, egg: Egg, name: str):
100 if not name.isidentifier() or keyword.iskeyword(name):
101 raise ValueError("Invalid registration name")
102
103 if egg.tournament is not None:
104 raise ValueError("An egg cannot be registered in multiple tournaments")
105
106 if name in self._eggs:
107 raise ValueError(f"Egg with name {name} has already been registered")
108
109 self._eggs[name] = egg
110 self._egg_to_name[id(egg)] = name
111 self._victories[id(egg)] = 0
112 egg.tournament = self
113
114 def record_battle(self, egg1: Egg, egg2: Egg, side: str, winner: Egg):
115 if id(egg1) not in self._egg_to_name or id(egg2) not in self._egg_to_name:
116 return
117
118 key = (frozenset({id(egg1), id(egg2)}), side)
119 self._history[key] = winner
120 self._victories[id(winner)] += 1
121
122 def __getitem__(self, item):
123 if isinstance(item, tuple):
124 egg1, egg2, side = item
125 elif isinstance(item, slice):
126 egg1 = item.start
127 egg2 = item.stop
128 side = item.step
129 else:
130 raise KeyError(item)
131
132 side_map = {"top": "top", "bottom": "bottom"}
133 if side not in side_map:
134 raise KeyError(f"Unknown side: {side}")
135
136 key = (frozenset({id(egg1), id(egg2)}), side)
137 if key not in self._history:
138 raise KeyError("No such battle found in the tournament.")
139
140 return self._history[key]
141
142 def __rmatmul__(self, position: int):
143 if not self._eggs:
144 raise IndexError("No eggs registered in this tournament.")
145
146 sorted_eggs = sorted(
147 self._victories.items(),
148 key=lambda kv: kv[1],
149 reverse=True
150 )
151
152 ranks = {}
153 current_rank = 1
154 prev_wins = None
155 for i, (egg_id, wins) in enumerate(sorted_eggs):
156 if wins != prev_wins:
157 current_rank = i + 1
158 prev_wins = wins
159 ranks[egg_id] = current_rank
160
161 result = {
162 self._eggs[self._egg_to_name[egg_id]]
163 for egg_id, rank in ranks.items()
164 if rank == position
165 }
166
167 if not result:
168 raise IndexError(f"No egg at position {position} in the rankings.")
169
170 if len(result) == 1:
171 return next(iter(result))
172 return result
173
174 def __getattr__(self, name: str):
175 if name.startswith("_"):
176 raise AttributeError(f"Apologies, there is no such egg registered")
177
178 eggs = object.__getattribute__(self, "_eggs")
179 victories = object.__getattribute__(self, "_victories")
180
181 if name not in eggs:
182 raise AttributeError("Apologies, there is no such egg registered")
183
184 egg = eggs[name]
185 wins = victories[id(egg)]
186
187 sorted_wins = sorted(victories.values(), reverse=True)
188 rank = 1
189 prev = None
190 for i, v in enumerate(sorted_wins):
191 if v != prev:
192 rank = i + 1
193 prev = v
194 if v == wins:
195 break
196
197 return {"position": rank, "victories": wins}
198
199 def __contains__(self, egg: Egg):
200 return id(egg) in self._egg_to_name
.....................F................F..E....
======================================================================
ERROR: test_ranking_uses_dense_ranking_without_skipping_places (test.TestEggTournament.test_ranking_uses_dense_ranking_without_skipping_places)
Looking up ranking positions should use dense ranking without skipping places.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 516, in test_ranking_uses_dense_ranking_without_skipping_places
self.assertEqual(3 @ tournament, {delta, epsilon})
~~^~~~~~~~~~~~
File "/tmp/solution.py", line 168, in __rmatmul__
raise IndexError(f"No egg at position {position} in the rankings.")
IndexError: No egg at position 3 in the rankings.
======================================================================
FAIL: test_unpainted_half_loses_to_half_painted_black (test.TestEgg.test_unpainted_half_loses_to_half_painted_black)
An unpainted half should lose to a half painted in black.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 205, in test_unpainted_half_loses_to_half_painted_black
self.assertIs(black_egg * unpainted_egg, black_egg)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x70904122cfa0> is not <solution.Egg object at 0x70904122ce50>
======================================================================
FAIL: test_ranking_missing_position_raises_index_error (test.TestEggTournament.test_ranking_missing_position_raises_index_error)
Looking up a missing ranking position should raise an IndexError.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 538, in test_ranking_missing_position_raises_index_error
with self.assertRaises(IndexError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
AssertionError: IndexError not raised
----------------------------------------------------------------------
Ran 46 tests in 0.003s
FAILED (failures=2, errors=1)
17.04.2026 22:13
17.04.2026 22:13
17.04.2026 22:14
17.04.2026 22:15
17.04.2026 22:16
17.04.2026 22:17
17.04.2026 22:19
17.04.2026 22:18
17.04.2026 22:20
17.04.2026 22:21
17.04.2026 22:24