Inheritance Project · RPG

RPG Character
Builder

A live Python inheritance project. Warrior, Mage, and Archer share a Character base class, but each overrides attack() and defend() with their own style. Run the code, see the hierarchy in action, and build your own hero.

🐍 Python 3.11+ 🎓 Inheritance Deep-Dive ⚔️ Warrior · Mage · Archer
Back to Inheritance Guide

Build a Party of Heroes

⚔️ Inheritance in Game Design

Every RPG needs characters that share common traits — health, name, level — but fight differently. This project implements that exact pattern using inheritance. A Character base class holds shared state, and three subclasses (Warrior, Mage, Archer) override the combat methods to express their unique identities.

When you click Run, the code creates one of each hero, sends them into battle, and prints their actions — showing exactly how inheritance enables both reuse (the shared Character foundation) and specialization (each subclass's own attack() and defend()).

3 hero subclasses 2 overridden methods each Live combat simulation Built on inheritance

Character Classes — The Hierarchy

Every hero inherits from Character, which provides:

  • Name, health, level — shared state for every character
  • is_alive() — a helper method used by all subclasses
  • A contract for attack() and defend() — each subclass must implement its own version
⚔️
Warrior
class Warrior(Character)
❤️ 120 HP⚡ 22 ATK
🔮
Mage
class Mage(Character)
❤️ 80 HP⚡ 35 ATK
🏹
Archer
class Archer(Character)
❤️ 90 HP⚡ 25 ATK

Live Inheritance Example

What this code does: Defines a Character base class with shared logic, then three subclasses that override attack() and defend(). The final block creates one of each and runs a combat simulation — showing inheritance in action. Click Run below
import random

class Character:
    def __init__(self, name: str, health: int, level: int = 1):
        self.name = name
        self.health = health
        self.level = level
        self.max_health = health

    def is_alive(self) -> bool:
        return self.health > 0

    def take_damage(self, damage: int) -> None:
        self.health = max(0, self.health - damage)

    def attack(self, target: 'Character') -> str:
        raise NotImplementedError("Subclasses must implement attack()")

    def defend(self, damage: int) -> int:
        raise NotImplementedError("Subclasses must implement defend()")

    def __str__(self) -> str:
        return f"{self.name} (❤️ {self.health}/{self.max_health})"

class Warrior(Character):
    def __init__(self, name: str):
        super().__init__(name, health=120, level=1)

    def attack(self, target: 'Character') -> str:
        damage = random.randint(18, 26)
        mitigated = target.defend(damage)
        target.take_damage(mitigated)
        return f"⚔️ {self.name} slashes {target.name} for {mitigated} damage!"

    def defend(self, damage: int) -> int:
        return int(damage * 0.6)

class Mage(Character):
    def __init__(self, name: str):
        super().__init__(name, health=80, level=1)

    def attack(self, target: 'Character') -> str:
        damage = random.randint(30, 40)
        mitigated = target.defend(damage)
        target.take_damage(mitigated)
        return f"🔮 {self.name} casts Fireball on {target.name} for {mitigated} damage!"

    def defend(self, damage: int) -> int:
        return int(damage * 0.8)

class Archer(Character):
    def __init__(self, name: str):
        super().__init__(name, health=90, level=1)

    def attack(self, target: 'Character') -> str:
        damage = random.randint(20, 30)
        if random.random() < 0.3:
            damage *= 2
            crit_msg = "💥 Critical hit!"
        else:
            crit_msg = ""
        mitigated = target.defend(damage)
        target.take_damage(mitigated)
        msg = f"🏹 {self.name} shoots {target.name} for {mitigated} damage!"
        return f"{msg} {crit_msg}".strip()

    def defend(self, damage: int) -> int:
        if random.random() < 0.3:
            return 0
        return damage

def run_battle():
    heroes = [
        Warrior("Thorn"),
        Mage("Lyra"),
        Archer("Kael")
    ]
    print("🔥 The party assembles!")
    for h in heroes:
        print(h)
    print("\n⚔️ Battle begins! Each hero attacks the next...")
    for i in range(2):
        for idx, attacker in enumerate(heroes):
            target = heroes[(idx + 1) % len(heroes)]
            if attacker.is_alive() and target.is_alive():
                print(attacker.attack(target))
                print(f"  ↳ {target}")
        print("---")
    print("\n🏁 Battle over!")
    for h in heroes:
        status = "❤️ alive" if h.is_alive() else "💀 defeated"
        print(f"{h.name}: {status}")

if __name__ == "__main__":
    run_battle()
📝 Why This Code Was Used & How It Works
  1. Why an RPG? Games are one of the most intuitive domains for inheritance — the "is-a" relationship is crystal clear: a Warrior is a Character, a Mage is a Character. Each subclass shares the same interface but behaves differently.
  2. Significance of raise NotImplementedError: In Character.attack() and defend(), this forces every subclass to provide its own implementation — exactly like the Shape example in the main guide. It's a design contract.
  3. How Warrior.defend() reduces damage: It applies a 40% mitigation (heavy armor). The base Character knows nothing about armor — this is pure specialization in the subclass.
  4. How Archer.defend() handles dodge: A 30% chance to take zero damage — completely different logic from Warrior and Mage, yet all three share the same defend(damage) → int signature.
  5. How the simulation demonstrates polymorphism: The run_battle() function treats every hero as a Character — calling .attack() and .is_alive() without ever checking the subclass. Python dynamically dispatches to the correct attack() and defend() based on the actual object type.

How the Classes Relate

Inheritance Tree

This is a hierarchical inheritance pattern: one parent (Character), three independent children (Warrior, Mage, Archer).

Character
│││
⚔️ Warrior 🔮 Mage 🏹 Archer

Key insight: Each subclass independently inherits the same parent. Changes to Character (e.g., adding a level_up() method) automatically apply to all three subclasses — this is the power of inheritance.

Method Overriding Summary

MethodWarriorMageArcher
attack()Slash (18–26 dmg)Fireball (30–40 dmg)Shot (20–30 dmg, 30% crit)
defend()40% reduction20% reduction30% dodge
__init__super() + 120 HPsuper() + 80 HPsuper() + 90 HP

Note: Each subclass calls super().__init__() to reuse the parent's attribute setup — exactly as shown in the main Inheritance guide's Manager example.

Execute the RPG Builder

Click Run below to execute the full RPG battle simulation in your browser — no setup required. Watch the inheritance hierarchy come to life.

python · rpg_simulation.py
import random

class Character:
    def __init__(self, name: str, health: int, level: int = 1):
        self.name = name
        self.health = health
        self.level = level
        self.max_health = health

    def is_alive(self) -> bool:
        return self.health > 0

    def take_damage(self, damage: int) -> None:
        self.health = max(0, self.health - damage)

    def attack(self, target: 'Character') -> str:
        raise NotImplementedError("Subclasses must implement attack()")

    def defend(self, damage: int) -> int:
        raise NotImplementedError("Subclasses must implement defend()")

    def __str__(self) -> str:
        return f"{self.name} (❤️ {self.health}/{self.max_health})"

class Warrior(Character):
    def __init__(self, name: str):
        super().__init__(name, health=120, level=1)

    def attack(self, target: 'Character') -> str:
        damage = random.randint(18, 26)
        mitigated = target.defend(damage)
        target.take_damage(mitigated)
        return f"⚔️ {self.name} slashes {target.name} for {mitigated} damage!"

    def defend(self, damage: int) -> int:
        return int(damage * 0.6)

class Mage(Character):
    def __init__(self, name: str):
        super().__init__(name, health=80, level=1)

    def attack(self, target: 'Character') -> str:
        damage = random.randint(30, 40)
        mitigated = target.defend(damage)
        target.take_damage(mitigated)
        return f"🔮 {self.name} casts Fireball on {target.name} for {mitigated} damage!"

    def defend(self, damage: int) -> int:
        return int(damage * 0.8)

class Archer(Character):
    def __init__(self, name: str):
        super().__init__(name, health=90, level=1)

    def attack(self, target: 'Character') -> str:
        damage = random.randint(20, 30)
        if random.random() < 0.3:
            damage *= 2
            crit_msg = "💥 Critical hit!"
        else:
            crit_msg = ""
        mitigated = target.defend(damage)
        target.take_damage(mitigated)
        msg = f"🏹 {self.name} shoots {target.name} for {mitigated} damage!"
        return f"{msg} {crit_msg}".strip()

    def defend(self, damage: int) -> int:
        if random.random() < 0.3:
            return 0
        return damage

def run_battle():
    heroes = [
        Warrior("Thorn"),
        Mage("Lyra"),
        Archer("Kael")
    ]
    print("🔥 The party assembles!")
    for h in heroes:
        print(h)
    print("\n⚔️ Battle begins! Each hero attacks the next...")
    for _ in range(2):
        for idx, attacker in enumerate(heroes):
            target = heroes[(idx + 1) % len(heroes)]
            if attacker.is_alive() and target.is_alive():
                print(attacker.attack(target))
                print(f"  ↳ {target}")
        print("---")
    print("\n🏁 Battle over!")
    for h in heroes:
        status = "❤️ alive" if h.is_alive() else "💀 defeated"
        print(f"{h.name}: {status}")

if __name__ == "__main__":
    run_battle()

🧪 Experiment: Try modifying the code. Change the Warrior's damage range, give the Mage a shield spell, or add a new Rogue subclass. The inheritance structure makes it easy — just subclass Character and override the methods you want.

Share the RPG Builder