1from keyword import iskeyword
2
3
4class Egg:
5
6 def __init__(self):
7 self.upper_colors = {}
8 self.lower_colors = {}
9 self.percentage_colored = 0.0
10 self.bottom_broken = False
11 self.top_broken = False
12 self.tournament = None
13
14 def paint(self, *color_pairs):
15 new_total = self.percentage_colored + sum(p for _, p in color_pairs)
16 if new_total > 100.0:
17 raise ValueError("Egg color cannot exceed 100%")
18
19 for hex_color, percentage in color_pairs:
20
21 start = self.percentage_colored
22 end = self.percentage_colored + percentage
23 hex_color = hex_color.upper()
24
25 if start > 50.0:
26 self.lower_colors[hex_color] = self.lower_colors.get(hex_color, 0) + percentage
27 self.percentage_colored += percentage
28 continue
29
30 if start < 50 and end > 50:
31 upper_part = 50 - self.percentage_colored
32 lower_part = percentage - upper_part
33 self.upper_colors[hex_color] = self.upper_colors.get(hex_color, 0) + upper_part
34 self.lower_colors[hex_color] = self.lower_colors.get(hex_color, 0) + lower_part
35 self.percentage_colored += percentage
36 continue
37
38 if end <= 50.0:
39 self.upper_colors[hex_color] = self.upper_colors.get(hex_color, 0) + percentage
40 self.percentage_colored += percentage
41 continue
42
43 def register_in_tournament(self, tournament):
44 self.tournament = tournament
45
46
47 def __mul__(self, other):
48 if self.bottom_broken or other.bottom_broken:
49 raise TypeError("Egg is broken")
50 winner = None
51 if self.get_topside_pigment() < other.get_topside_pigment():
52 self.topside_broken = True
53 winner = other
54 else:
55 other.top_broken = True
56 winner = self
57
58 if self.tournament == other.tournament and self.tournament is not None:
59 self.tournament.log_result(self, other, "top", winner)
60 winner.tournament.add_point_to_egg(winner)
61 return winner
62
63 def __matmul__(self, other):
64 if self.bottom_broken or other.bottom_broken:
65 raise TypeError("Egg is broken")
66 winner = None
67 if self.get_bottomside_pigment() < other.get_bottomside_pigment():
68 self.bottom_broken = True
69 winner = other
70 else:
71 other.bottom_broken = True
72 winner = self
73
74 if self.tournament == other.tournament and self.tournament is not None:
75 self.tournament.log_result(self, other, "bottom", winner)
76 winner.tournament.add_point_to_egg(winner)
77 return winner
78
79 def get_topside_pigment(self):
80 pigment = 0
81 for colour, percentage in self.upper_colors.items():
82 r = int(colour[0:2], 16)
83 g = int(colour[2:4], 16)
84 b = int(colour[4:6], 16)
85 pigment += percentage * (r + g + b)
86 if self.percentage_colored > 0.0:
87 pigment += 0.0001
88
89 return pigment
90
91
92 def get_bottomside_pigment(self):
93 pigment = 0
94 for colour, percentage in self.lower_colors.items():
95 r = int(colour[0:2], 16)
96 g = int(colour[2:4], 16)
97 b = int(colour[4:6], 16)
98 pigment += percentage * (r + g + b)
99 if self.percentage_colored > 50.0:
100 pigment += 0.0001
101
102 return pigment
103
104class EggTournament:
105 registered_eggs = set()
106
107 def __init__(self):
108 self.eggs = {}
109 self.result = {}
110 self.ranking = {}
111
112 def register(self, egg, name):
113 if egg in self.registered_eggs:
114 raise ValueError("An egg cannot be registered in multiple tournaments")
115 if not name.isidentifier() or iskeyword(name):
116 raise ValueError("Invalid registration name")
117 if name in self.eggs:
118 raise ValueError(f"Egg with name {name} has already been registered")
119 self.eggs[name] = egg
120 self.registered_eggs.add(egg)
121 egg.register_in_tournament(self)
122 self.ranking[egg] = 0
123
124 def log_result(self, egg1, egg2, side, outcome):
125 self.result[(egg1,egg2, side)] = outcome
126
127 def __getitem__(self, index):
128 if isinstance(index, slice):
129 egg1, egg2, side = index.start, index.stop, index.step
130 else:
131 egg1, egg2, side = index
132
133 if (egg1, egg2, side) in self.result:
134 return self.result[(egg1, egg2, side)]
135 if (egg2, egg1, side) in self.result:
136 return self.result[(egg2, egg1, side)]
137 raise KeyError("No such battle has occurred")
138
139 def add_point_to_egg(self, egg):
140 self.ranking[egg] = self.ranking.get(egg, 0) + 1
141
142 def __rmatmul__(self, other):
143 if other <= 0:
144 raise IndexError("index out of bounds")
145 ranklist = {}
146 for egg, points in self.ranking.items():
147 ranklist.setdefault(points, set()).add(egg)
148
149 sorted_wins = sorted(ranklist.items(), reverse=True, key=lambda x: x[0])
150
151 if len(sorted_wins) < other:
152 raise IndexError("Index out of bounds")
153
154 return sorted_wins[other - 1][1]
155
156 def __getattr__(self, name):
157 if name not in self.eggs:
158 raise AttributeError("Apologies, there is no such egg registered")
159 target_egg = self.eggs[name]
160
161 wins_to_eggs = {}
162 for egg, points in self.ranking.items():
163 wins_to_eggs.setdefault(points, set()).add(egg)
164
165 sorted_wins = sorted(wins_to_eggs.keys(), reverse=True)
166 victories = self.ranking[target_egg]
167 position = sorted_wins.index(victories) + 1
168 return {"position": position, "victories": victories}
169
170 def __contains__(self, egg):
171 return egg in self.registered_eggs
.FFFF.F.F....F..........F...F.F.......F.FF..F.
======================================================================
FAIL: test_breaking_one_side_does_not_prevent_using_the_other_side (test.TestEgg.test_breaking_one_side_does_not_prevent_using_the_other_side)
Breaking one side of an egg should not prevent using the other side.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 293, in test_breaking_one_side_does_not_prevent_using_the_other_side
self.assertIs(third_egg @ fourth_egg, fourth_egg)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x7599bb6435c0> is not <solution.Egg object at 0x7599bb494950>
======================================================================
FAIL: test_broken_side_raises_type_error_regardless_of_operand_position (test.TestEgg.test_broken_side_raises_type_error_regardless_of_operand_position)
Using a broken side in a collision should raise a TypeError regardless of operand position.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 262, in test_broken_side_raises_type_error_regardless_of_operand_position
with self.assertRaises(TypeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^
AssertionError: TypeError not raised
======================================================================
FAIL: test_collision_result_is_independent_of_operand_order (test.TestEgg.test_collision_result_is_independent_of_operand_order)
Swapping the egg operands should not change the collision winner.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 227, in test_collision_result_is_independent_of_operand_order
self.assertIs(
~~~~~~~~~~~~~^
reversed_bottom_second_egg @ reversed_bottom_first_egg,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
reversed_bottom_first_egg,
^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
AssertionError: <solution.Egg object at 0x7599bb4889f0> is not <solution.Egg object at 0x7599bb6b13d0>
======================================================================
FAIL: test_collision_with_partially_painted_eggs (test.TestEgg.test_collision_with_partially_painted_eggs)
Partially painted eggs should collide correctly from both sides.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 193, in test_collision_with_partially_painted_eggs
self.assertIs(first_egg @ second_egg, second_egg)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x7599bb4887e0> is not <solution.Egg object at 0x7599bb66e850>
======================================================================
FAIL: test_losing_side_becomes_unusable (test.TestEgg.test_losing_side_becomes_unusable)
Losing a collision should make the losing side unusable.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 240, in test_losing_side_becomes_unusable
with self.assertRaises(TypeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^
AssertionError: TypeError not raised
======================================================================
FAIL: test_paint_can_be_called_multiple_times (test.TestEgg.test_paint_can_be_called_multiple_times)
Painting an egg in multiple calls should preserve the full painting order.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 55, in test_paint_can_be_called_multiple_times
self.assertIs(egg @ bottom_opponent, egg)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x7599bb49c550> is not <solution.Egg object at 0x7599bb49c450>
======================================================================
FAIL: test_paint_order_of_color_chunks_matters (test.TestEgg.test_paint_order_of_color_chunks_matters)
Painting the same colors in different orders should change the collision result.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 123, in test_paint_order_of_color_chunks_matters
self.assertIs(first_egg @ second_egg, second_egg)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x7599bb49c3d0> is not <solution.Egg object at 0x7599bb49c550>
======================================================================
FAIL: test_collision_between_two_registered_eggs_is_recorded (test.TestEggTournament.test_collision_between_two_registered_eggs_is_recorded)
A collision between two registered eggs should be recorded in the tournament.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 373, in test_collision_between_two_registered_eggs_is_recorded
self.assertIs(1 @ tournament, first_egg)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: {<solution.Egg object at 0x7599bb49c4d0>} is not <solution.Egg object at 0x7599bb49c4d0>
======================================================================
FAIL: test_egg_broken_outside_tournament_remains_broken_inside_tournament (test.TestEggTournament.test_egg_broken_outside_tournament_remains_broken_inside_tournament)
Breaking an egg outside the tournament should preserve its broken state inside the tournament.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 406, in test_egg_broken_outside_tournament_remains_broken_inside_tournament
with self.assertRaises(TypeError):
~~~~~~~~~~~~~~~~~^^^^^^^^^^^
AssertionError: TypeError not raised
======================================================================
FAIL: test_failed_registration_in_second_tournament_does_not_remove_first_registration (test.TestEggTournament.test_failed_registration_in_second_tournament_does_not_remove_first_registration)
Failing to register an egg in a second tournament should not remove its first registration.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 363, in test_failed_registration_in_second_tournament_does_not_remove_first_registration
self.assertNotIn(egg, second_tournament)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x7599bb49c7d0> unexpectedly found in <solution.EggTournament object at 0x7599bb651e50>
======================================================================
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 535, in test_ranking_missing_position_raises_index_error
self.assertIs(beta @ delta, beta)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x7599bb49d050> is not <solution.Egg object at 0x7599bb49cf50>
======================================================================
FAIL: test_ranking_unique_position_returns_single_egg (test.TestEggTournament.test_ranking_unique_position_returns_single_egg)
Looking up a unique ranking position should return a single egg.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 478, in test_ranking_unique_position_returns_single_egg
self.assertIs(1 @ tournament, alpha)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: {<solution.Egg object at 0x7599bb49d3d0>} is not <solution.Egg object at 0x7599bb49d3d0>
======================================================================
FAIL: 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 511, in test_ranking_uses_dense_ranking_without_skipping_places
self.assertIs(beta @ delta, beta)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x7599bb49dad0> is not <solution.Egg object at 0x7599bb49d8d0>
======================================================================
FAIL: test_registered_egg_attribute_returns_position_and_victories (test.TestEggTournament.test_registered_egg_attribute_returns_position_and_victories)
Accessing a registered egg as an attribute should return its position and victories.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 558, in test_registered_egg_attribute_returns_position_and_victories
self.assertIs(beta @ delta, beta)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x7599bb49e2d0> is not <solution.Egg object at 0x7599bb49e1d0>
----------------------------------------------------------------------
Ran 46 tests in 0.004s
FAILED (failures=14)
15.04.2026 16:09
15.04.2026 16:11
15.04.2026 16:10
15.04.2026 16:12
15.04.2026 16:12