text: from muscular_system import MuscularCondition
  from immune_system import ImmuneCondition
  from sensory_system import SensoryCondition
  from cardio_system import CardioCondition
  from damageable import Owner, Damage, TurnBasedDamage, SeqDamage, SlashingDamage, BleedingDamage, CrushingDamage
  from weapon import Limbs, Sword, NatureWeapon, Weapon, Club
  from armor import LeatherArmor, Skin, Armor, ChainArmor
  from rpg_logs import battle_log, general_log
  from copy import deepcopy
  from itertools import permutations, combinations
  
  class Living(Owner):
      """any living thing can own damageable things
      """
  
      def __init__(self, name='nameless', muscular=1, immune=1, sensory=1, cardio=1, weight=100, perks=[]):
          self.name = name
          self.m = MuscularCondition(muscular)  # strength, mobility
          self.i = ImmuneCondition(immune)  # resistence to discease, poison
          self.s = SensoryCondition(sensory)  # initiative, ranged attack, precision (criticals)
          self.c = CardioCondition(cardio)  # energy, regeneration
          self.weight = weight
          self.perks = perks
          self.turn_based = [self.m, self.i, self.s, self.c]
          self.damageable = [self.m, self.i, self.s, self.c]
          self.natural_weapon = Limbs(self)
          self.natural_armor = Skin(self)
          self.weapon = self.natural_weapon
          self.armor = self.natural_armor
          self.actionable = 0  # 1 is actionable, accumulated every turn based on speed()
          for sub in self.turn_based:
              sub.owner = self.name  # no circular reference, just name
  
      def revive(self):  # only useful if no permanent damage
          self.actionable = 0
          general_log.add("%s's action point reset to: %d" % (self.name, self.actionable))
          for sys in self.damageable:
              sys.magically_restore()
              general_log.add("%s's %s condition restored to: %d" % (self.name, sys.name, sys.cur_value))
      
      def attack(self):
          self.actionable -= 1  # spend the action point on attack
          adjusted = []
          if isinstance(self.weapon.damage, SeqDamage):
              damages = self.weapon.damage.lst
          else:
              damages = [self.weapon.damage]
          for damage in damages:
              dmg = deepcopy(damage)
              #TODO: check weapon type when dealing damages, firearms are not based on muscle condition
              if isinstance(dmg, TurnBasedDamage):  # not amplified by muscle
                  battle_log.add(
                      '%s attacks with [%s] giving turn-based [%s] damage: %d' %
                      (self.name, self.weapon.name, dmg.name, dmg.turn_value)
                  )
              else:  # will be amplified by muscle
                  dmg_value = int(dmg.value * (self.m.value() / 100))
                  battle_log.add(
                      '%s attacks with [%s] giving [%s] damage increased by muscle: %d -> %d' %
                      (self.name, self.weapon.name, dmg.name, dmg.value, dmg_value)
                  )
                  dmg.value = dmg_value
              adjusted.append(dmg)
          if len(adjusted) > 1:
              return SeqDamage(adjusted)
          else:
              return adjusted[0]
  
      def defend(self, damage):
          if self.armor is not None and (not self.is_unconscious()) and (not self.is_disabled()):
              damage = self.armor.resist(damage)
          self.take_damage(damage)
  
      def health(self):
          return self.m.value() * self.s.value() // 100
  
      def energy(self):
          return self.c.value() // 10
  
      def regeneration(self):
          return self.c.value() // 70  # fail to regenrate if cardio condition lower than 70%
  
      def is_dead(self):
          sub_sys_broken = False
          for sys in self.damageable:
              sub_sys_broken = sub_sys_broken or sys.is_broken()
              if sub_sys_broken:
                  break
          return sub_sys_broken
  
      def is_disabled(self):
          return self.m.value() == 0 or self.c.value() == 0
  
      def is_unconscious(self):
          return self.s.value() == 0
  
      def speed(self):
          return self.m.value() / self.weight
  
      def status(self):
          text = ''
          if self.is_dead():
              return 'DEAD'
          if self.is_disabled():
              text += 'D'  # may still use mind power ??
          if self.is_unconscious():
              text += 'U'
          return text
  
      def is_actionable(self):
          return self.actionable >= 1
      
      def take_turn(self):
          self.actionable += self.speed()
          battle_log.add('%s recovers action point to %f' % (self.name, self.actionable))
          for x in self.turn_based:
              x.take_turn()  # sub system may have turn-based effects, such as bleeding
              if x.value() < x.max_value:
                  regen = self.regeneration()
                  x.change_cur_value(regen)
                  battle_log.add('%s [%s] condition regenerated by +%d' % (self.name, x.name, regen))
  
      def take_damage(self, damage):
          if isinstance(damage, SeqDamage):
              damages = damage.lst
          else:
              damages = [damage]  # make it a list
          last_dmg = None
          for dmg in damages:
              if last_dmg is None or last_dmg > 0:  # sequential damage only occurs when last damage > 0
                  if isinstance(dmg, SlashingDamage):
                      self.m.take_damage(dmg)
                  elif isinstance(dmg, BleedingDamage):
                      self.c.take_damage(dmg)
                  elif isinstance(dmg, CrushingDamage):
                      self.s.take_damage(dmg)
                  if isinstance(dmg, TurnBasedDamage):
                      battle_log.add('%s afflicted turn-based %s damage: %d' % (self.name, dmg.name, dmg.turn_value))
                  else:
                      battle_log.add('%s suffered %s damage: %d' % (self.name, dmg.name, dmg.value))
                  last_dmg = dmg.value
  
  
  class Intelligent(Living):
      """Intelligent Living can use (non-natural) weapon and armor
      """
  
      def wield(self, weapon):
          if weapon is None:  # wield nothing == wield natural weapon == wield "limbs"
              if self.weapon is not self.natural_weapon:
                  self.weapon.set_owner(None)
                  self.weight -= self.weapon.weight
                  self.weapon = self.natural_weapon
          else:
              assert isinstance(weapon, Weapon), '%s: must wield instance of Weapon' % self.__class__.__name__
              self.weapon = weapon
              self.weight += weapon.weight
              weapon.set_owner(self)
  
      def wear(self, armor):
          if armor is None:
              if self.armor is not self.natural_armor:
                  self.armor.set_owner(None)
                  self.weight -= self.armor.weight
                  self.armor = self.natural_armor
          else:
              assert isinstance(armor, Armor), '%s: must wear instance of Armor' % self.__class__.__name__
              self.armor = armor
              self.weight += armor.weight
              armor.set_owner(self)
  
  
  if __name__ == "__main__":
      player = Intelligent('Player', 100, 100, 100, 100)
      player.wield(Sword())
      player.wear(ChainArmor())
      ogre = Intelligent('Ogre', 300, 100, 100, 100, weight=400)  # stronger muscle and sensory system, but weight slows it down
      ogre.wield(Club())
      orc = Intelligent('Orc', 150, 100, 120, 100, weight=150)
      orc.wield(Sword())
      def log_condition(being):
          conds = [x.cur_value for x in being.damageable]
          battle_log.add('%s MISC condition %s status (%s)' % (being.name, conds, being.status()))
      turn = 0
      all_actors = [player, ogre, orc]
      results = ""
      for actors in combinations(all_actors, 2):
          while not any(x.is_dead() for x in actors):
              turn += 1
              battle_log.add('\nTurn %d:' % turn)
              while not any(x.is_actionable() for x in actors):  # at least one actionable in high-level turn
                  for actor in actors:
                      actor.take_turn()
              for (attacker, defender) in permutations(actors):
                  if attacker.is_actionable() and (not attacker.is_unconscious()):
                      defender.defend(attacker.attack())
                      log_condition(defender)
                  else:
                      battle_log.add('%s is not actionable and skips this turn' % attacker.name)
              battle_log.flush()
          general_log.add('\n*** Duel ends. Preparing for next...')
          winner = 1 if actors[0].is_dead() else 0
          loser = abs(1 - winner)
          results += '\n%s defeats %s in %d turns' % (actors[winner].name, actors[loser].name, turn)
          for actor in actors:
              actor.revive()
              actor.armor.repair()
          general_log.flush()
          turn = 0  # start another round
      print(results)