• Test 21 Issues

Question related to mission Straight Fight

 

Somebody else posted that they're having issues with Test 21, but their issue seems different. I suppose I just coincidentally also have an issue with Test 21?

Anyway, I have no idea what's wrong. Army 2 is supposed to win, but Army 1 seems to win by a landslide.

Attemps

I have tried the following:

  • Having healers heal the left unit.
  • Having healers heal the right unit.
  • Having healers heal both the left and the right units.
  • Having healers heal nobody.
  • Having a fighter's max_health limit vampirism, as well as the opposite.
  • Having the lancer's damage calculation occur before the protector's defense (which fails other missions anyway)
  • Probably some other stuff; I don't remember.

Code

Below is my code, with Test #21 ready for testing. As a note, because of how many times I print to console for this, the assert error seems to splice itself into the middle of them all, not at the end; the error is there, you just need to scroll.

If you've completed this one, could you see if your battle matches mine? I'd love to know what's incorrect about my battle, since it appears fine.

I've commented it the best I can. Please help me out, I would appreciate anything at this point.

class FighterClass(object):
    def __init__(self, health, attack=0, defense=0):
        self._health = health
        self.max_health = health
        self.attack = attack
        self.defense = defense

    @property # Get the name of the fighter's class. Helps with debugging.
    def unit_class(self): return type(self).__name__
    @property # Decide whether the fighter is alive.
    def is_alive(self): return self.health > 0

    @property # Get the figher's health.
    def health(self):
        return self._health
    @health.setter # Set the fighter's health.
    def health(self, h):
        # Health cannot exceed maximum health.
        self._health = int(min(h, self.max_health))

    def hit(self, targ):
        # Damage cannot be lower than 0 (after calculating defense).
        damage = max(self.attack - targ.defense, 0)
        # Damage cannot be above the target's health, as that may mess with subclass features.
        # For example, Vampire should heal based on the damage they actually deal, even if they kill their target.
        damage = self.post_hit(min(damage, targ.health), targ)
        targ.health -= min(damage, targ.health)

    def post_hit(self, damage, targ) -> int:
        # Custom function for subclasses with special features.
        return damage


# Ideally, I would make everything a subclass of FighterClass.
# However, this test DEMANDS that Warrior is the parent class, even after the first mission.
# Thus, Warrior has to act like some kind of class-passer for other subclasses.
class Warrior(FighterClass):
    def __init__(self, health=50, attack=5, defense=0):
        super().__init__(health, attack, defense)

#class Knight(FighterClass):
class Knight(Warrior):
    def __init__(self):
        super().__init__(50, 7)

#class Defender(FighterClass):
class Defender(Warrior):
    def __init__(self):
        super().__init__(60, 3, 2)

#class Vampire(FighterClass):
class Vampire(Warrior):
    def __init__(self):
        super().__init__(40, 4)
        self.vampirism = 50
    def post_hit(self, damage, targ):
        # The Vampire deals 150% damage.
        self.health += damage * self.vampirism / 100
        return damage

#class Lancer(FighterClass):
class Lancer(Warrior):
    def __init__(self):
        super().__init__(50, 6)
        self.attack = 6
    def post_hit(self, damage, targ):
        # The Lancer deals 150% damage.
        return damage * 1.5

#class Healer(FighterClass):
class Healer(Warrior):
    def __init__(self):
        super().__init__(60, 0)
        # Healers appear to be named "priest", so I guess this is a cleric situation. So, "self.faith"?
        self.faith = 2
    def post_hit(self, damage, targ):
        # To prevent a soft-lock, the Healer's head explodes in sheer astonishment when they encounter another Healer.
        # I don't think the mission allows this to happen anyway, but whatever.
        if isinstance(targ, Healer):
            damage = targ.health
            self.health = 0
        return damage
    def heal(self, targ):
        # A dead Healer shouldn't heal. A dead target shouldn't be healed.
        if not self.is_alive or not targ.is_alive: return
        # Heal the target.
        targ.health += self.faith




