0

I am creating a basic OOP program for one of my modules at college. I am making a simple CLI RPG game using python. For a few classes I will be overloading all of the parent class's functions and I'm not sure whether I should be using polymorphism in these cases.

Example:

class character():

def __init__(self, name, hp, level, defense, damage):
    self.hp = hp + (10 * level)
    self.name = name
    self.level = level
    self.defense = defense + (10 * level)
    self.damage = damage + (10 * level)
    self.target = None
    self.invetory = []

def attack(self, target):
    hit_chance = random.random()
    print '%s %s attacked %s %s' % (self.__class__.__name__, self.name, target.__class__.__name__, target.name)

    if self.target == None:
        self.target = target

    if hit_chance <= 0.2:
        print '%s %ss\' attack missed \n' % (self.__class__.__name__, self.name)
        return

    target.attacked(self.damage, self)
    self.fighting()

def attacked(self, wound, attacker):
    wound = wound - float(self.defense) / 100 * random.randint(30, 60)
    self.hp = self.hp - wound
    print '%s %s took %s points of damage from %s %s' % (self.__class__.__name__, self.name, wound, attacker.__class__.__name__, attacker.name)
    if self.target == None:
        self.target=attacker

    if self.hp <= 0:
        print '%s has been defeated. %s is victorious!' % (self.name, attacker.name)
        attacker.victory(self.level)
        del self
        return

    print '%s %ss\' hp fell to %s\n' % (self.__class__.__name__, self.name, self.hp)

def fighting(self):
    threading.Timer(5.0, fighting).start()
    if self.target:
        attack(self.target)
        if self.target.hp <= 0:
            self.target = None

    time.sleep(2)

class player(character):

    def __init__(self, name):
        self.level = 1
        self.max_hp = 100 + 10 * self.level
        self.hp = self.max_hp
        self.name = name
        self.defense = 10 * self.level
        self.damage = 10 * self.level
        self.target = None
        self.inventory = ['bronze_dagger']
        self.xp = 0
        self.gold = 0
        self.weapon = bronze_sword

    def victory(self, level):
        self.xp = self.xp + ((level - self.level) * 20 + random.randint(10, 50))
        self.gold = level * random.randint(1, 100)
        self.hp = self.max_hp

        if self.xp >= self.level * 100:
            self.level_up()

    def attacked(self, wound, attacker):
        wound = wound - float(self.defense) / 100 * random.randint(40, 60)
        self.hp = self.hp - wound
        print '%s %s took %s points of damage from %s %s' % (self.__class__.__name__, self.name, wound, attacker.__class__.__name__, attacker.name)

        if self.hp <= 0:
            print 'You have been defeated by %s!\nGAME OVER!' % (attacker.name)
            del self
            return

        print '%s %ss\' hp fell to %s\n' % (self.__class__.__name__, self.name, self.hp)

    def level_up(self):
        self.xp = 0
        self.level = self.level + 1
        self.hp = 100 + 10 * self.level
        self.defense = 10 * self.level
        self.damage = 10 * self.level
        print '%s %s has reached level %s!' % (self.__class__.__name__, self.name, self.level)

As you can see the player class overloads all of the character class's functions and adds more functions.

Ben
  • 45
  • 7
  • Wouldn't hurt to give an example. With the reasoning behind your concerns. I also suggest taking a look (wiki or elsewhere) at the Liskov Substitution Principle, which theorizes a bit around this type of consideration. – JL Peyret Jul 13 '16 at 16:02
  • Interesting but I don't think my subclass could replace the parent class as I plan to use character as a parent for other class's which I will be creating later on and my child class has functions which would be redundant in the other classes I would be creating.. – Ben Jul 13 '16 at 16:15

1 Answers1

0

Yeah, I don't see why not. Here's an example of the benefits, where I've refactored your code to re-use (look up 'Python DRY') some common code between Player and Character, just for attacked. It looks as if it's a lot more code, but what it allows you to do is to focus on differences and commonalities between Player and Character classes. The 2 message methods could use string templates defined at the class level (on Character and Player) and still use the same method (you'd have to use dictionary based template lookups).

Point is, OOP brings a lot of power to the table. But, not the way you originally wrote those classes.

class Character(object):

    defense_min, defense_max = 30, 60

    def choose_target(self, attacker):
        if self.target == None:
            self.target=attacker

    def show_hit_msg(self):
        print '%s %s took %s points of damage from %s %s' % (self.__class__.__name__, self.name, wound, attacker.__class__.__name__, attacker.name)

    def do_0_hp(self):
        if self.hp <= 0:
            print '%s has been defeated. %s is victorious!' % (self.name, attacker.name)
            attacker.victory(self.level)
            del self
            return

    def end_attack(self):
        print '%s %ss\' hp fell to %s\n' % (self.__class__.__name__, self.name, self.hp)

    def attacked(self, wound, attacker):
        wound = wound - float(self.defense) / 100 * random.randint(self.defense_min, self.defense_max)
        self.hp = self.hp - wound
        self.show_hit_msg()

        self.choose_target()
        self.do_0_hp()

        self.end_attack()                



class Player(Character):

    .....

    defense_min, defense_max = 40, 60

    def choose_target(self, attacker):
        #you dont do anything 
        pass

    def show_hit_msg(self):
        print '%s %s took %s points of damage from %s %s' % (self.__class__.__name__, self.name, wound, attacker.__class__.__name__, attacker.name)

    def do_0_hp(self):
        if self.hp <= 0:
            print 'You have been defeated by %s!\nGAME OVER!' % (attacker.name)
            del self
            return

    def end_attack(self):
        print '%s %ss\' hp fell to %s\n' % (self.__class__.__name__, self.name, self.hp)

p.s. del self probably doesn't work the way you think it does and is not really necessary.

JL Peyret
  • 10,917
  • 2
  • 54
  • 73
  • What do you mean by del self doesn't work the way I think it does? I just thought it would delete the instance of the class. – Ben Jul 14 '16 at 08:14
  • http://stackoverflow.com/questions/6146963/when-is-del-useful-in-python should give you an idea. I actually wasn't aware you could use del to erase *names* just its use on container types. But even the name bit just decrease the ref count for that object by 1 and then only locally. **Self**, or rather what it points at, clearly exists outside of *attacked*. Now, if you had say a global dict, say 'enemies' that held Character instances then a del there might help keeping mem use low. But see also *weakref* to avoid keeping stuff around by mistake. – JL Peyret Jul 14 '16 at 15:09