Enable Javascript in your browser and then refresh this page, for a much enhanced experience.
Clean OOP Solution with full inheritance solution in Clear category for The Warlords by bsquare
from math import floor
def debug(_):
pass
class Weapon:
def __init__(self, health, attack, defense=0, vampirism=0, heal_power=0):
self.health = self.max_health = health
self.attack = attack
self.defense = defense
self.vampirism = vampirism
self.heal_power = heal_power
class Sword(Weapon):
def __init__(self):
super().__init__(health=5, attack=2)
class Shield(Weapon):
def __init__(self):
super().__init__(health=20, attack=-1, defense=2)
class GreatAxe(Weapon):
def __init__(self):
super().__init__(health=-15, attack=5, defense=-2, vampirism=10)
class Katana(Weapon):
def __init__(self):
super().__init__(health=-20, attack=6, defense=-5, vampirism=50)
class MagicWand(Weapon):
def __init__(self):
super().__init__(health=30, attack=3, heal_power=3)
class Warrior:
_max_health = 50
def __init__(self, attack=5):
self._army = None
self._weapons = []
self.health = self._max_health
self._attack = attack
def join_army(self, army):
self._army = army
return self
@property
def max_health(self):
return self._max_health + sum([weapon.max_health for weapon in self._weapons])
@property
def attack(self):
return max(0, self._attack + sum([weapon.attack for weapon in self._weapons]))
@attack.setter
def attack(self, new_attack):
self._attack = new_attack
@property
def is_alive(self):
return self.health > 0
def equip_weapon(self, weapon):
self._weapons.append(weapon)
self.health = max(0, self.health + weapon.health)
def hit(self, targets, specific_damage=None):
# In this implementation targets is the list of all living opponents,
# by default, we still keep figthing only the first one.
target = targets[0]
# Optional specific_damage can be specified (when the lancer is hitting the 'next unit'.
removed_health = target.damage_taken(specific_damage if specific_damage is not None else self.attack)
target.update_health(-removed_health)
return removed_health
def damage_taken(self, attack):
return attack
def heal_received(self, heal_strength):
# An unit can't be healed higher than its maximum health.
return min(self.max_health - self.health, heal_strength)
def update_health(self, update):
self.health = min(self.max_health, self.health + update)
if not self.is_alive and self._army is not None:
self._army.notify_dead_warrior(self)
def __str__(self):
return f"Type={self.__class__.__name__}; Health={self.health}; Attack={self.attack}"
class Knight(Warrior):
def __init__(self):
super().__init__(attack=7)
class Defender(Warrior):
_max_health = 60
def __init__(self, attack=3, defense=2):
super().__init__(attack=attack)
self._defense = defense
@property
def defense(self):
return max(0, self._defense + sum([weapon.defense for weapon in self._weapons]))
def damage_taken(self, attack):
return max(0, attack - self.defense)
def __str__(self):
return super().__str__() + f"; Defense={self.defense}"
class Vampire(Warrior):
_max_health = 40
def __init__(self):
super().__init__(attack=4)
self._vampirism = 50
@property
def vampirism(self):
return max(0, self._vampirism + sum([weapon.vampirism for weapon in self._weapons]))
def hit(self, targets, specific_damage=None):
removed_health = super().hit(targets)
self.update_health(floor(self.vampirism * removed_health / 100))
def __str__(self):
return super().__str__() + f"; Vampirism={self.vampirism}"
class Lancer(Warrior):
def __init__(self):
super().__init__(attack=6)
def hit(self, targets, specific_damage=None):
# The hit is the same than usual, with the first opponent.
removed_health = super().hit(targets)
# If there is another opponent "behind", deals half of the same damage
# Because we call the same method hit, the characteristic of the opponent
# will be taken care (like defense of a Defenseur).
if len(targets) > 1:
additional_damage = removed_health // 2
# Important: the first argument MUST be a list, even if there is only one warrior.
super().hit([targets[1]], specific_damage=additional_damage)
class Healer(Warrior):
_max_health = 60
def __init__(self):
super().__init__(attack=0)
self._heal_power = 2
@property
def heal_power(self):
return max(0, self._heal_power + sum([weapon.heal_power for weapon in self._weapons]))
def heal(self, target):
heal_received = target.heal_received(self.heal_power)
target.update_health(heal_received)
def hit(self, targets, specific_damage=None):
pass
def __str__(self):
return super().__str__() + f"; Heal_power={self.heal_power}"
class Warlord(Defender):
_max_health = 100
def __init__(self):
super().__init__(attack=4, defense=2)
class Army:
army_number = 0
def __init__(self):
Army.army_number += 1
self._army_number = Army.army_number
self.warriors = []
@property
def units(self):
return self.warriors
def add_units(self, cls, unit_count):
# An army can only have one Warlord at most.
if cls == Warlord:
if any(filter(lambda unit: isinstance(unit, Warlord), self.warriors)):
return
unit_count = 1
for _ in range(unit_count):
self.warriors.append(cls().join_army(self))
def __extract_specific_units(self, unit_cls):
return [unit for unit in self.warriors if isinstance(unit, unit_cls)]
def can_move_units(self):
return self.__extract_specific_units(Warlord)
def move_units(self):
# Cleans dead warriors.
self.warriors = self.next_living_warriors()
warlord = self.__extract_specific_units(Warlord)
if not warlord:
return
lancers = self.__extract_specific_units(Lancer)
healers = self.__extract_specific_units(Healer)
others = [unit for unit in self.warriors if not isinstance(unit, Warlord) and not isinstance(unit, Lancer) and not isinstance(unit, Healer)]
new_ordered_units = lancers or others
new_ordered_units[1:1] = healers
if lancers:
new_ordered_units.extend(others)
new_ordered_units.extend(warlord)
self.warriors = new_ordered_units
def notify_dead_warrior(self, warrior):
debug(f'DEAD in Army #{self._army_number}: {warrior}')
self.move_units()
def next_living_warriors(self):
# In this implementation, we return ALL living warriors, in case interaction
# must be done with 'next unit' of the figthing one.
return [unit for unit in self.warriors if unit.is_alive]
def head_unit(self):
living_unit = self.next_living_warriors()
return living_unit[0] if living_unit else None
def head_unit_healer(self):
'''
Returns the second unit if exists, and is a Healer.
'''
living_unit = self.next_living_warriors()
return living_unit[1] if len(living_unit) > 1 and isinstance(living_unit[1], Healer) else None
def is_destroyed(self):
return not [unit for unit in self.warriors if unit.is_alive]
def __str__(self):
return f"Army #{self._army_number}: " + "; ".join([f"({unit})" for unit in self.warriors])
class Battle:
def __init__(self):
pass
def fight(self, army_1: Army, army_2: Army):
army_1.move_units()
army_2.move_units()
while not army_1.is_destroyed() and not army_2.is_destroyed():
turn = 0
while True:
turn += 1
debug(f' {army_1}\n {army_2}')
attacking_army = (army_1, army_2)[not turn%2]
target_army = (army_1, army_2)[turn%2]
attacking_unit = attacking_army.head_unit()
target_unit = target_army.head_unit()
attacking_unit.hit(target_army.next_living_warriors())
attacking_unit_healer = attacking_army.head_unit_healer()
if attacking_unit_healer:
attacking_unit_healer.heal(attacking_unit)
if not target_unit.is_alive:
break
debug(f'FINAL\n {army_1}\n {army_2}')
# Returns True or False, according to living unit in army_1 or not.
return not army_1.is_destroyed()
def straight_fight(self, army_1: Army, army_2: Army):
army_1.move_units()
army_2.move_units()
while not army_1.is_destroyed() and not army_2.is_destroyed():
units_1: list(Warrior) = army_1.next_living_warriors()
units_2: list(Warrior) = army_2.next_living_warriors()
debug(f' {army_1}\n {army_2}')
for unit_1, unit_2 in zip(units_1, units_2):
fight(unit_1, unit_2)
# Returns True or False, according to living unit in army_1 or not.
return not army_1.is_destroyed()
def fight(unit_1, unit_2):
turn = 0
while unit_1.is_alive and unit_2.is_alive:
turn += 1
attacking_unit = (unit_1, unit_2)[not turn%2]
target_unit = (unit_1, unit_2)[turn%2]
attacking_unit.hit([target_unit])
# Returns True or False, according to unit_1 is still alive.
return unit_1.is_alive
Aug. 26, 2019
Comments: