Python : Object oriented programming (Basics)

In this post we will be learning object oriented programming in python by developing a simple battle game. we will cover topics like classes, inheritance, polymorphism etc…

In Python everything is an object, if we call type() it will tell what type of object it is. To prove our point let us try these examples.


$type(1)
int
$type('hello')
str
$type([1,2,3])
list
$type({'name':'jon'})
dict
$def square(num):
$    return num * num
$type(square)    
function
 

So it shows that even a function is an object in Python. So far we were discussing about built-in objects in Python. Now let us see how to create custom objects and that’s where Class comes into picture. A class in Python defines the blue print of the object. A class can have attributes and method. An attribute is a characterstic of the an object and a method is an operation we can perform with the object. Let us create a simple class.

class Circle(object):

    # class object attribute
    pi = 3.14

    def __init__(self, radius):
        self.radius = radius

    def area(self):
        return self.pi * self.radius**2

    def perimeter(self):
        return 2 * self.pi * self.radius

---
c1 = Circle(10)
print(c1.area(), c1.perimeter(), c1.pi)

c2 = Circle(100)
print(c2.area(), c2.perimeter())

So an attribute is defined using self keyword and __init__ is a special/magic method to initialize attributes of an object. I additon a class can have class object attribute which is like static members and shared across all objects. In the above example pi in a class object attribute.

Inheritance

Inheritance is the way of creating a new class using classes that have been already defined. The newly formed classes are called derived classes, the classes that we derive from are called base classess. Inheritance improves code reuse and reduce program complexity. The derived class can override/extend the functionality of base classess.

Let us create a base class “Animal”.

# animal.py
class Animal(object):
    def __init__(self):
        print('Animal created !')

    def whoami(self):
        print('I am an Animal.')

    def eat(self):
        print('Eating...')

Now let us derive a Dog from Animal. Dog overrides the __init__ and whoami. In addition Dog has its own method bark. Its important that in this example,Dog initializer is also calling base class initializer.

# dog.py

class Dog(Animal):
    def __init__(self, breed):
        Animal.__init__(self) # call base class initializer
        self.breed = breed
        print("Dog created !")

    def whoami(self):
        print("I am a dog and my breed is {}.".format(self.breed))

    def bark(self):
        print('Whoof whoof')
# program.py

d = Dog('husky')
d.whoami()
d.eat()
d.bark()

--output--
Animal created !
Dog created !
I am a dog and my breed is husky.
Eating...
Whoof whoof

A simple game

Now to solidify our understanding let us create a simple Game. This is how the game works. Our hero is trapped in a forest,where there will be different kind of creatures who is ready to attack the hero.A creature will appear in front of our hero and our hero can do three actions 1) Attack 2) Run 3) List all creatures.

All our actors in this game have 2 things in common. They have a name and a level. Based on level we decides the strength of attack.Here is how we determines the winner. When our hero attacks, we roll a dice for hero and creature. Multiply the roll value with the level to determine the attack strength.One with higher attack strength wins the battle.So based on this we can create a base class which contains the common fields and functions. This is part of actor.py module.


# This is the base class
class Creature:
    def __init__(self, name, level):
        self.name = name
        self.level = level

    def __repr__(self):
        return "Name is {} and level is {}".format(self.name, self.level)

    def get_roll(self):
        roll = random.randint(1, 12) * self.level
        return roll
    

__init__ is the magic method which acts as the constructor of the class. get_roll is the common method that implements dice rolling.

Now let us create the Hero class, which is inherited from the Creature base class. Hero is a human and human’s can have IQ in addition to default level. So we over-ride the constructor by adding an iq parameter. In addition,Hero can attack the creature,so we have added that method too.We also have to accommodate iq level when we roll for Hero, so we over-ride the get_roll() method too as shown below. Its important to remember that we are calling base class methods inside the over-ridden method when necessary.