class Army:
    def __init__(self):
        self.army = []
    def add_units(self, unitClass, quantity):
        self.army.extend([unitClass() for i in range(0, quantity)])




class Battle:
    def __init__(self, debug=False):
        self.debug = debug

    def fight(self, army1, army2):
        # Debug printout.
        if self.debug: print('\n\nBattle Between Armies')
        while any(army1.army) and any(army2.army):
            # Check if a healer is behind the fighter.
            h1, h2 = None, None
            if len(army1.army) > 1 and isinstance(army1.army[1], Healer): h1 = army1.army[1]
            if len(army2.army) > 1 and isinstance(army2.army[1], Healer): h2 = army2.army[1]
            # Fight!
            fight(army1.army[0], army2.army[0], h1, h2, self.debug)
            # Remove the dead bodies.
            if not army1.army[0].is_alive: del army1.army[0]
            if not army2.army[0].is_alive: del army2.army[0]
        return any(army1.army)

    def straight_fight(self, army1, army2, test_name='Armies'):
        # Debug printout.
        if self.debug: print('\n\nStraight Battle Between Armies')
        while any(army1.army) and any(army2.army):
            l1, l2 = len(army1.army), len(army2.army)
            num = min(l1, l2)
            for i in range(0, num):
                # Check if a healer is to the left of the fighter???
                h1, h2 = None, None
                if l1-1 > i and isinstance(army1.army[i+1], Healer): h1 = army1.army[i+1]
                if l2-1 > i and isinstance(army2.army[i+1], Healer): h2 = army2.army[i+1]
                # Fight!
                temp = fight(army1.army[i], army2.army[i], h1, h2, self.debug)
            # Debug printout.
            if self.debug:
                # Victorious fighters will be in uppercase.
                print('')
                print('Army 1:', [u.unit_class[0:4].upper() if u.is_alive else u.unit_class[0:4].lower() for u in army1.army])
                print('Army 2:', [u.unit_class[0:4].upper() if u.is_alive else u.unit_class[0:4].lower() for u in army2.army])
            # Remove the dead bodies.
            army1.army = [*filter(lambda u: u.is_alive, army1.army)]
            army2.army = [*filter(lambda u: u.is_alive, army2.army)]
        return any(army1.army)




def fight(unit_1, unit_2, healer_1=None, healer_2=None, debug=False):
    a, b = unit_1, unit_2
    ha, hb = healer_1, healer_2
    # Debug printout.
    if debug:
        print('\n{0} ({1}/{2}) vs {3} ({4}/{5})'.format(unit_1.unit_class,unit_1.health,unit_1.max_health,unit_2.unit_class,unit_2.health,unit_2.max_health))
        print('{0} Attack: {1} Defense: {2}'.format(unit_1.unit_class,unit_1.attack,unit_1.defense))
        print('{0} Attack: {1} Defense: {2}'.format(unit_2.unit_class,unit_2.attack,unit_2.defense))
    while unit_1.is_alive and unit_2.is_alive:
        # Debug printout.
        if debug:
            aH = a.health
            bH = b.health
        # Attack!
        a.hit(b)
        # Get healed if a healer is available.
        if ha: ha.heal(a)
        # Debug printout.
        if debug:
            dD = bH - b.health
            dH = a.health - aH
            ss = ''
            if dH > 0: ss = ' and healed ' + str(dH)
            print('{0} ({1}/{2}) dealt {3} damage to {4} ({5}/{6}){7}.'.format(type(a).__name__,a.health,a.max_health,dD,type(b).__name__,b.health,b.max_health,ss))
        # Switch turns.
        a,b = b,a
        ha,hb = hb,ha
    return unit_1.is_alive




if __name__ == '__main__':
    # Test No. 21
    army_1 = Army()
    army_2 = Army()
    army_1.add_units(Lancer, 7)
    army_1.add_units(Vampire, 3)
    army_1.add_units(Healer, 1)
    army_1.add_units(Warrior, 4)
    army_1.add_units(Healer, 1)
    army_1.add_units(Defender, 2)
    army_2.add_units(Warrior, 4)
    army_2.add_units(Defender, 4)
    army_2.add_units(Healer, 1)
    army_2.add_units(Vampire, 6)
    army_2.add_units(Lancer, 4)
    battle = Battle(True)
    assert battle.straight_fight(army_1, army_2) == False

