1from enum import StrEnum
2
3
4class EggSide(StrEnum):
5 top = "top"
6 bottom = "bottom"
7
8
9class Egg:
10 def __init__(self):
11 self.top_coloring = []
12 self.bottom_coloring = []
13 self.broken_sides = []
14
15 def paint(self, *colors):
16 total_coverage = sum(percentage for _, percentage in self.top_coloring + self.bottom_coloring + list(colors))
17 if total_coverage > 100:
18 raise ValueError(f"Cannot overpaint egg, egg is already at {total_coverage}%")
19 for color, percentage in colors:
20 coverage_top = sum(percentage for _, percentage in self.top_coloring)
21 if leftover_coverage_top := 50 - coverage_top:
22 self.top_coloring.append((color, min(leftover_coverage_top, percentage)))
23 if coverage_top + percentage > 50:
24 self.bottom_coloring.append((color, percentage - leftover_coverage_top))
25
26 def _calculate_hardness(self, side):
27 return sum(
28 (int(color[:2], base=16) + int(color[2:4], base=16) + int(color[4:], base=16) + 1) * percentage
29 for color, percentage in getattr(self, f"{side}_coloring")
30 )
31
32 @property
33 def top_hardness(self):
34 return self._calculate_hardness(EggSide.top)
35
36 @property
37 def bottom_hardness(self):
38 return self._calculate_hardness(EggSide.bottom)
39
40 def break_side(self, side):
41 self.broken_sides.append(side)
42
43 def _collide_eggs(self, other, side):
44 if side in self.broken_sides or side in other.broken_sides:
45 raise TypeError("Cannot collide with broken sides")
46 if getattr(self, f"{side}_hardness") > getattr(other, f"{side}_hardness"):
47 winner, loser = self, other
48 else:
49 winner, loser = other, self
50 loser.break_side(side)
51 EggTournament.log_fight(self, other, side, winner)
52 return winner
53
54 def __mul__(self, other):
55 return self._collide_eggs(other, EggSide.top)
56
57 def __matmul__(self, other):
58 return self._collide_eggs(other, EggSide.bottom)
59
60
61class EggTournament:
62 instances = set()
63
64 def __init__(self):
65 self.instances.add(self)
66 self.registered_eggs = {}
67 self.fight_history = {}
68
69 def register(self, egg, name):
70 if name in self.registered_eggs:
71 raise ValueError(f"Egg with name {name} has already been registered")
72 if not name.isidentifier():
73 raise ValueError("Invalid registration name")
74 if any(egg in tournament.registered_eggs.values() for tournament in self.__class__.instances):
75 raise ValueError("An egg cannot be registered in multiple tournaments")
76 self.registered_eggs[name] = egg
77
78 @classmethod
79 def log_fight(cls, first, second, side, winner):
80 for tournament in cls.instances:
81 if first in tournament.registered_eggs.values() and second in tournament.registered_eggs.values():
82 tournament.fight_history[(first, second, side)] = winner
83
84 @property
85 def egg_wins(self):
86 return {egg: list(self.fight_history.values()).count(egg) for egg in self.registered_eggs.values()}
87
88 @property
89 def ranking(self):
90 return [
91 {egg for egg in self.registered_eggs.values() if self.egg_wins[egg] == wins_for_rank}
92 for wins_for_rank in sorted(set(self.egg_wins.values()), reverse=True)
93 ]
94
95 def __getattr__(self, name):
96 try:
97 egg = self.registered_eggs[name]
98 except KeyError:
99 raise AttributeError("Apologies, there is no such egg registered")
100 for rank, eggs in enumerate(self.ranking, start=1):
101 if egg in eggs:
102 return {"position": rank, "victories": self.egg_wins[egg]}
103
104 def __getitem__(self, key):
105 if isinstance(key, slice):
106 key = key.start, key.stop, key.step
107 try:
108 return self.fight_history[key]
109 except KeyError:
110 return self.fight_history[key[1], key[0], key[2]]
111
112 def __rmatmul__(self, position):
113 return eggs_on_rank if len(eggs_on_rank := self.ranking[position - 1]) > 1 else eggs_on_rank.pop()
114
115 def __contains__(self, value):
116 return value in self.registered_eggs.values()
..............................................
----------------------------------------------------------------------
Ran 46 tests in 0.002s
OK
| f | 1 | from enum import StrEnum | f | 1 | from enum import StrEnum |
| 2 | 2 | ||||
| 3 | 3 | ||||
| 4 | class EggSide(StrEnum): | 4 | class EggSide(StrEnum): | ||
| 5 | top = "top" | 5 | top = "top" | ||
| 6 | bottom = "bottom" | 6 | bottom = "bottom" | ||
| 7 | 7 | ||||
| 8 | 8 | ||||
| 9 | class Egg: | 9 | class Egg: | ||
| 10 | def __init__(self): | 10 | def __init__(self): | ||
| 11 | self.top_coloring = [] | 11 | self.top_coloring = [] | ||
| 12 | self.bottom_coloring = [] | 12 | self.bottom_coloring = [] | ||
| 13 | self.broken_sides = [] | 13 | self.broken_sides = [] | ||
| 14 | 14 | ||||
| 15 | def paint(self, *colors): | 15 | def paint(self, *colors): | ||
| t | 16 | coverage = sum(percentage for _, percentage in self.top_coloring + self.bottom_coloring) + sum( | t | 16 | total_coverage = sum(percentage for _, percentage in self.top_coloring + self.bottom_coloring + list(colors)) |
| 17 | percentage for _, percentage in colors | ||||
| 18 | ) | ||||
| 19 | if coverage > 100: | 17 | if total_coverage > 100: | ||
| 20 | raise ValueError(f"Cannot overpaint egg, egg is already at {coverage}%") | 18 | raise ValueError(f"Cannot overpaint egg, egg is already at {total_coverage}%") | ||
| 21 | for color, percentage in colors: | 19 | for color, percentage in colors: | ||
| 22 | coverage_top = sum(percentage for _, percentage in self.top_coloring) | 20 | coverage_top = sum(percentage for _, percentage in self.top_coloring) | ||
| 23 | if leftover_coverage_top := 50 - coverage_top: | 21 | if leftover_coverage_top := 50 - coverage_top: | ||
| 24 | self.top_coloring.append((color, min(leftover_coverage_top, percentage))) | 22 | self.top_coloring.append((color, min(leftover_coverage_top, percentage))) | ||
| 25 | if coverage_top + percentage > 50: | 23 | if coverage_top + percentage > 50: | ||
| 26 | self.bottom_coloring.append((color, percentage - leftover_coverage_top)) | 24 | self.bottom_coloring.append((color, percentage - leftover_coverage_top)) | ||
| 27 | 25 | ||||
| 28 | def _calculate_hardness(self, side): | 26 | def _calculate_hardness(self, side): | ||
| 29 | return sum( | 27 | return sum( | ||
| 30 | (int(color[:2], base=16) + int(color[2:4], base=16) + int(color[4:], base=16) + 1) * percentage | 28 | (int(color[:2], base=16) + int(color[2:4], base=16) + int(color[4:], base=16) + 1) * percentage | ||
| 31 | for color, percentage in getattr(self, f"{side}_coloring") | 29 | for color, percentage in getattr(self, f"{side}_coloring") | ||
| 32 | ) | 30 | ) | ||
| 33 | 31 | ||||
| 34 | @property | 32 | @property | ||
| 35 | def top_hardness(self): | 33 | def top_hardness(self): | ||
| 36 | return self._calculate_hardness(EggSide.top) | 34 | return self._calculate_hardness(EggSide.top) | ||
| 37 | 35 | ||||
| 38 | @property | 36 | @property | ||
| 39 | def bottom_hardness(self): | 37 | def bottom_hardness(self): | ||
| 40 | return self._calculate_hardness(EggSide.bottom) | 38 | return self._calculate_hardness(EggSide.bottom) | ||
| 41 | 39 | ||||
| 42 | def break_side(self, side): | 40 | def break_side(self, side): | ||
| 43 | self.broken_sides.append(side) | 41 | self.broken_sides.append(side) | ||
| 44 | 42 | ||||
| 45 | def _collide_eggs(self, other, side): | 43 | def _collide_eggs(self, other, side): | ||
| 46 | if side in self.broken_sides or side in other.broken_sides: | 44 | if side in self.broken_sides or side in other.broken_sides: | ||
| 47 | raise TypeError("Cannot collide with broken sides") | 45 | raise TypeError("Cannot collide with broken sides") | ||
| 48 | if getattr(self, f"{side}_hardness") > getattr(other, f"{side}_hardness"): | 46 | if getattr(self, f"{side}_hardness") > getattr(other, f"{side}_hardness"): | ||
| 49 | winner, loser = self, other | 47 | winner, loser = self, other | ||
| 50 | else: | 48 | else: | ||
| 51 | winner, loser = other, self | 49 | winner, loser = other, self | ||
| 52 | loser.break_side(side) | 50 | loser.break_side(side) | ||
| 53 | EggTournament.log_fight(self, other, side, winner) | 51 | EggTournament.log_fight(self, other, side, winner) | ||
| 54 | return winner | 52 | return winner | ||
| 55 | 53 | ||||
| 56 | def __mul__(self, other): | 54 | def __mul__(self, other): | ||
| 57 | return self._collide_eggs(other, EggSide.top) | 55 | return self._collide_eggs(other, EggSide.top) | ||
| 58 | 56 | ||||
| 59 | def __matmul__(self, other): | 57 | def __matmul__(self, other): | ||
| 60 | return self._collide_eggs(other, EggSide.bottom) | 58 | return self._collide_eggs(other, EggSide.bottom) | ||
| 61 | 59 | ||||
| 62 | 60 | ||||
| 63 | class EggTournament: | 61 | class EggTournament: | ||
| 64 | instances = set() | 62 | instances = set() | ||
| 65 | 63 | ||||
| 66 | def __init__(self): | 64 | def __init__(self): | ||
| 67 | self.instances.add(self) | 65 | self.instances.add(self) | ||
| 68 | self.registered_eggs = {} | 66 | self.registered_eggs = {} | ||
| 69 | self.fight_history = {} | 67 | self.fight_history = {} | ||
| 70 | 68 | ||||
| 71 | def register(self, egg, name): | 69 | def register(self, egg, name): | ||
| 72 | if name in self.registered_eggs: | 70 | if name in self.registered_eggs: | ||
| 73 | raise ValueError(f"Egg with name {name} has already been registered") | 71 | raise ValueError(f"Egg with name {name} has already been registered") | ||
| 74 | if not name.isidentifier(): | 72 | if not name.isidentifier(): | ||
| 75 | raise ValueError("Invalid registration name") | 73 | raise ValueError("Invalid registration name") | ||
| 76 | if any(egg in tournament.registered_eggs.values() for tournament in self.__class__.instances): | 74 | if any(egg in tournament.registered_eggs.values() for tournament in self.__class__.instances): | ||
| 77 | raise ValueError("An egg cannot be registered in multiple tournaments") | 75 | raise ValueError("An egg cannot be registered in multiple tournaments") | ||
| 78 | self.registered_eggs[name] = egg | 76 | self.registered_eggs[name] = egg | ||
| 79 | 77 | ||||
| 80 | @classmethod | 78 | @classmethod | ||
| 81 | def log_fight(cls, first, second, side, winner): | 79 | def log_fight(cls, first, second, side, winner): | ||
| 82 | for tournament in cls.instances: | 80 | for tournament in cls.instances: | ||
| 83 | if first in tournament.registered_eggs.values() and second in tournament.registered_eggs.values(): | 81 | if first in tournament.registered_eggs.values() and second in tournament.registered_eggs.values(): | ||
| 84 | tournament.fight_history[(first, second, side)] = winner | 82 | tournament.fight_history[(first, second, side)] = winner | ||
| 85 | 83 | ||||
| 86 | @property | 84 | @property | ||
| 87 | def egg_wins(self): | 85 | def egg_wins(self): | ||
| 88 | return {egg: list(self.fight_history.values()).count(egg) for egg in self.registered_eggs.values()} | 86 | return {egg: list(self.fight_history.values()).count(egg) for egg in self.registered_eggs.values()} | ||
| 89 | 87 | ||||
| 90 | @property | 88 | @property | ||
| 91 | def ranking(self): | 89 | def ranking(self): | ||
| 92 | return [ | 90 | return [ | ||
| 93 | {egg for egg in self.registered_eggs.values() if self.egg_wins[egg] == wins_for_rank} | 91 | {egg for egg in self.registered_eggs.values() if self.egg_wins[egg] == wins_for_rank} | ||
| 94 | for wins_for_rank in sorted(set(self.egg_wins.values()), reverse=True) | 92 | for wins_for_rank in sorted(set(self.egg_wins.values()), reverse=True) | ||
| 95 | ] | 93 | ] | ||
| 96 | 94 | ||||
| 97 | def __getattr__(self, name): | 95 | def __getattr__(self, name): | ||
| 98 | try: | 96 | try: | ||
| 99 | egg = self.registered_eggs[name] | 97 | egg = self.registered_eggs[name] | ||
| 100 | except KeyError: | 98 | except KeyError: | ||
| 101 | raise AttributeError("Apologies, there is no such egg registered") | 99 | raise AttributeError("Apologies, there is no such egg registered") | ||
| 102 | for rank, eggs in enumerate(self.ranking, start=1): | 100 | for rank, eggs in enumerate(self.ranking, start=1): | ||
| 103 | if egg in eggs: | 101 | if egg in eggs: | ||
| 104 | return {"position": rank, "victories": self.egg_wins[egg]} | 102 | return {"position": rank, "victories": self.egg_wins[egg]} | ||
| 105 | 103 | ||||
| 106 | def __getitem__(self, key): | 104 | def __getitem__(self, key): | ||
| 107 | if isinstance(key, slice): | 105 | if isinstance(key, slice): | ||
| 108 | key = key.start, key.stop, key.step | 106 | key = key.start, key.stop, key.step | ||
| 109 | try: | 107 | try: | ||
| 110 | return self.fight_history[key] | 108 | return self.fight_history[key] | ||
| 111 | except KeyError: | 109 | except KeyError: | ||
| 112 | return self.fight_history[key[1], key[0], key[2]] | 110 | return self.fight_history[key[1], key[0], key[2]] | ||
| 113 | 111 | ||||
| 114 | def __rmatmul__(self, position): | 112 | def __rmatmul__(self, position): | ||
| 115 | return eggs_on_rank if len(eggs_on_rank := self.ranking[position - 1]) > 1 else eggs_on_rank.pop() | 113 | return eggs_on_rank if len(eggs_on_rank := self.ranking[position - 1]) > 1 else eggs_on_rank.pop() | ||
| 116 | 114 | ||||
| 117 | def __contains__(self, value): | 115 | def __contains__(self, value): | ||
| 118 | return value in self.registered_eggs.values() | 116 | return value in self.registered_eggs.values() |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
| f | 1 | from enum import StrEnum | f | 1 | from enum import StrEnum |
| 2 | 2 | ||||
| 3 | 3 | ||||
| 4 | class EggSide(StrEnum): | 4 | class EggSide(StrEnum): | ||
| 5 | top = "top" | 5 | top = "top" | ||
| 6 | bottom = "bottom" | 6 | bottom = "bottom" | ||
| 7 | 7 | ||||
| 8 | 8 | ||||
| 9 | class Egg: | 9 | class Egg: | ||
| 10 | def __init__(self): | 10 | def __init__(self): | ||
| 11 | self.top_coloring = [] | 11 | self.top_coloring = [] | ||
| 12 | self.bottom_coloring = [] | 12 | self.bottom_coloring = [] | ||
| 13 | self.broken_sides = [] | 13 | self.broken_sides = [] | ||
| 14 | 14 | ||||
| 15 | def paint(self, *colors): | 15 | def paint(self, *colors): | ||
| n | n | 16 | coverage = sum(percentage for _, percentage in self.top_coloring + self.bottom_coloring) + sum( | ||
| 17 | percentage for _, percentage in colors | ||||
| 18 | ) | ||||
| 19 | if coverage > 100: | ||||
| 20 | raise ValueError(f"Cannot overpaint egg, egg is already at {coverage}%") | ||||
| 16 | for color, percentage in colors: | 21 | for color, percentage in colors: | ||
| t | 17 | coverage = sum(percentage for _, percentage in self.top_coloring + self.bottom_coloring) + percentage | t | ||
| 18 | if coverage > 100: | ||||
| 19 | raise ValueError(f"Cannot overpaint egg, egg is already at {coverage}%") | ||||
| 20 | coverage_top = sum(percentage for _, percentage in self.top_coloring) | 22 | coverage_top = sum(percentage for _, percentage in self.top_coloring) | ||
| 21 | if leftover_coverage_top := 50 - coverage_top: | 23 | if leftover_coverage_top := 50 - coverage_top: | ||
| 22 | self.top_coloring.append((color, min(leftover_coverage_top, percentage))) | 24 | self.top_coloring.append((color, min(leftover_coverage_top, percentage))) | ||
| 23 | if coverage_top + percentage > 50: | 25 | if coverage_top + percentage > 50: | ||
| 24 | self.bottom_coloring.append((color, percentage - leftover_coverage_top)) | 26 | self.bottom_coloring.append((color, percentage - leftover_coverage_top)) | ||
| 25 | 27 | ||||
| 26 | def _calculate_hardness(self, side): | 28 | def _calculate_hardness(self, side): | ||
| 27 | return sum( | 29 | return sum( | ||
| 28 | (int(color[:2], base=16) + int(color[2:4], base=16) + int(color[4:], base=16) + 1) * percentage | 30 | (int(color[:2], base=16) + int(color[2:4], base=16) + int(color[4:], base=16) + 1) * percentage | ||
| 29 | for color, percentage in getattr(self, f"{side}_coloring") | 31 | for color, percentage in getattr(self, f"{side}_coloring") | ||
| 30 | ) | 32 | ) | ||
| 31 | 33 | ||||
| 32 | @property | 34 | @property | ||
| 33 | def top_hardness(self): | 35 | def top_hardness(self): | ||
| 34 | return self._calculate_hardness(EggSide.top) | 36 | return self._calculate_hardness(EggSide.top) | ||
| 35 | 37 | ||||
| 36 | @property | 38 | @property | ||
| 37 | def bottom_hardness(self): | 39 | def bottom_hardness(self): | ||
| 38 | return self._calculate_hardness(EggSide.bottom) | 40 | return self._calculate_hardness(EggSide.bottom) | ||
| 39 | 41 | ||||
| 40 | def break_side(self, side): | 42 | def break_side(self, side): | ||
| 41 | self.broken_sides.append(side) | 43 | self.broken_sides.append(side) | ||
| 42 | 44 | ||||
| 43 | def _collide_eggs(self, other, side): | 45 | def _collide_eggs(self, other, side): | ||
| 44 | if side in self.broken_sides or side in other.broken_sides: | 46 | if side in self.broken_sides or side in other.broken_sides: | ||
| 45 | raise TypeError("Cannot collide with broken sides") | 47 | raise TypeError("Cannot collide with broken sides") | ||
| 46 | if getattr(self, f"{side}_hardness") > getattr(other, f"{side}_hardness"): | 48 | if getattr(self, f"{side}_hardness") > getattr(other, f"{side}_hardness"): | ||
| 47 | winner, loser = self, other | 49 | winner, loser = self, other | ||
| 48 | else: | 50 | else: | ||
| 49 | winner, loser = other, self | 51 | winner, loser = other, self | ||
| 50 | loser.break_side(side) | 52 | loser.break_side(side) | ||
| 51 | EggTournament.log_fight(self, other, side, winner) | 53 | EggTournament.log_fight(self, other, side, winner) | ||
| 52 | return winner | 54 | return winner | ||
| 53 | 55 | ||||
| 54 | def __mul__(self, other): | 56 | def __mul__(self, other): | ||
| 55 | return self._collide_eggs(other, EggSide.top) | 57 | return self._collide_eggs(other, EggSide.top) | ||
| 56 | 58 | ||||
| 57 | def __matmul__(self, other): | 59 | def __matmul__(self, other): | ||
| 58 | return self._collide_eggs(other, EggSide.bottom) | 60 | return self._collide_eggs(other, EggSide.bottom) | ||
| 59 | 61 | ||||
| 60 | 62 | ||||
| 61 | class EggTournament: | 63 | class EggTournament: | ||
| 62 | instances = set() | 64 | instances = set() | ||
| 63 | 65 | ||||
| 64 | def __init__(self): | 66 | def __init__(self): | ||
| 65 | self.instances.add(self) | 67 | self.instances.add(self) | ||
| 66 | self.registered_eggs = {} | 68 | self.registered_eggs = {} | ||
| 67 | self.fight_history = {} | 69 | self.fight_history = {} | ||
| 68 | 70 | ||||
| 69 | def register(self, egg, name): | 71 | def register(self, egg, name): | ||
| 70 | if name in self.registered_eggs: | 72 | if name in self.registered_eggs: | ||
| 71 | raise ValueError(f"Egg with name {name} has already been registered") | 73 | raise ValueError(f"Egg with name {name} has already been registered") | ||
| 72 | if not name.isidentifier(): | 74 | if not name.isidentifier(): | ||
| 73 | raise ValueError("Invalid registration name") | 75 | raise ValueError("Invalid registration name") | ||
| 74 | if any(egg in tournament.registered_eggs.values() for tournament in self.__class__.instances): | 76 | if any(egg in tournament.registered_eggs.values() for tournament in self.__class__.instances): | ||
| 75 | raise ValueError("An egg cannot be registered in multiple tournaments") | 77 | raise ValueError("An egg cannot be registered in multiple tournaments") | ||
| 76 | self.registered_eggs[name] = egg | 78 | self.registered_eggs[name] = egg | ||
| 77 | 79 | ||||
| 78 | @classmethod | 80 | @classmethod | ||
| 79 | def log_fight(cls, first, second, side, winner): | 81 | def log_fight(cls, first, second, side, winner): | ||
| 80 | for tournament in cls.instances: | 82 | for tournament in cls.instances: | ||
| 81 | if first in tournament.registered_eggs.values() and second in tournament.registered_eggs.values(): | 83 | if first in tournament.registered_eggs.values() and second in tournament.registered_eggs.values(): | ||
| 82 | tournament.fight_history[(first, second, side)] = winner | 84 | tournament.fight_history[(first, second, side)] = winner | ||
| 83 | 85 | ||||
| 84 | @property | 86 | @property | ||
| 85 | def egg_wins(self): | 87 | def egg_wins(self): | ||
| 86 | return {egg: list(self.fight_history.values()).count(egg) for egg in self.registered_eggs.values()} | 88 | return {egg: list(self.fight_history.values()).count(egg) for egg in self.registered_eggs.values()} | ||
| 87 | 89 | ||||
| 88 | @property | 90 | @property | ||
| 89 | def ranking(self): | 91 | def ranking(self): | ||
| 90 | return [ | 92 | return [ | ||
| 91 | {egg for egg in self.registered_eggs.values() if self.egg_wins[egg] == wins_for_rank} | 93 | {egg for egg in self.registered_eggs.values() if self.egg_wins[egg] == wins_for_rank} | ||
| 92 | for wins_for_rank in sorted(set(self.egg_wins.values()), reverse=True) | 94 | for wins_for_rank in sorted(set(self.egg_wins.values()), reverse=True) | ||
| 93 | ] | 95 | ] | ||
| 94 | 96 | ||||
| 95 | def __getattr__(self, name): | 97 | def __getattr__(self, name): | ||
| 96 | try: | 98 | try: | ||
| 97 | egg = self.registered_eggs[name] | 99 | egg = self.registered_eggs[name] | ||
| 98 | except KeyError: | 100 | except KeyError: | ||
| 99 | raise AttributeError("Apologies, there is no such egg registered") | 101 | raise AttributeError("Apologies, there is no such egg registered") | ||
| 100 | for rank, eggs in enumerate(self.ranking, start=1): | 102 | for rank, eggs in enumerate(self.ranking, start=1): | ||
| 101 | if egg in eggs: | 103 | if egg in eggs: | ||
| 102 | return {"position": rank, "victories": self.egg_wins[egg]} | 104 | return {"position": rank, "victories": self.egg_wins[egg]} | ||
| 103 | 105 | ||||
| 104 | def __getitem__(self, key): | 106 | def __getitem__(self, key): | ||
| 105 | if isinstance(key, slice): | 107 | if isinstance(key, slice): | ||
| 106 | key = key.start, key.stop, key.step | 108 | key = key.start, key.stop, key.step | ||
| 107 | try: | 109 | try: | ||
| 108 | return self.fight_history[key] | 110 | return self.fight_history[key] | ||
| 109 | except KeyError: | 111 | except KeyError: | ||
| 110 | return self.fight_history[key[1], key[0], key[2]] | 112 | return self.fight_history[key[1], key[0], key[2]] | ||
| 111 | 113 | ||||
| 112 | def __rmatmul__(self, position): | 114 | def __rmatmul__(self, position): | ||
| 113 | return eggs_on_rank if len(eggs_on_rank := self.ranking[position - 1]) > 1 else eggs_on_rank.pop() | 115 | return eggs_on_rank if len(eggs_on_rank := self.ranking[position - 1]) > 1 else eggs_on_rank.pop() | ||
| 114 | 116 | ||||
| 115 | def __contains__(self, value): | 117 | def __contains__(self, value): | ||
| 116 | return value in self.registered_eggs.values() | 118 | return value in self.registered_eggs.values() |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
| f | 1 | from enum import StrEnum | f | 1 | from enum import StrEnum |
| 2 | 2 | ||||
| 3 | 3 | ||||
| 4 | class EggSide(StrEnum): | 4 | class EggSide(StrEnum): | ||
| 5 | top = "top" | 5 | top = "top" | ||
| 6 | bottom = "bottom" | 6 | bottom = "bottom" | ||
| 7 | 7 | ||||
| 8 | 8 | ||||
| 9 | class Egg: | 9 | class Egg: | ||
| 10 | def __init__(self): | 10 | def __init__(self): | ||
| 11 | self.top_coloring = [] | 11 | self.top_coloring = [] | ||
| 12 | self.bottom_coloring = [] | 12 | self.bottom_coloring = [] | ||
| 13 | self.broken_sides = [] | 13 | self.broken_sides = [] | ||
| 14 | 14 | ||||
| 15 | def paint(self, *colors): | 15 | def paint(self, *colors): | ||
| 16 | for color, percentage in colors: | 16 | for color, percentage in colors: | ||
| 17 | coverage = sum(percentage for _, percentage in self.top_coloring + self.bottom_coloring) + percentage | 17 | coverage = sum(percentage for _, percentage in self.top_coloring + self.bottom_coloring) + percentage | ||
| 18 | if coverage > 100: | 18 | if coverage > 100: | ||
| 19 | raise ValueError(f"Cannot overpaint egg, egg is already at {coverage}%") | 19 | raise ValueError(f"Cannot overpaint egg, egg is already at {coverage}%") | ||
| t | 20 | t | |||
| 21 | coverage_top = sum(percentage for _, percentage in self.top_coloring) | 20 | coverage_top = sum(percentage for _, percentage in self.top_coloring) | ||
| 22 | if leftover_coverage_top := 50 - coverage_top: | 21 | if leftover_coverage_top := 50 - coverage_top: | ||
| 23 | self.top_coloring.append((color, min(leftover_coverage_top, percentage))) | 22 | self.top_coloring.append((color, min(leftover_coverage_top, percentage))) | ||
| 24 | if coverage_top + percentage > 50: | 23 | if coverage_top + percentage > 50: | ||
| 25 | self.bottom_coloring.append((color, percentage - leftover_coverage_top)) | 24 | self.bottom_coloring.append((color, percentage - leftover_coverage_top)) | ||
| 26 | 25 | ||||
| 27 | def _calculate_hardness(self, side): | 26 | def _calculate_hardness(self, side): | ||
| 28 | return sum( | 27 | return sum( | ||
| 29 | (int(color[:2], base=16) + int(color[2:4], base=16) + int(color[4:], base=16) + 1) * percentage | 28 | (int(color[:2], base=16) + int(color[2:4], base=16) + int(color[4:], base=16) + 1) * percentage | ||
| 30 | for color, percentage in getattr(self, f"{side}_coloring") | 29 | for color, percentage in getattr(self, f"{side}_coloring") | ||
| 31 | ) | 30 | ) | ||
| 32 | 31 | ||||
| 33 | @property | 32 | @property | ||
| 34 | def top_hardness(self): | 33 | def top_hardness(self): | ||
| 35 | return self._calculate_hardness(EggSide.top) | 34 | return self._calculate_hardness(EggSide.top) | ||
| 36 | 35 | ||||
| 37 | @property | 36 | @property | ||
| 38 | def bottom_hardness(self): | 37 | def bottom_hardness(self): | ||
| 39 | return self._calculate_hardness(EggSide.bottom) | 38 | return self._calculate_hardness(EggSide.bottom) | ||
| 40 | 39 | ||||
| 41 | def break_side(self, side): | 40 | def break_side(self, side): | ||
| 42 | self.broken_sides.append(side) | 41 | self.broken_sides.append(side) | ||
| 43 | 42 | ||||
| 44 | def _collide_eggs(self, other, side): | 43 | def _collide_eggs(self, other, side): | ||
| 45 | if side in self.broken_sides or side in other.broken_sides: | 44 | if side in self.broken_sides or side in other.broken_sides: | ||
| 46 | raise TypeError("Cannot collide with broken sides") | 45 | raise TypeError("Cannot collide with broken sides") | ||
| 47 | if getattr(self, f"{side}_hardness") > getattr(other, f"{side}_hardness"): | 46 | if getattr(self, f"{side}_hardness") > getattr(other, f"{side}_hardness"): | ||
| 48 | winner, loser = self, other | 47 | winner, loser = self, other | ||
| 49 | else: | 48 | else: | ||
| 50 | winner, loser = other, self | 49 | winner, loser = other, self | ||
| 51 | loser.break_side(side) | 50 | loser.break_side(side) | ||
| 52 | EggTournament.log_fight(self, other, side, winner) | 51 | EggTournament.log_fight(self, other, side, winner) | ||
| 53 | return winner | 52 | return winner | ||
| 54 | 53 | ||||
| 55 | def __mul__(self, other): | 54 | def __mul__(self, other): | ||
| 56 | return self._collide_eggs(other, EggSide.top) | 55 | return self._collide_eggs(other, EggSide.top) | ||
| 57 | 56 | ||||
| 58 | def __matmul__(self, other): | 57 | def __matmul__(self, other): | ||
| 59 | return self._collide_eggs(other, EggSide.bottom) | 58 | return self._collide_eggs(other, EggSide.bottom) | ||
| 60 | 59 | ||||
| 61 | 60 | ||||
| 62 | class EggTournament: | 61 | class EggTournament: | ||
| 63 | instances = set() | 62 | instances = set() | ||
| 64 | 63 | ||||
| 65 | def __init__(self): | 64 | def __init__(self): | ||
| 66 | self.instances.add(self) | 65 | self.instances.add(self) | ||
| 67 | self.registered_eggs = {} | 66 | self.registered_eggs = {} | ||
| 68 | self.fight_history = {} | 67 | self.fight_history = {} | ||
| 69 | 68 | ||||
| 70 | def register(self, egg, name): | 69 | def register(self, egg, name): | ||
| 71 | if name in self.registered_eggs: | 70 | if name in self.registered_eggs: | ||
| 72 | raise ValueError(f"Egg with name {name} has already been registered") | 71 | raise ValueError(f"Egg with name {name} has already been registered") | ||
| 73 | if not name.isidentifier(): | 72 | if not name.isidentifier(): | ||
| 74 | raise ValueError("Invalid registration name") | 73 | raise ValueError("Invalid registration name") | ||
| 75 | if any(egg in tournament.registered_eggs.values() for tournament in self.__class__.instances): | 74 | if any(egg in tournament.registered_eggs.values() for tournament in self.__class__.instances): | ||
| 76 | raise ValueError("An egg cannot be registered in multiple tournaments") | 75 | raise ValueError("An egg cannot be registered in multiple tournaments") | ||
| 77 | self.registered_eggs[name] = egg | 76 | self.registered_eggs[name] = egg | ||
| 78 | 77 | ||||
| 79 | @classmethod | 78 | @classmethod | ||
| 80 | def log_fight(cls, first, second, side, winner): | 79 | def log_fight(cls, first, second, side, winner): | ||
| 81 | for tournament in cls.instances: | 80 | for tournament in cls.instances: | ||
| 82 | if first in tournament.registered_eggs.values() and second in tournament.registered_eggs.values(): | 81 | if first in tournament.registered_eggs.values() and second in tournament.registered_eggs.values(): | ||
| 83 | tournament.fight_history[(first, second, side)] = winner | 82 | tournament.fight_history[(first, second, side)] = winner | ||
| 84 | 83 | ||||
| 85 | @property | 84 | @property | ||
| 86 | def egg_wins(self): | 85 | def egg_wins(self): | ||
| 87 | return {egg: list(self.fight_history.values()).count(egg) for egg in self.registered_eggs.values()} | 86 | return {egg: list(self.fight_history.values()).count(egg) for egg in self.registered_eggs.values()} | ||
| 88 | 87 | ||||
| 89 | @property | 88 | @property | ||
| 90 | def ranking(self): | 89 | def ranking(self): | ||
| 91 | return [ | 90 | return [ | ||
| 92 | {egg for egg in self.registered_eggs.values() if self.egg_wins[egg] == wins_for_rank} | 91 | {egg for egg in self.registered_eggs.values() if self.egg_wins[egg] == wins_for_rank} | ||
| 93 | for wins_for_rank in sorted(set(self.egg_wins.values()), reverse=True) | 92 | for wins_for_rank in sorted(set(self.egg_wins.values()), reverse=True) | ||
| 94 | ] | 93 | ] | ||
| 95 | 94 | ||||
| 96 | def __getattr__(self, name): | 95 | def __getattr__(self, name): | ||
| 97 | try: | 96 | try: | ||
| 98 | egg = self.registered_eggs[name] | 97 | egg = self.registered_eggs[name] | ||
| 99 | except KeyError: | 98 | except KeyError: | ||
| 100 | raise AttributeError("Apologies, there is no such egg registered") | 99 | raise AttributeError("Apologies, there is no such egg registered") | ||
| 101 | for rank, eggs in enumerate(self.ranking, start=1): | 100 | for rank, eggs in enumerate(self.ranking, start=1): | ||
| 102 | if egg in eggs: | 101 | if egg in eggs: | ||
| 103 | return {"position": rank, "victories": self.egg_wins[egg]} | 102 | return {"position": rank, "victories": self.egg_wins[egg]} | ||
| 104 | 103 | ||||
| 105 | def __getitem__(self, key): | 104 | def __getitem__(self, key): | ||
| 106 | if isinstance(key, slice): | 105 | if isinstance(key, slice): | ||
| 107 | key = key.start, key.stop, key.step | 106 | key = key.start, key.stop, key.step | ||
| 108 | try: | 107 | try: | ||
| 109 | return self.fight_history[key] | 108 | return self.fight_history[key] | ||
| 110 | except KeyError: | 109 | except KeyError: | ||
| 111 | return self.fight_history[key[1], key[0], key[2]] | 110 | return self.fight_history[key[1], key[0], key[2]] | ||
| 112 | 111 | ||||
| 113 | def __rmatmul__(self, position): | 112 | def __rmatmul__(self, position): | ||
| 114 | return eggs_on_rank if len(eggs_on_rank := self.ranking[position - 1]) > 1 else eggs_on_rank.pop() | 113 | return eggs_on_rank if len(eggs_on_rank := self.ranking[position - 1]) > 1 else eggs_on_rank.pop() | ||
| 115 | 114 | ||||
| 116 | def __contains__(self, value): | 115 | def __contains__(self, value): | ||
| 117 | return value in self.registered_eggs.values() | 116 | return value in self.registered_eggs.values() |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
| f | 1 | from enum import StrEnum | f | 1 | from enum import StrEnum |
| 2 | 2 | ||||
| 3 | 3 | ||||
| 4 | class EggSide(StrEnum): | 4 | class EggSide(StrEnum): | ||
| 5 | top = "top" | 5 | top = "top" | ||
| 6 | bottom = "bottom" | 6 | bottom = "bottom" | ||
| 7 | 7 | ||||
| 8 | 8 | ||||
| 9 | class Egg: | 9 | class Egg: | ||
| 10 | def __init__(self): | 10 | def __init__(self): | ||
| 11 | self.top_coloring = [] | 11 | self.top_coloring = [] | ||
| 12 | self.bottom_coloring = [] | 12 | self.bottom_coloring = [] | ||
| 13 | self.broken_sides = [] | 13 | self.broken_sides = [] | ||
| 14 | 14 | ||||
| 15 | def paint(self, *colors): | 15 | def paint(self, *colors): | ||
| 16 | for color, percentage in colors: | 16 | for color, percentage in colors: | ||
| 17 | coverage = sum(percentage for _, percentage in self.top_coloring + self.bottom_coloring) + percentage | 17 | coverage = sum(percentage for _, percentage in self.top_coloring + self.bottom_coloring) + percentage | ||
| 18 | if coverage > 100: | 18 | if coverage > 100: | ||
| 19 | raise ValueError(f"Cannot overpaint egg, egg is already at {coverage}%") | 19 | raise ValueError(f"Cannot overpaint egg, egg is already at {coverage}%") | ||
| 20 | 20 | ||||
| 21 | coverage_top = sum(percentage for _, percentage in self.top_coloring) | 21 | coverage_top = sum(percentage for _, percentage in self.top_coloring) | ||
| 22 | if leftover_coverage_top := 50 - coverage_top: | 22 | if leftover_coverage_top := 50 - coverage_top: | ||
| 23 | self.top_coloring.append((color, min(leftover_coverage_top, percentage))) | 23 | self.top_coloring.append((color, min(leftover_coverage_top, percentage))) | ||
| 24 | if coverage_top + percentage > 50: | 24 | if coverage_top + percentage > 50: | ||
| 25 | self.bottom_coloring.append((color, percentage - leftover_coverage_top)) | 25 | self.bottom_coloring.append((color, percentage - leftover_coverage_top)) | ||
| 26 | 26 | ||||
| 27 | def _calculate_hardness(self, side): | 27 | def _calculate_hardness(self, side): | ||
| 28 | return sum( | 28 | return sum( | ||
| 29 | (int(color[:2], base=16) + int(color[2:4], base=16) + int(color[4:], base=16) + 1) * percentage | 29 | (int(color[:2], base=16) + int(color[2:4], base=16) + int(color[4:], base=16) + 1) * percentage | ||
| 30 | for color, percentage in getattr(self, f"{side}_coloring") | 30 | for color, percentage in getattr(self, f"{side}_coloring") | ||
| 31 | ) | 31 | ) | ||
| 32 | 32 | ||||
| 33 | @property | 33 | @property | ||
| 34 | def top_hardness(self): | 34 | def top_hardness(self): | ||
| 35 | return self._calculate_hardness(EggSide.top) | 35 | return self._calculate_hardness(EggSide.top) | ||
| 36 | 36 | ||||
| 37 | @property | 37 | @property | ||
| 38 | def bottom_hardness(self): | 38 | def bottom_hardness(self): | ||
| 39 | return self._calculate_hardness(EggSide.bottom) | 39 | return self._calculate_hardness(EggSide.bottom) | ||
| 40 | 40 | ||||
| 41 | def break_side(self, side): | 41 | def break_side(self, side): | ||
| 42 | self.broken_sides.append(side) | 42 | self.broken_sides.append(side) | ||
| 43 | 43 | ||||
| 44 | def _collide_eggs(self, other, side): | 44 | def _collide_eggs(self, other, side): | ||
| 45 | if side in self.broken_sides or side in other.broken_sides: | 45 | if side in self.broken_sides or side in other.broken_sides: | ||
| 46 | raise TypeError("Cannot collide with broken sides") | 46 | raise TypeError("Cannot collide with broken sides") | ||
| 47 | if getattr(self, f"{side}_hardness") > getattr(other, f"{side}_hardness"): | 47 | if getattr(self, f"{side}_hardness") > getattr(other, f"{side}_hardness"): | ||
| 48 | winner, loser = self, other | 48 | winner, loser = self, other | ||
| 49 | else: | 49 | else: | ||
| 50 | winner, loser = other, self | 50 | winner, loser = other, self | ||
| 51 | loser.break_side(side) | 51 | loser.break_side(side) | ||
| 52 | EggTournament.log_fight(self, other, side, winner) | 52 | EggTournament.log_fight(self, other, side, winner) | ||
| 53 | return winner | 53 | return winner | ||
| 54 | 54 | ||||
| 55 | def __mul__(self, other): | 55 | def __mul__(self, other): | ||
| 56 | return self._collide_eggs(other, EggSide.top) | 56 | return self._collide_eggs(other, EggSide.top) | ||
| 57 | 57 | ||||
| 58 | def __matmul__(self, other): | 58 | def __matmul__(self, other): | ||
| 59 | return self._collide_eggs(other, EggSide.bottom) | 59 | return self._collide_eggs(other, EggSide.bottom) | ||
| 60 | 60 | ||||
| 61 | 61 | ||||
| 62 | class EggTournament: | 62 | class EggTournament: | ||
| 63 | instances = set() | 63 | instances = set() | ||
| 64 | 64 | ||||
| 65 | def __init__(self): | 65 | def __init__(self): | ||
| 66 | self.instances.add(self) | 66 | self.instances.add(self) | ||
| 67 | self.registered_eggs = {} | 67 | self.registered_eggs = {} | ||
| 68 | self.fight_history = {} | 68 | self.fight_history = {} | ||
| 69 | 69 | ||||
| 70 | def register(self, egg, name): | 70 | def register(self, egg, name): | ||
| 71 | if name in self.registered_eggs: | 71 | if name in self.registered_eggs: | ||
| t | 72 | raise ValueError("Egg with name the_monster has already been registered") | t | 72 | raise ValueError(f"Egg with name {name} has already been registered") |
| 73 | if not name.isidentifier(): | 73 | if not name.isidentifier(): | ||
| 74 | raise ValueError("Invalid registration name") | 74 | raise ValueError("Invalid registration name") | ||
| 75 | if any(egg in tournament.registered_eggs.values() for tournament in self.__class__.instances): | 75 | if any(egg in tournament.registered_eggs.values() for tournament in self.__class__.instances): | ||
| 76 | raise ValueError("An egg cannot be registered in multiple tournaments") | 76 | raise ValueError("An egg cannot be registered in multiple tournaments") | ||
| 77 | self.registered_eggs[name] = egg | 77 | self.registered_eggs[name] = egg | ||
| 78 | 78 | ||||
| 79 | @classmethod | 79 | @classmethod | ||
| 80 | def log_fight(cls, first, second, side, winner): | 80 | def log_fight(cls, first, second, side, winner): | ||
| 81 | for tournament in cls.instances: | 81 | for tournament in cls.instances: | ||
| 82 | if first in tournament.registered_eggs.values() and second in tournament.registered_eggs.values(): | 82 | if first in tournament.registered_eggs.values() and second in tournament.registered_eggs.values(): | ||
| 83 | tournament.fight_history[(first, second, side)] = winner | 83 | tournament.fight_history[(first, second, side)] = winner | ||
| 84 | 84 | ||||
| 85 | @property | 85 | @property | ||
| 86 | def egg_wins(self): | 86 | def egg_wins(self): | ||
| 87 | return {egg: list(self.fight_history.values()).count(egg) for egg in self.registered_eggs.values()} | 87 | return {egg: list(self.fight_history.values()).count(egg) for egg in self.registered_eggs.values()} | ||
| 88 | 88 | ||||
| 89 | @property | 89 | @property | ||
| 90 | def ranking(self): | 90 | def ranking(self): | ||
| 91 | return [ | 91 | return [ | ||
| 92 | {egg for egg in self.registered_eggs.values() if self.egg_wins[egg] == wins_for_rank} | 92 | {egg for egg in self.registered_eggs.values() if self.egg_wins[egg] == wins_for_rank} | ||
| 93 | for wins_for_rank in sorted(set(self.egg_wins.values()), reverse=True) | 93 | for wins_for_rank in sorted(set(self.egg_wins.values()), reverse=True) | ||
| 94 | ] | 94 | ] | ||
| 95 | 95 | ||||
| 96 | def __getattr__(self, name): | 96 | def __getattr__(self, name): | ||
| 97 | try: | 97 | try: | ||
| 98 | egg = self.registered_eggs[name] | 98 | egg = self.registered_eggs[name] | ||
| 99 | except KeyError: | 99 | except KeyError: | ||
| 100 | raise AttributeError("Apologies, there is no such egg registered") | 100 | raise AttributeError("Apologies, there is no such egg registered") | ||
| 101 | for rank, eggs in enumerate(self.ranking, start=1): | 101 | for rank, eggs in enumerate(self.ranking, start=1): | ||
| 102 | if egg in eggs: | 102 | if egg in eggs: | ||
| 103 | return {"position": rank, "victories": self.egg_wins[egg]} | 103 | return {"position": rank, "victories": self.egg_wins[egg]} | ||
| 104 | 104 | ||||
| 105 | def __getitem__(self, key): | 105 | def __getitem__(self, key): | ||
| 106 | if isinstance(key, slice): | 106 | if isinstance(key, slice): | ||
| 107 | key = key.start, key.stop, key.step | 107 | key = key.start, key.stop, key.step | ||
| 108 | try: | 108 | try: | ||
| 109 | return self.fight_history[key] | 109 | return self.fight_history[key] | ||
| 110 | except KeyError: | 110 | except KeyError: | ||
| 111 | return self.fight_history[key[1], key[0], key[2]] | 111 | return self.fight_history[key[1], key[0], key[2]] | ||
| 112 | 112 | ||||
| 113 | def __rmatmul__(self, position): | 113 | def __rmatmul__(self, position): | ||
| 114 | return eggs_on_rank if len(eggs_on_rank := self.ranking[position - 1]) > 1 else eggs_on_rank.pop() | 114 | return eggs_on_rank if len(eggs_on_rank := self.ranking[position - 1]) > 1 else eggs_on_rank.pop() | ||
| 115 | 115 | ||||
| 116 | def __contains__(self, value): | 116 | def __contains__(self, value): | ||
| 117 | return value in self.registered_eggs.values() | 117 | return value in self.registered_eggs.values() |
| Legends | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
| |||||||||
12.04.2026 22:01