1from collections import defaultdict
2from keyword import iskeyword
3
4
5class Egg:
6 def __init__(self):
7 self.hex_colors_top = []
8 self.hex_colors_bottom = []
9 self.percentage = 0
10 self.top_broken = False
11 self.bottom_broken = False
12 self.tournament = None
13
14 def paint(self, *args):
15 to_add = 0.0
16
17 for color, percentage in args:
18 to_add += percentage
19
20 if to_add + self.percentage > 100:
21 raise ValueError("Too much paint")
22
23 for color, percentage in args:
24 color = color.upper()
25
26 if self.percentage >= 50:
27 self.hex_colors_bottom.append((color, percentage))
28 else:
29 if percentage + self.percentage <= 50:
30 self.hex_colors_top.append((color, percentage))
31 else:
32 top_percentage = 50 - self.percentage
33 self.hex_colors_top.append((color, top_percentage))
34 self.hex_colors_bottom.append((color, percentage - top_percentage))
35
36 self.percentage += percentage
37
38 def __color_val(self, color):
39 r = int(color[0:2], 16)
40 g = int(color[2:4], 16)
41 b = int(color[4:], 16)
42 return r + g + b
43
44 def __damage(self, colors):
45 total_damage = 0.0
46 painted = 0
47
48 for color, percentage in colors:
49 total_damage += (percentage / 50.0) * self.__color_val(color)
50 painted += percentage
51
52 return (total_damage, painted)
53
54 @property
55 def top_damage(self):
56 return self.__damage(self.hex_colors_top)
57
58 @property
59 def bottom_damage(self):
60 return self.__damage(self.hex_colors_bottom)
61
62 def __mul__(self, other):
63 if not isinstance(other, Egg):
64 return NotImplemented
65
66 if self.top_broken or other.top_broken:
67 raise TypeError("Already broken")
68
69 if self.top_damage > other.top_damage:
70 other.top_broken = True
71 winner = self
72 loser = other
73 else:
74 self.top_broken = True
75 winner = other
76 loser = self
77
78 if self.tournament and self.tournament is other.tournament:
79 self.tournament.record(winner, loser, "top")
80 self.tournament.add_point(winner)
81 return winner
82
83 def __matmul__(self, other):
84 if not isinstance(other, Egg):
85 return NotImplemented
86
87 if self.bottom_broken or other.bottom_broken:
88 raise TypeError("Already broken")
89
90 if self.bottom_damage > other.bottom_damage:
91 other.bottom_broken = True
92 winner = self
93 loser = other
94 else:
95 self.bottom_broken = True
96 winner = other
97 loser = self
98
99 if self.tournament and self.tournament is other.tournament:
100 self.tournament.record(winner, loser, "bottom")
101 self.tournament.add_point(winner)
102
103 return winner
104
105
106class EggTournament:
107
108 def __init__(self):
109 self.registered = {}
110 self.history = {}
111 self.points = defaultdict(int)
112
113 def register(self, egg, nickname):
114 if not isinstance(egg, Egg):
115 return NotImplemented
116
117 if egg.tournament is not None:
118 raise ValueError("An egg cannot be registered in multiple tournaments")
119
120 if not nickname.isidentifier() or iskeyword(nickname):
121 raise ValueError("Invalid registration name")
122
123 if nickname in self.registered:
124 raise ValueError(f"Egg with name {nickname} has already been registered")
125
126 self.registered[nickname] = egg
127 egg.tournament = self
128
129 def record(self, winner, loser, side):
130 self.history[(frozenset((winner, loser)), side)] = winner
131
132 def __getitem__(self, key):
133 if isinstance(key, tuple) and len(key) == 3:
134 egg1, egg2, side = key
135 elif isinstance(key, slice):
136 egg1, egg2, side = key.start, key.stop, key.step
137 else:
138 raise KeyError
139
140 key = (frozenset((egg1, egg2)), side)
141
142 if key not in self.history:
143 raise KeyError
144
145 return self.history[key]
146
147 def add_point(self, egg):
148 if not isinstance(egg, Egg):
149 return NotImplemented
150 self.points[egg] += 1
151
152 def __rmatmul__(self, place):
153 grouped_by_points = defaultdict(set)
154
155 for egg in self.registered.values():
156 grouped_by_points[self.points[egg]].add(egg)
157
158 ranked_points = sorted(grouped_by_points.keys(), reverse=True)
159
160 if place < 1 or place > len(ranked_points):
161 raise IndexError()
162
163 result = grouped_by_points[ranked_points[place - 1]]
164
165 if len(result) == 1:
166 return next(iter(result))
167 return result
168
169 def _position(self, egg):
170 ranked_points = set(self.points.values())
171 ranked_points.add(0)
172 ranked_points = list(sorted(ranked_points, reverse=True))
173
174 egg_points = self.points[egg]
175
176 place = 1
177 for pt in ranked_points:
178 if pt == egg_points:
179 return place
180 place += 1
181
182 def __getattr__(self, nickname):
183 if nickname not in self.registered:
184 raise AttributeError("Apologies, there is no such egg registered")
185
186 egg = self.registered[nickname]
187 return {"position": self._position(egg), "victories": self.points[egg]}
188
189 def __contains__(self, egg):
190 return egg in self.registered.values()
..............................................
----------------------------------------------------------------------
Ran 46 tests in 0.001s
OK
17.04.2026 21:53
17.04.2026 21:53
17.04.2026 21:55
17.04.2026 21:56
17.04.2026 21:56
17.04.2026 21:57
17.04.2026 21:59