Evaluation

I even went through and painstakingly checked the battle for mathematical issues, which goes as follows:

Round 1

  • Lanc a1 (50hp) vs Warr b1 (50hp): Lancer deals 9 and Warrior deals 5; Lanc a1 (25hp) wins.
  • Lanc a2 (50hp) vs Warr b2 (50hp): Lancer deals 9 and Warrior deals 5; Lanc a2 (25hp) wins.
  • Lanc a3 (50hp) vs Warr b3 (50hp): Lancer deals 9 and Warrior deals 5; Lanc a3 (25hp) wins.
  • Lanc a4 (50hp) vs Warr b4 (50hp): Lancer deals 9 and Warrior deals 5; Lanc a4 (25hp) wins.
  • Lanc a5 (50hp) vs Defe b1 (60hp): Lancer deals 6 and Protector deals 3; Lanc a5 (23hp) wins.
  • Lanc a6 (50hp) vs Defe b2 (60hp): Lancer deals 6 and Protector deals 3; Lanc a6 (23hp) wins.
  • Lanc a7 (50hp) vs Defe b3 (60hp): Lancer deals 6 and Protector deals 3; Lanc a7 (23hp) wins.
  • Vamp a1 (40hp) vs Defe b4 (60hp): Vampire deals 2 to heal 1 and Protector deals 3 to heal 2 (from Healer); Defe b4 (60hp) wins.
  • Vamp a2 (40hp) vs Heal b1 (60hp): Vampire deals 4 to heal 2 and Healer deals 0. Vamp a2 (40hp) wins.
  • Vamp a3 (40hp) vs Vamp b1 (40hp): Vampire deals 4 to heal 4 (from Vampirism + Healer) and Vampire deals 4 to heal 2. Vamp a3 (40hp) wins.
  • Heal a1 (60hp) vs Vamp b2 (40hp): Healer deals 0 and Vampire deals 4 to heal 2. Vamp b2 (40hp) wins.
  • Warr a1 (50hp) vs Vamp b3 (40hp): Warrior deals 5 damage and Vampire deals 4 to heal 2. Warr a1 (2hp) wins.
  • Warr a2 (50hp) vs Vamp b4 (40hp): Warrior deals 5 damage and Vampire deals 4 to heal 2. Warr a2 (2hp) wins.
  • Warr a3 (50hp) vs Vamp b5 (40hp): Warrior deals 5 damage and Vampire deals 4 to heal 2. Warr a3 (2hp) wins.
  • Warr a4 (50hp) vs Vamp b6 (40hp): Warrior deals 5 damage to heal 2 (from Healer) and Vampire deals 4 to heal 2. Warr a4 (26hp) wins.
  • Heal a2 (60hp) vs Lanc b1 (50hp): Healer deals 0 and Lancer deals 9. Lanc b1 (50hp) wins.
  • Defe a1 (60hp) vs Lanc b2 (50hp): Protector deals 3 and Lancer deals 6; Lanc b2 (20hp) wins.
  • Defe a2 (60hp) vs Lanc b3 (50hp): Protector deals 3 and Lancer deals 6; Lanc b3 (20hp) wins.
  • Lanc b4 (50hp) does not fight.

Army 1: Lanc a1 (25hp), Lanc a2 (25hp), Lanc a3 (25hp), Lanc a4 (25hp), Lanc a5 (23hp), Lanc a6 (23hp), Lanc a7 (23hp), Vamp a2 (40hp), Vamp a3 (40hp), Warr a1 (2hp), Warr a2 (2hp), Warr a3 (2hp), Warr a4 (26hp)

Army 2: Defe b4 (60hp), Vamp b2 (40hp), Lanc b1 (50hp), Lanc b2 (20hp), Lanc b3 (20hp), Lanc b4 (50hp)