# This class represents the Hero.
class Hero(Creature):
    def __init__(self, name, level, iq):
        super().__init__(name, level)
        self.iq = iq

    def get_roll(self):
        base_roll = super().get_roll()
        return int(base_roll * (self.iq/10))

    def attack(self, opponent):
        my_roll = self.get_roll()
        creature_roll = opponent.get_roll()
        print('Your roll is {} and {} roll is {}'.format(my_roll, opponent.name, creature_roll))
        if my_roll >= creature_roll:
            return True
        else:
            return False


In similar way we are also creating some creatures inheriting from the base class.


class Reptile(Creature):
    def __init__(self, name, level, is_poison):
        super().__init__(name, level)
        self.is_poison = is_poison

    def get_roll(self):
        base_roll = super().get_roll()
        poison_modifier = 2 if self.is_poison else 1
        return int(base_roll * poison_modifier)


Now let us create a Dragon and Predator respectively.



# This class represents a dragon
class Dragon(Creature):
    def __init__(self, name, level, breaths_fire):
        super().__init__(name, level)
        self.breaths_fire = breaths_fire

    def get_roll(self):
        base_roll = super().get_roll()
        fire_modifier = 5 if self.breaths_fire else 1

        return int(base_roll * fire_modifier )


Predator class is just same as the base class. We can add some methods or fields later if needed. For time being just pass.



# This class represents a predator.
class Predator(Creature):
    pass
    

Now we are done with creating all actors in our game, now let us focus on our game loop. The program will exit game loop if all creatures are killed by hero or if player exited the program by entering an invalid key other than (a, r or l). We started creating our hero and array of creatures (line : 4 and 5).
Once we enter the loop, an active creature is selected using random.choice() (line:9). The name and level of the active creature is displayed to player. Player can provide the input based on selected creature as shown in line : 12.

If hero attacks and failed, then hero is given three lives. We use thread module to sleep for 5 seconds to revitalize our hero as show in line:31.


def game_loop():

    life_count = 0;
    hero = Hero('David', 100, 50)
    creatures = [Reptile('Viper', 50, True), Predator('Tiger', 100),
                 Dragon('Dragon', 200, True)]

    while True:
        active_creature = random.choice(creatures)
        print("A {} with level {} shows up from the forest".
            format(active_creature.name, active_creature.level))
        action = input("Enter your action : [A]ttack, [R]un, [L]ook : ")
        action = action.lower()
        if action == 'a':
           result = hero.attack(active_creature)
           if result:
               print('Hero defeated the {}'.format(active_creature.name))
               creatures.remove(active_creature)
           else:
               print('{} defeated the Hero. Hero hides..'.format(active_creature.name))
               time.sleep(5)
               life_count += 1
               if life_count > 3:
                   print('No more lives .. game over!')
                   break
               else:
                   print('Hero is revitalized with rest and back in game')

        elif action == 'r':
           print('Run away..')
        elif action == 'l':
           for c in creatures:
               print(c.name)
        else:
           break
        if not creatures:
           print('You killed all creatures and game over !')
           break
    print()
    

This our main entry and includes..


from actor import Hero, Reptile, Predator, Dragon
import random
import time

if __name__ == '__main__':
    print_header()
    game_loop()

Output:



------------------------------------
      Wizard Battle (OOP)           
------------------------------------

A Dragon with level 200 shows up from the forest
Enter your action : [A]ttack, [R]un, [L]ook : A
Your roll is 6000 and Dragon roll is 4000
Hero defeated the Dragon
A Tiger with level 100 shows up from the forest
Enter your action : [A]ttack, [R]un, [L]ook : A
Your roll is 5500 and Tiger roll is 200
Hero defeated the Tiger
A Viper with level 50 shows up from the forest
Enter your action : [A]ttack, [R]un, [L]ook : L
Viper
A Viper with level 50 shows up from the forest
Enter your action : [A]ttack, [R]un, [L]ook : A
Your roll is 3000 and Viper roll is 300
Hero defeated the Viper
You killed all creatures and game over !


Coding is fun enjoy…