| f | class Egg: | f | class Egg: |
| def __init__(self): | | def __init__(self): |
| self._pattern = [] | | self._pattern = [] |
| self._filled = 0.0 | | self._filled = 0.0 |
| self._broken_top = False | | self._broken_top = False |
| self._broken_bottom = False | | self._broken_bottom = False |
| self._tournament = None | | self._tournament = None |
| n | | n | |
| def paint (self, *args): | | def paint (self, *args): |
| to_add = 0.0 | | to_add = 0.0 |
| for hex_color, percentage in args: | | for hex_color, percentage in args: |
| to_add += percentage | | to_add += percentage |
| | | |
| if to_add + self._filled > 100.0: | | if to_add + self._filled > 100.0: |
| raise ValueError("Too much paint! Cannot fill more than 100% of the egg.") | | raise ValueError("Too much paint! Cannot fill more than 100% of the egg.") |
| | | |
| for hex_color, percentage in args: | | for hex_color, percentage in args: |
| n | self._pattern.append((hex_color, percentage)) | n | self._pattern.append((hex_color.upper(), percentage)) |
| | | |
| self._filled += to_add | | self._filled += to_add |
| n | | n | |
| def _color_value(self, hex_color): | | def _color_value(self, hex_color): |
| r = int(hex_color[0:2], 16) | | r = int(hex_color[0:2], 16) |
| g = int(hex_color[2:4], 16) | | g = int(hex_color[2:4], 16) |
| b = int(hex_color[4:6], 16) | | b = int(hex_color[4:6], 16) |
| return r + g + b | | return r + g + b |
| n | | n | |
| def _side_strength(self, top=True): | | def _side_strength(self, top=True): |
| start = 0.0 if top else 50.0 | | start = 0.0 if top else 50.0 |
| end = 50.0 if top else 100.0 | | end = 50.0 if top else 100.0 |
| strength = 0.0 | | strength = 0.0 |
| painted_so_far = 0.0 | | painted_so_far = 0.0 |
| | | |
| for hex_color, percentage in self._pattern: | | for hex_color, percentage in self._pattern: |
| seg_start = painted_so_far | | seg_start = painted_so_far |
| seg_end = painted_so_far + percentage | | seg_end = painted_so_far + percentage |
| overlap_start = max(seg_start, start) | | overlap_start = max(seg_start, start) |
| overlap_end = min(seg_end, end) | | overlap_end = min(seg_end, end) |
| | | |
| if overlap_start < overlap_end: | | if overlap_start < overlap_end: |
| overlap_percentage = overlap_end - overlap_start | | overlap_percentage = overlap_end - overlap_start |
| proportion = overlap_percentage / 50.0 | | proportion = overlap_percentage / 50.0 |
| strength += self._color_value(hex_color) * proportion | | strength += self._color_value(hex_color) * proportion |
| | | |
| painted_so_far = seg_end | | painted_so_far = seg_end |
| | | |
| return strength | | return strength |
| n | | n | |
| def _notify_tournament(self, other, winner, side): | | def _notify_tournament(self, other, winner, side): |
| if (self._tournament is not None and self._tournament is other._tournament): | | if (self._tournament is not None and self._tournament is other._tournament): |
| self._tournament._record_match(self, other, winner, side) | | self._tournament._record_match(self, other, winner, side) |
| n | | n | |
| def _duel(self, other, top): | | def _duel(self, other, top): |
| if top: | | if top: |
| if self._broken_top or other._broken_top: | | if self._broken_top or other._broken_top: |
| raise TypeError("Broken side cannot be used again") | | raise TypeError("Broken side cannot be used again") |
| else: | | else: |
| if self._broken_bottom or other._broken_bottom: | | if self._broken_bottom or other._broken_bottom: |
| raise TypeError("Broken side cannot be used again") | | raise TypeError("Broken side cannot be used again") |
| | | |
| my_strength = self._side_strength(top=top) | | my_strength = self._side_strength(top=top) |
| other_strength = other._side_strength(top=top) | | other_strength = other._side_strength(top=top) |
| | | |
| if my_strength > other_strength: | | if my_strength > other_strength: |
| winner = self | | winner = self |
| loser = other | | loser = other |
| else: | | else: |
| winner = other | | winner = other |
| loser = self | | loser = self |
| | | |
| if top: | | if top: |
| loser._broken_top = True | | loser._broken_top = True |
| else: | | else: |
| loser._broken_bottom = True | | loser._broken_bottom = True |
| | | |
| self._notify_tournament(other, winner, "top" if top else "bottom") | | self._notify_tournament(other, winner, "top" if top else "bottom") |
| return winner | | return winner |
| n | | n | |
| def __mul__(self, other): | | def __mul__(self, other): |
| return self._duel(other, top=True) | | return self._duel(other, top=True) |
| n | | n | |
| def __matmul__(self, other): | | def __matmul__(self, other): |
| return self._duel(other, top=False) | | return self._duel(other, top=False) |
| n | | n | |
| class EggTournament: | | class EggTournament: |
| def __init__(self): | | def __init__(self): |
| self._eggs_by_name = {} | | self._eggs_by_name = {} |
| self._names_by_egg = {} | | self._names_by_egg = {} |
| self._history = [] | | self._history = [] |
| self._wins = {} | | self._wins = {} |
| | | |
| def register(self, egg, name): | | def register(self, egg, name): |
| if egg._tournament is not None: | | if egg._tournament is not None: |
| raise ValueError("An egg cannot be registered in multiple tournaments") | | raise ValueError("An egg cannot be registered in multiple tournaments") |
| | | |
| if not name.isidentifier(): | | if not name.isidentifier(): |
| raise ValueError("Invalid registration name") | | raise ValueError("Invalid registration name") |
| | | |
| if name in self._eggs_by_name: | | if name in self._eggs_by_name: |
| raise ValueError(f"Egg with name {name} has already been registered") | | raise ValueError(f"Egg with name {name} has already been registered") |
| | | |
| self._eggs_by_name[name] = egg | | self._eggs_by_name[name] = egg |
| self._names_by_egg[egg] = name | | self._names_by_egg[egg] = name |
| self._wins[egg] = 0 | | self._wins[egg] = 0 |
| egg._tournament = self | | egg._tournament = self |
| | | |
| def _record_match(self, egg1, egg2, winner, side): | | def _record_match(self, egg1, egg2, winner, side): |
| self._history.append({ | | self._history.append({ |
| "eggs": frozenset((egg1, egg2)), | | "eggs": frozenset((egg1, egg2)), |
| "winner": winner, | | "winner": winner, |
| "side": side, | | "side": side, |
| }) | | }) |
| self._wins[winner] += 1 | | self._wins[winner] += 1 |
| | | |
| def __contains__(self, egg): | | def __contains__(self, egg): |
| return egg in self._names_by_egg | | return egg in self._names_by_egg |
| | | |
| def __getitem__(self, key): | | def __getitem__(self, key): |
| egg1 = egg2 = side = None | | egg1 = egg2 = side = None |
| | | |
| if isinstance(key, tuple) and len(key) == 3: | | if isinstance(key, tuple) and len(key) == 3: |
| egg1, egg2, side = key | | egg1, egg2, side = key |
| elif isinstance(key, slice): | | elif isinstance(key, slice): |
| egg1 = key.start | | egg1 = key.start |
| egg2 = key.stop | | egg2 = key.stop |
| side = key.step | | side = key.step |
| else: | | else: |
| raise KeyError | | raise KeyError |
| | | |
| pair = frozenset((egg1, egg2)) | | pair = frozenset((egg1, egg2)) |
| | | |
| for record in reversed(self._history): | | for record in reversed(self._history): |
| if record["eggs"] == pair and record["side"] == side: | | if record["eggs"] == pair and record["side"] == side: |
| return record["winner"] | | return record["winner"] |
| | | |
| raise KeyError | | raise KeyError |
| | | |
| def _ranking_groups(self): | | def _ranking_groups(self): |
| eggs = list(self._wins.keys()) | | eggs = list(self._wins.keys()) |
| eggs.sort(key=lambda egg: self._wins[egg], reverse=True) | | eggs.sort(key=lambda egg: self._wins[egg], reverse=True) |
| | | |
| groups = {} | | groups = {} |
| current_position = 0 | | current_position = 0 |
| previous_wins = None | | previous_wins = None |
| | | |
| for index, egg in enumerate(eggs): | | for index, egg in enumerate(eggs): |
| wins = self._wins[egg] | | wins = self._wins[egg] |
| | | |
| if previous_wins is None or wins < previous_wins: | | if previous_wins is None or wins < previous_wins: |
| current_position += 1 | | current_position += 1 |
| | | |
| groups.setdefault(current_position, set()).add(egg) | | groups.setdefault(current_position, set()).add(egg) |
| previous_wins = wins | | previous_wins = wins |
| | | |
| return groups | | return groups |
| | | |
| def _position_of_egg(self, target_egg): | | def _position_of_egg(self, target_egg): |
| groups = self._ranking_groups() | | groups = self._ranking_groups() |
| | | |
| for position, eggs in groups.items(): | | for position, eggs in groups.items(): |
| if target_egg in eggs: | | if target_egg in eggs: |
| return position | | return position |
| | | |
| raise AttributeError("Apologies, there is no such egg registered") | | raise AttributeError("Apologies, there is no such egg registered") |
| | | |
| def __rmatmul__(self, position): | | def __rmatmul__(self, position): |
| groups = self._ranking_groups() | | groups = self._ranking_groups() |
| | | |
| if position not in groups: | | if position not in groups: |
| raise IndexError | | raise IndexError |
| | | |
| eggs = groups[position] | | eggs = groups[position] |
| if len(eggs) == 1: | | if len(eggs) == 1: |
| return next(iter(eggs)) | | return next(iter(eggs)) |
| return eggs | | return eggs |
| | | |
| def __getattr__(self, name): | | def __getattr__(self, name): |
| if name not in self._eggs_by_name: | | if name not in self._eggs_by_name: |
| raise AttributeError("Apologies, there is no such egg registered") | | raise AttributeError("Apologies, there is no such egg registered") |
| | | |
| egg = self._eggs_by_name[name] | | egg = self._eggs_by_name[name] |
| n | return { | n | return {"position": self._position_of_egg(egg), "victories": self._wins[egg]} |
| "position": self._position_of_egg(egg), | | |
| "victories": self._wins[egg], | | |
| } | | |
| | | |
| | | |
| | | |
| t | | t | |
15.04.2026 16:04
15.04.2026 16:05
15.04.2026 16:06
15.04.2026 16:07