1import re
2import keyword
3
4class Egg:
5 _tournament = None
6
7 def __init__(self):
8 self._colors = []
9 self._total = 0.0
10 self._broken_top = False
11 self._broken_bottom = False
12
13 def paint(self, *pairs):
14 new_total = sum(p for _, p in pairs)
15 if self._total + new_total > 100.0:
16 raise ValueError("Cannot overfill the egg")
17 for hex_color, percentage in pairs:
18 self._colors.append((hex_color.upper(), percentage))
19 self._total += percentage
20
21 def _pigment(self, side):
22 """Return total pigment for 'top' (0-50%) or 'bottom' (50-100%) half."""
23 if side not in ("top", "bottom"):
24 raise ValueError("side must be 'top' or 'bottom'")
25 lower = 0.0 if side == "top" else 50.0
26 upper = 50.0 if side == "top" else 100.0
27
28 total_pigment = 0.0
29 cumulative = 0.0
30 for hex_color, percentage in self._colors:
31 seg_start = cumulative
32 seg_end = cumulative + percentage
33 overlap = max(0.0, min(seg_end, upper) - max(seg_start, lower))
34 if overlap > 0:
35 r = int(hex_color[0:2], 16)
36 g = int(hex_color[2:4], 16)
37 b = int(hex_color[4:6], 16)
38 pig_per_pct = (r + g + b) / percentage
39 total_pigment += overlap * pig_per_pct
40 cumulative += percentage
41 return total_pigment
42
43 def _clash(self, other, side):
44 if side == "top":
45 if self._broken_top:
46 raise TypeError("This egg's top is already broken")
47 if other._broken_top:
48 raise TypeError("The other egg's top is already broken")
49 else:
50 if self._broken_bottom:
51 raise TypeError("This egg's bottom is already broken")
52 if other._broken_bottom:
53 raise TypeError("The other egg's bottom is already broken")
54
55 my_pig = self._pigment(side)
56 their_pig = other._pigment(side)
57
58 if my_pig >= their_pig:
59 winner, loser = self, other
60 else:
61 winner, loser = other, self
62
63 if side == "top":
64 loser._broken_top = True
65 else:
66 loser._broken_bottom = True
67
68 if self._tournament is not None:
69 self._tournament._record(self, other, side, winner)
70
71 return winner
72
73 def __mul__(self, other):
74 return self._clash(other, "top")
75
76 def __matmul__(self, other):
77 return self._clash(other, "bottom")
78
79
80class EggTournament:
81 def __init__(self):
82 self._eggs = {} # name -> egg
83 self._wins = {} # egg -> int
84 self._history = {}
85
86 def register(self, egg, name):
87 if not name.isidentifier() or keyword.iskeyword(name):
88 raise ValueError("Invalid registration name")
89 if egg._tournament is not None:
90 raise ValueError("An egg cannot be registered in multiple tournaments")
91 if name in self._eggs:
92 raise ValueError(f"Egg with name {name} has already been registered")
93
94 self._eggs[name] = egg
95 self._wins[egg] = 0
96 egg._tournament = self
97
98 def _record(self, egg_a, egg_b, side, winner):
99 """Only care about clashes between two registered eggs."""
100 reg = set(self._eggs.values())
101 if egg_a not in reg or egg_b not in reg:
102 return
103 key = frozenset({egg_a, egg_b})
104 if key not in self._history:
105 self._history[key] = {}
106 self._history[key][side] = winner
107 self._wins[winner] += 1
108
109 def _ranking(self):
110 """Return dict: egg -> rank (dense/no-skip)."""
111 eggs = list(self._wins.keys())
112 sorted_eggs = sorted(eggs, key=lambda e: self._wins[e], reverse=True)
113 rank_map = {}
114 rank = 1
115 i = 0
116 while i < len(sorted_eggs):
117 j = i
118 while j < len(sorted_eggs) and \
119 self._wins[sorted_eggs[j]] == self._wins[sorted_eggs[i]]:
120 j += 1
121 for e in sorted_eggs[i:j]:
122 rank_map[e] = rank
123 rank += 1
124 i = j
125 return rank_map
126
127 def __getitem__(self, key):
128 if isinstance(key, slice):
129 egg_a = key.start
130 egg_b = key.stop
131 side_str = key.step
132 elif isinstance(key, tuple) and len(key) == 3:
133 egg_a, egg_b, side_str = key
134 else:
135 raise KeyError(key)
136
137 side = "top" if side_str == "top" else "bottom"
138 k = frozenset({egg_a, egg_b})
139 if k not in self._history or side not in self._history[k]:
140 raise KeyError("No such clash recorded")
141 return self._history[k][side]
142
143 def __rmatmul__(self, position):
144 rank_map = self._ranking()
145 at_pos = {e for e, r in rank_map.items() if r == position}
146 if not at_pos:
147 raise IndexError(f"No egg at position {position}")
148 if len(at_pos) == 1:
149 return next(iter(at_pos))
150 return at_pos
151
152 def __getattr__(self, name):
153 """Called only when normal lookup fails"""
154 eggs = object.__getattribute__(self, "_eggs")
155 if name in eggs:
156 egg = eggs[name]
157 rank_map = self._ranking()
158 wins = object.__getattribute__(self, "_wins")
159 return {"position": rank_map[egg], "victories": wins[egg]}
160 raise AttributeError("Apologies, there is no such egg registered")
161
162 def __contains__(self, egg):
163 return egg in self._eggs.values()
.........F..F....F...F........................
======================================================================
FAIL: test_paint_chunk_crossing_50_percent_boundary_is_split_correctly (test.TestEgg.test_paint_chunk_crossing_50_percent_boundary_is_split_correctly)
Painting a chunk across the half boundary should split it correctly between the two halves.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 139, in test_paint_chunk_crossing_50_percent_boundary_is_split_correctly
self.assertIs(egg @ opponent, opponent)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x728945038d70> is not <solution.Egg object at 0x728945038e50>
======================================================================
FAIL: test_paint_multiple_calls_can_cross_50_percent_boundary (test.TestEgg.test_paint_multiple_calls_can_cross_50_percent_boundary)
Painting an egg across the half boundary in multiple calls should split the halves correctly.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 151, in test_paint_multiple_calls_can_cross_50_percent_boundary
self.assertIs(egg @ opponent, opponent)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x728945038d70> is not <solution.Egg object at 0x728945038e50>
======================================================================
FAIL: test_paint_overflow_with_multiple_colors_should_not_change_the_egg (test.TestEgg.test_paint_overflow_with_multiple_colors_should_not_change_the_egg)
Failing to overpaint an egg with multiple colors should not change the egg.
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 163, in test_paint_overflow_with_multiple_colors_should_not_change_the_egg
self.assertIs(egg @ bottom_opponent, bottom_opponent)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x728945038d70> is not <solution.Egg object at 0x728945038e50>
======================================================================
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 200, in test_unpainted_half_loses_to_half_painted_black
self.assertIs(unpainted_egg * black_egg, black_egg)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AssertionError: <solution.Egg object at 0x728945038d70> is not <solution.Egg object at 0x728945038e50>
----------------------------------------------------------------------
Ran 46 tests in 0.003s
FAILED (failures=4)
16.04.2026 19:17
16.04.2026 19:38
16.04.2026 19:41
16.04.2026 19:42
16.04.2026 19:43
16.04.2026 19:44
16.04.2026 19:45
16.04.2026 19:47
16.04.2026 19:49