Round 2

  • Lanc a1 (25hp) vs Defe b4 (60hp): Lancer deals 6 and Protector deals 3; Defe b4 (6hp) wins.
  • Lanc a2 (25hp) vs Vamp b2 (40hp): Lancer deals 9 and Vampire deals 4 to heal 2; Lanc a2 (5hp) wins.
  • Lanc a3 (25hp) vs Lanc b1 (50hp): Each deals 9 damage; Lanc b1 (23hp) wins.
  • Lanc a4 (25hp) vs Lanc b2 (20hp): Each deals 9 damage; Lanc a4 (7hp) wins.
  • Lanc a5 (23hp) vs Lanc b3 (20hp): Each deals 9 damage; Lanc a5 (5hp) wins.
  • Lanc a6 (23hp) vs Lanc b4 (50hp): Each deals 9 damage; Lanc b4 (23hp) wins.
  • Lanc a7 (23hp) does not fight.
  • Vamp a2 (40hp) does not fight.
  • Vamp a3 (40hp) does not fight.
  • Warr a1 (2hp) does not fight.
  • Warr a2 (2hp) does not fight.
  • Warr a3 (2hp) does not fight.
  • Warr a4 (26hp) does not fight.

Army 1: Lanc a2 (5hp), Lanc a4 (7hp), Lanc a5 (5hp), Lanc a7 (23hp), Vamp a2 (40hp), Vamp a3 (40hp), Warr a1 (2hp), Warr a2 (2hp), Warr a3 (2hp), Warr a4 (26hp)

Army 2: Defe b4 (6hp), Lanc b1 (23hp), Lanc b4 (23hp)

Round 3

  • Lanc a2 (5hp) vs Defe b4 (6hp): Lancer deals 6 before Protector can deal 3; Lanc a2 (5hp) wins.
  • Lanc a4 (7hp) vs Lanc b1 (23hp): Each deals 9 damage; Lanc b1 (14hp) wins.
  • Lanc a5 (5hp) vs Lanc b4 (23hp): Each deals 9 damage; Lanc b4 (14hp) wins.
  • Lanc a7 (23hp) does not fight.
  • Vamp a2 (40hp) does not fight.
  • Vamp a3 (40hp) does not fight.
  • Warr a1 (2hp) does not fight.
  • Warr a2 (2hp) does not fight.
  • Warr a3 (2hp) does not fight.
  • Warr a4 (26hp) does not fight.

Army 1: Lanc a2 (5hp), Lanc a7 (23hp), Vamp a2 (40hp), Vamp a3 (40hp), Warr a1 (2hp), Warr a2 (2hp), Warr a3 (2hp), Warr a4 (26hp)

Army 2: Lanc b1 (14hp), Lanc b4 (14hp)

Round 4

  • Lanc a2 (5hp) vs Lanc b1 (14hp): Each deals 9 damage; Lanc b1 (5hp) wins.
  • Lanc a7 (23hp) vs Lanc b4 (14hp): Each deals 9 damage; Lanc a7 (14hp) wins.
  • Vamp a2 (40hp) does not fight.
  • Vamp a3 (40hp) does not fight.
  • Warr a1 (2hp) does not fight.
  • Warr a2 (2hp) does not fight.
  • Warr a3 (2hp) does not fight.
  • Warr a4 (26hp) does not fight.

Army 1: Lanc a7 (14hp), Vamp a2 (40hp), Vamp a3 (40hp), Warr a1 (2hp), Warr a2 (2hp), Warr a3 (2hp), Warr a4 (26hp)

Army 2: Lanc b1 (5hp)

Round 5

  • Lanc a7 (14hp) vs Lanc b1 (5hp): Lancer deals 6 before Lancer can deal 6; Lanc a7 (14hp) wins.
  • Vamp a2 (40hp) does not fight.
  • Vamp a3 (40hp) does not fight.
  • Warr a1 (2hp) does not fight.
  • Warr a2 (2hp) does not fight.
  • Warr a3 (2hp) does not fight.
  • Warr a4 (26hp) does not fight.

Army 1 wins.