Skip to content

Commit 8390b2f

Browse files
committed
Solution 42 - Python
1 parent 4eedb39 commit 8390b2f

File tree

1 file changed

+333
-0
lines changed

1 file changed

+333
-0
lines changed
Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
# pylint: disable=missing-module-docstring,missing-class-docstring,missing-function-docstring,broad-exception-raised,too-many-arguments
2+
3+
from abc import ABCMeta, abstractmethod
4+
from typing import TypedDict
5+
from random import choice, random
6+
7+
8+
# ---------------------------------------------------------------------------- #
9+
# CLASSES #
10+
# ---------------------------------------------------------------------------- #
11+
12+
13+
# ---------------------------------- Fighter --------------------------------- #
14+
15+
16+
class AbcFighter(metaclass=ABCMeta):
17+
@property
18+
@abstractmethod
19+
def life(self) -> float:
20+
pass
21+
22+
@property
23+
@abstractmethod
24+
def attack(self) -> float:
25+
pass
26+
27+
@property
28+
@abstractmethod
29+
def defense(self) -> float:
30+
pass
31+
32+
@property
33+
@abstractmethod
34+
def name(self) -> str:
35+
pass
36+
37+
@property
38+
@abstractmethod
39+
def speed(self) -> float:
40+
pass
41+
42+
@life.setter
43+
def life(self, new_life: float) -> None:
44+
pass
45+
46+
@abstractmethod
47+
def copy(self) -> "AbcFighter":
48+
pass
49+
50+
51+
class Fighter(AbcFighter):
52+
__life: float
53+
__attack: float
54+
__defense: float
55+
__name: str
56+
__speed: float
57+
58+
def __init__(
59+
self,
60+
*,
61+
life: float = 100,
62+
attack: float,
63+
defense: float,
64+
name: str,
65+
speed: float,
66+
) -> None:
67+
self.__life = life
68+
self.__attack = attack
69+
self.__defense = defense
70+
self.__name = name
71+
self.__speed = speed
72+
73+
@property
74+
def life(self) -> float:
75+
return self.__life
76+
77+
@property
78+
def attack(self) -> float:
79+
return self.__attack
80+
81+
@property
82+
def defense(self) -> float:
83+
return self.__defense
84+
85+
@property
86+
def name(self) -> str:
87+
return self.__name
88+
89+
@property
90+
def speed(self) -> float:
91+
return self.__speed
92+
93+
@life.setter
94+
def life(self, new_life: float) -> None:
95+
self.__life = max(new_life, 0)
96+
97+
def copy(self) -> AbcFighter:
98+
return Fighter(
99+
life=self.__life,
100+
attack=self.__attack,
101+
defense=self.__defense,
102+
name=self.__name,
103+
speed=self.__speed,
104+
)
105+
106+
107+
# -------------------------------- Tournament -------------------------------- #
108+
109+
110+
ShiftInformation = TypedDict(
111+
"ShiftInformation",
112+
{
113+
"attack_damage": float,
114+
"attacker": AbcFighter,
115+
"shift": float,
116+
"victim": AbcFighter,
117+
},
118+
)
119+
120+
Round = TypedDict(
121+
"Round",
122+
{
123+
"info_per_shifts": list[ShiftInformation],
124+
"looser": AbcFighter,
125+
"shifts": float,
126+
"winner": AbcFighter,
127+
},
128+
)
129+
130+
131+
class AbcTournament(metaclass=ABCMeta):
132+
@property
133+
@abstractmethod
134+
def phase(self) -> int:
135+
pass
136+
137+
@property
138+
@abstractmethod
139+
def team_a(self) -> list[AbcFighter]:
140+
pass
141+
142+
@property
143+
@abstractmethod
144+
def team_b(self) -> list[AbcFighter]:
145+
pass
146+
147+
@property
148+
@abstractmethod
149+
def winner(self) -> AbcFighter | None:
150+
pass
151+
152+
@abstractmethod
153+
def execute_next_round(self) -> Round:
154+
pass
155+
156+
157+
class Tournament(AbcTournament):
158+
__pair_of_fighters: list[AbcFighter]
159+
__phase: int
160+
__round: int
161+
__team_a: list[AbcFighter]
162+
__team_b: list[AbcFighter]
163+
__winner: AbcFighter | None
164+
165+
def __init__(self, *, _team_a: list[AbcFighter], _team_b: list[AbcFighter]) -> None:
166+
if len(_team_a) != len(_team_b):
167+
raise Exception("The number of fighters in both teams must be equal")
168+
169+
if len(_team_a) % 2 or len(_team_b) % 2:
170+
raise Exception("The number of fighters in both teams must be even")
171+
172+
self.__pair_of_fighters = []
173+
self.__phase = 0
174+
self.__round = 0
175+
self.__team_a = _team_a
176+
self.__team_b = _team_b
177+
self.__winner = None
178+
179+
self.__set_rnd_pair_of_fighters()
180+
181+
@property
182+
def phase(self) -> int:
183+
return self.__phase
184+
185+
@property
186+
def team_a(self) -> list[AbcFighter]:
187+
return self.__team_a
188+
189+
@property
190+
def team_b(self) -> list[AbcFighter]:
191+
return self.__team_b
192+
193+
@property
194+
def winner(self) -> AbcFighter | None:
195+
return self.__winner
196+
197+
def __set_rnd_pair_of_fighters(self) -> None:
198+
199+
team_a_cpy: list[AbcFighter] = self.__team_a.copy()
200+
team_b_cpy: list[AbcFighter] = self.__team_b.copy()
201+
202+
for _ in range(len(self.__team_a)):
203+
rnd_fighter_a: AbcFighter = choice(seq=team_a_cpy)
204+
rnd_fighter_b: AbcFighter = choice(seq=team_b_cpy)
205+
206+
team_a_cpy.remove(rnd_fighter_a)
207+
team_b_cpy.remove(rnd_fighter_b)
208+
209+
self.__pair_of_fighters.append(rnd_fighter_a)
210+
self.__pair_of_fighters.append(rnd_fighter_b)
211+
212+
def execute_next_round(self) -> Round:
213+
if self.__winner is not None:
214+
raise Exception("The tournament already has a winner")
215+
if len(self.__pair_of_fighters) < 2:
216+
raise Exception("Not enough fighters to execute the next round")
217+
218+
self.__round += 1
219+
offset: int = self.__round - 1
220+
221+
fighter_01: AbcFighter = self.__pair_of_fighters[offset]
222+
fighter_02: AbcFighter = self.__pair_of_fighters[offset + 1]
223+
224+
if fighter_01.speed < fighter_02.speed:
225+
fighter_01, fighter_02 = fighter_02, fighter_01
226+
227+
shifts = 0
228+
info_per_shifts: list[ShiftInformation] = []
229+
230+
while fighter_01.life and fighter_02.life:
231+
attack_damage = 0
232+
233+
if random() > 0.2:
234+
attack: float = fighter_01.attack
235+
defense: float = fighter_02.defense
236+
237+
attack_damage: float = abs(attack - defense)
238+
if defense > attack:
239+
attack_damage = attack_damage * 0.1
240+
241+
fighter_02.life = fighter_02.life - attack_damage
242+
243+
shifts += 1
244+
245+
info_per_shifts.append(
246+
{
247+
"attack_damage": attack_damage,
248+
"attacker": fighter_01.copy(),
249+
"victim": fighter_02.copy(),
250+
"shift": shifts,
251+
}
252+
)
253+
254+
fighter_01, fighter_02 = fighter_02, fighter_01
255+
256+
fighter_01 = self.__pair_of_fighters[offset]
257+
fighter_02 = self.__pair_of_fighters[offset + 1]
258+
259+
looser_index: int = (offset + 1) if fighter_01.life else offset
260+
looser: AbcFighter = self.__pair_of_fighters.pop(looser_index)
261+
262+
if len(self.__pair_of_fighters) < 2:
263+
self.__winner = self.__pair_of_fighters[0]
264+
265+
if self.__round == len(self.__pair_of_fighters):
266+
self.__round = 0
267+
self.__phase += 1
268+
269+
return {
270+
"winner": fighter_01.copy() if fighter_01.life else fighter_02.copy(),
271+
"looser": looser,
272+
"shifts": shifts,
273+
"info_per_shifts": info_per_shifts,
274+
}
275+
276+
277+
# ---------------------------------------------------------------------------- #
278+
# MAIN #
279+
# ---------------------------------------------------------------------------- #
280+
281+
282+
team_a: list[AbcFighter] = [
283+
Fighter(attack=90, defense=80, name="Goku", speed=95),
284+
Fighter(attack=85, defense=75, name="Vegeta", speed=90),
285+
Fighter(attack=70, defense=65, name="Piccolo", speed=80),
286+
Fighter(attack=60, defense=55, name="Krillin", speed=70),
287+
]
288+
289+
team_b: list[AbcFighter] = [
290+
Fighter(attack=88, defense=78, name="Frieza", speed=92),
291+
Fighter(attack=82, defense=72, name="Cell", speed=88),
292+
Fighter(attack=75, defense=65, name="Majin Buu", speed=85),
293+
Fighter(attack=65, defense=60, name="Broly", speed=75),
294+
]
295+
296+
tournament: AbcTournament = Tournament(_team_a=team_a, _team_b=team_b)
297+
298+
rounds: int = 0
299+
300+
while tournament.winner is None:
301+
_round: Round = tournament.execute_next_round()
302+
303+
rounds += 1
304+
305+
print(
306+
f"> Round {rounds} "
307+
f"({_round['winner'].name} vs "
308+
f"{_round['looser'].name})...\n"
309+
)
310+
311+
for shift in _round["info_per_shifts"]:
312+
if shift["attack_damage"] > 0:
313+
print(
314+
f"> Shift {shift['shift']}: "
315+
f"{shift['attacker'].name} attacks "
316+
f"{shift['victim'].name} with an attack damage of ",
317+
shift["attack_damage"],
318+
)
319+
else:
320+
print(
321+
f"> Shift {shift['shift']}: "
322+
f"{shift['attacker'].name} attacks "
323+
f"{shift['victim'].name}, but {shift['victim'].name} "
324+
"evades the attack."
325+
)
326+
327+
print(
328+
f"\n> {_round['winner'].name} wins the fight against "
329+
f"{_round['looser'].name} in {_round['shifts']} shifts!\n"
330+
)
331+
332+
333+
print(f"> The winner of the tournament is {tournament.winner.name}!")

0 commit comments

Comments
 (0)