Python Forum
Need Help With Python RPG Battle System
Thread Rating:
  • 2 Vote(s) - 4.5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Need Help With Python RPG Battle System
#1
Hello everyone,

I've been working on the StackSkills Python video course, and completed the RPG system video tutorial a while back. I have one particular issue with one of the functions presented in the video produces an error after playing the game for a while.

What's a bummer is that the author of the video series never fixed this issue, and I would like to take the ideas in this battle system, and make a cool text RPG project for fun on the side.

I'm getting this error after running this a few times (ignore the character names because I was seeing how many characters I can fit within the current system):

Traceback (most recent call last): File "main.py", line 122, in enemies[enemy].take_damage(dmg) IndexError: list index out of range exited with non-zero status

Since it's been a week or so, just looking at this code is a bit harsh since it was a good 20 or so video tutorial playlist on this course, and I basically followed along. I understand it somewhat well in the fact that the events in the game are based on the text inputs of the player, and all of these events eventually call different methods from the game, magic, and inventory Python files which contain relevant classes.

For this error, my biggest hunch is that the line 'del players[player]' should be: 'del players[target]' instead.

Let me know what you think guys :)

Here's the main.py file:
from game import Person, bcolors
from magic import Spell
from inventory import Item 
import random

# Parameters for Person Class: (self, hp, mp, atk, df, magic)

# Create Black Magic 
# Parameters for Spell() class: name, cost, dmg, type
fire = Spell("Fire", 25, 600, "black")
thunder = Spell("Thunder", 25, 600, "black")
blizzard = Spell("Blizzard", 25, 600, "black")
meteor = Spell("Meteor", 40, 1200, "black")
quake = Spell("Quake", 14, 140, "black")

# Create White Magic 
cure = Spell("Cure", 25, 620, "white")
cura = Spell("Cura", 32, 1500, "white")
curaga = Spell("Curaga", 50, 6000, "white")

# Create Some Items
# Parameters for Item() class: name, type, description, prop
potion = Item("Potion", "potion", "Heals 50 HP", 50)
hipotion = Item("Hi-Potion", "potion", "Heals 100 HP", 100)
superpotion = Item("Super Potion", "potion", "Heals 1000 HP", 1000)
elixer = Item("Elixer", "elixer", "Full restores HP/MP of one party member", 9999)
megaelixer = Item("MegaElixer", "elixer", "Fully restores party's HP/MP", 9999)

grenade = Item("Grenade", "attack", "Deals 500 damage", 500)

enemy_spells = [fire, meteor, curaga]
player_spells = [fire, thunder, blizzard, meteor, cure, cura]
player_items = [{"item": potion, "quantity": 15}, {"item": hipotion, "quantity": 5}, 
                {"item": superpotion, "quantity": 5}, {"item": elixer, "quantity": 5}, 
                {"item": megaelixer, "quantity": 2}, {"item": grenade, "quantity": 5}]

# Title Screen
# print("********************")
# print("\n")
# print("   W E L C O M E")
# print("        T O     ")
# print("   S  T  R  A  Y")
# print("   ~ A Cat RPG ~")
# print("\n")
# print("********************")

# Character Creation
# characterEntry = True

# while characterEntry:
#  heroCat = input("Name Your Hero Cat (10 Character Maximum:)")
#  if len(heroCat) > 10:
#    characterEntry = False
#    heroCat = input("More Than 10 Characters Entered, Please Name Your Hero Cat (10 Character Maximum)")
    
#  elif heroCat == "":
#    characterEntry = False
#    heroCat = input("Less Than 10 Characters Entered, Please Name Your Hero Cat (10 Character Maximum)")
    
#  elif len(heroCat) > 0 and len(heroCat) < 10:
#    characterEntry = True

#print("heroCat = ", heroCat)

# We are instantiating the Person() class twice to make a player and an enemy object 
# Parameters for Person Class: (self, name, hp, mp, atk, df, magic, items)
player1 = Person("Tiggerabcd:", 3460, 165, 300, 34, player_spells, player_items)
player2 = Person("Tiggerabcd:", 4460, 185, 311, 34, player_spells, player_items)
player3 = Person("Tiggerabcd:", 3460, 175, 288, 34, player_spells, player_items)

enemy1 = Person("Minion", 1250, 130, 560, 325, enemy_spells, [])
enemy2 = Person("Ulfric", 11200, 265, 525, 25, enemy_spells, [])
enemy3 = Person("Minion", 1250, 130, 560, 325, enemy_spells, [])

enemies = [enemy1, enemy2, enemy3]

players = [player1, player2, player3]

# This calls the generate_damage() class to generate an attack
# print("Player generates damage with", player.generate_damage(), "atk pts")
# print("Player generates damage with", player.generate_damage(), "atk pts")
# print("Player generates damage with", player.generate_damage(), "atk pts")

# This calls the generate_spell_damage to generate a magic spell by giving a parameter index value for a specific spell in the 'magic' dictionary
# print("Player casts Fire spell for", player.generate_spell_damage(0), "atk pts")
# print("Player casts Thunder spell for", player.generate_spell_damage(1), "atk pts")

running = True

print(bcolors.FAIL + bcolors.BOLD + "AN ENEMY ATTACKS!" + bcolors.ENDC)

while running:
  print("===================")
  
  print("\n\n")
  
  print("NAME                HP                                 MP")
  
  for player in players:
    player.get_stats()
    
  print("\n")
  
  for enemy in enemies:
    enemy.get_enemy_stats()
  
  for player in players:

    player.choose_action()
    choice = input("    Choose Action:")
    # Index is a variable that takes the value of choice and subtracts it by 1 to give the program the correct index value for each of the lists
    # This is because computers start counting lists from position "0"
    index = int(choice) - 1 
    
    # print("You chose", player.get_spell_name(int(index)))
    
    if index == 0:
      dmg = player.generate_damage()
      # This calls the choose_target() function in the Player class to choose an enemy in the available Enemy array 
      enemy = player.choose_target(enemies)
      enemies[enemy].take_damage(dmg)
      print("You attacked " + enemies[enemy].name.replace(" ", "") + " for", dmg, "points of damage.")
      # This checks to see if the enemy is dead, and if so, delete it from the enemies list 
      if enemies[enemy].get_hp() == 0:
        print(enemies[enemy].name.replace(" ", "") + " has died.")
        del enemies[enemy]
        
    elif index == 1:
      player.choose_magic()
      # This combined statement allows you to index into the correct magic spell choice since computers start counting from 0 onward
      magic_choice = int(input("Choose magic:"))-1
      
      # This allows you to go back into the previous menu with the use of the number choice, '0'
      if magic_choice == -1:
        continue
      
      # This indexes into the magic list based on the user's number choice
      spell = player.magic[magic_choice]
      # This calls the generate_damage() class from the magic class
      magic_dmg = spell.generate_damage()
      
      current_mp = player.get_mp()
      
      if spell.cost > current_mp:
        # \n: this creates a line break within a print statement in Python
        print(bcolors.FAIL + "\nNot enough MP\n" + bcolors.ENDC)
        continue
      
      # This reduces the player's mp based on the mp cost of the spell
      player.reduce_mp(spell.cost)
      
      if spell.type == "white":
        player.heal(magic_dmg)
        print(bcolors.OKBLUE + "\n" + spell.name + " heals for", str(magic_dmg), "HP." + bcolors.ENDC)
      elif spell.type == "black":
        # This calls the choose_target() function in the Player class to choose an enemy in the available Enemy array 
        enemy = player.choose_target(enemies)
        enemies[enemy].take_damage(magic_dmg)
        print(bcolors.OKBLUE + "\n" + spell.name + " deals", str(magic_dmg), "points of damage to " + enemies[enemy].name.replace(" ", "") + bcolors.ENDC)
        
        # This checks to see if the enemy is dead, and if so, delete it from the enemies list 
        if enemies[enemy].get_hp() == 0:
          print(enemies[enemy].name.replace(" ", "") + " has died.")
          del enemies[enemy]
     
    elif index == 2:
      player.choose_items()
      item_choice = int(input("Choose item: ")) - 1
      
      # This allows you to go back into the previous menu with the use of the number choice, '0'
      if item_choice == -1:
        continue
      
      item = player.items[item_choice]["item"]
      
      if player.items[item_choice]["quantity"] == 0:
        print(bcolors.FAIL + "\n" + "No " + item.name + "s left..." + bcolors.ENDC)
        continue
      
      player.items[item_choice]["quantity"] -= 1
      
      if item.type == "potion":
        player.heal(item.prop)
        print(bcolors.OKGREEN + "\n" + item.name + " heals for", str(item.prop), "HP" + bcolors.ENDC)
      
      elif item.type == "elixer":
        if item.name == "MegaElixer":
          for i in players:
            i.hp = i.maxhp 
            i.mp = i.maxmp 
        else:
          player.hp = player.maxhp 
          player.mp = player.maxmp 
        print(bcolors.OKGREEN + "\n" + item.name + " fully restores HP/MP" + bcolors.ENDC)
        
      elif item.type == "attack":
        # This calls the choose_target() function in the Player class to choose an enemy in the available Enemy array 
        enemy = player.choose_target(enemies)
        enemies[enemy].take_damage(item.prop)
        print(bcolors.FAIL + "\n" + item.name + " deals", str(item.prop), "points of damage to " + enemies[enemy].name + bcolors.ENDC)
        # This checks to see if the enemy is dead, and if so, delete it from the enemies list 
        if enemies[enemy].get_hp() == 0:
          # The .replace() function strips a whitespace each time an enemy dies
          print(enemies[enemy].name.replace(" ", "") + " has died.")
          del enemies[enemy]
      
  # Check if battle is over
  defeated_enemies = 0 
  defeated_players = 0 
  
  for enemy in enemies:
    if enemy.get_hp() == 0:
      defeated_enemies += 1 
  
  for player in players:
    if player.get_hp() == 0:
      defeated_players += 1 
  
  # Check if Player Won:
  if defeated_enemies == 2:
    print(bcolors.OKGREEN + "You win!" + bcolors.ENDC)
    running = False
      
  # Check if Enemy Won:
  elif defeated_players == 2:
    print(bcolors.FAIL + "Your enemy has defeated you!" + bcolors.ENDC)
    running = False
    
  print("\n")
  
  # Enemy Attack Phase:
  for enemy in enemies:       
    enemy_choice = random.randrange(0, 2)
    
    if enemy_choice == 0:
      # Enemy chooses player to attack 
      target = random.randrange(0, 3)
      # This allows the first enemey to start the attack 
      enemy_dmg = enemy.generate_damage()
      players[target].take_damage(enemy_dmg)
      # The .replace() function replaces all spaces
      print(enemy.name.replace(" ", "") + " attacks " + players[target].name.replace(" ", "") + " for", enemy_dmg)
      
    elif enemy_choice == 1:
      spell, magic_dmg = enemy.choose_enemy_spell()
      enemy.reduce_mp(spell.cost)
      
      if spell.type == "white":
        enemy.heal(magic_dmg)
        print(bcolors.OKBLUE + spell.name + " heals " + enemy.name + "for", str(magic_dmg), "HP." + bcolors.ENDC)
      elif spell.type == "black":
        # This calls the choose_target() function in the Player class to choose an enemy in the available Enemy array 
        target = random.randrange(0, 3)
        players[target].take_damage(magic_dmg)
        print(bcolors.OKBLUE + "\n" + enemy.name.replace(" ", "") + "'s " + spell.name + " deals", str(magic_dmg), "points of damage to " + players[target].name.replace(" ", "") + bcolors.ENDC)
        
        # This checks to see if the enemy is dead, and if so, delete it from the enemies list 
        if players[target].get_hp() == 0:
          print(players[target].name.replace(" ", "") + " has died.")
          del players[player]
      # print("Enemy chose", spell, "damage is", magic_dmg)
      

  # Fix issue: File "main.py", line 131, in <module>
Here's the Game.py file:

# NOTES:
# This would normally be in a classes directory
# This would normally be called "game.py" if this were in a proper IDE

import random

from magic import Spell

import pprint

class bcolors:
  HEADER = '\033[95m'
  OKBLUE = '\033[94m'
  OKGREEN = '\033[92m'
  WARNING = '\033[93m'
  FAIL = '\033[91m'
  ENDC = '\033[0m'
  BOLD = '\033[1m'
  UNDERLINE = '\033[4m'
  
class Person:
  def __init__(self, name, hp, mp, atk, df, magic, items):
    # Max HP value
    self.maxhp = hp
    # Current HP 
    self.hp = hp
    self.maxmp = mp
    self.mp = mp 
    # We set the low and high attack power +-10
    self.atkl = atk - 10
    self.atkh = atk + 10
    self.df = df
    # This will be a dictionary of magic spells and their MP cost 
    self.magic = magic
    self.items = items
    self.actions = ["Attack", "Magic", "Items"]
    self.name = name
  
  def generate_damage(self):
    return random.randrange(self.atkl, self.atkh)
      
  def take_damage(self, dmg):
    self.hp -= dmg
    if self.hp < 0:
      self.hp = 0 
    return self.hp 
  
   # These are utility classes to get HP and MP information to calculate remaining MP and HP points  
   
  def heal(self, dmg):
    self.hp += dmg
    # This makes sure you don't heal above your max hp 
    if self.hp > self.maxhp:
      self.hp = self.maxhp 
   
  def get_hp(self):
    return self.hp
    
  def get_max_hp(self):
    return self.maxhp 
  
  def get_mp(self):
    return self.mp 
    
  def get_max_mp(self):
    return self.maxmp 
  
  def reduce_mp(self, cost):
    self.mp -= cost
    
  def choose_action(self):
    i = 1 
    print("\n" + "    " + bcolors.BOLD + self.name + bcolors.ENDC)
    print(bcolors.OKBLUE + bcolors.BOLD + "    ACTIONS" + bcolors.ENDC)
    for item in self.actions:
      print("        " + str(i) + ".", item)
      i += 1 
  
  def choose_magic(self):
    i = 1 
    
    print("\n" + bcolors.OKBLUE + bcolors.BOLD + "     MAGIC:" + bcolors.ENDC)
    for spell in self.magic:
      print("        " + str(i) + ".", spell.name, "(cost:", str(spell.cost) + ")")
      i += 1
      
  def choose_items(self):
    i = 1 
    
    print("\n" + bcolors.OKGREEN + bcolors.BOLD + "     ITEMS:" + bcolors.ENDC)
    
    # This prints out the items available in the items dictionary:
    for item in self.items:
      print("        " + str(i) + ".", item["item"].name, ":", item["item"].description, " (x" + str(item["quantity"]) + ")")
      i += 1
      
  def choose_target(self, enemies):
    i = 1
    
    print("\n" + bcolors.FAIL + bcolors.BOLD + "    TARGET:" + bcolors.ENDC)
    
    # This prints the enemies on screen, and then lets the player decide which enemy to attack
    for enemy in enemies:
      if enemy.get_hp() != 0:
        print("        " + str(i) + ".", enemy.name)
        i += 1 
    choice = int(input("    Choose target:")) - 1 
    return choice
      
  def get_enemy_stats(self):
    hp_bar = ""
    # Enemy HP bar will be 50 characters long
    bar_ticks = (self.hp / self.maxhp) * 100 / 2 
    
    while bar_ticks > 0:
      hp_bar += "█"
      bar_ticks -= 1
      
    while len(hp_bar) < 50:
      hp_bar += " "
      
    hp_string = str(self.hp) + "/" + str(self.maxhp)
    current_hp = ""
    
    # This makes sure that the enemy's hp bar is only 11 characters long
    if len(hp_string) < 11:
      decreased = 11 - len(hp_string)
      
      while decreased > 0:
        current_hp += " "
        decreased -= 1
      
      current_hp += hp_string
      
    else:
      current_hp = hp_string
      
    print("                    __________________________________________________")
    print(bcolors.BOLD + self.name  +  "|" +
          current_hp + " |" + bcolors.FAIL + hp_bar + bcolors.ENDC + "|")
      
  def get_stats(self):
    
    # Health Bar String
    hp_bar = ""
    hp_ticks = (self.hp / self.maxhp) * 100 / 4
    
    mp_bar = ""
    mp_ticks = (self.mp / self.maxmp) * 100 / 10
    
    
    while hp_ticks > 0:
      hp_bar += "█"
      hp_ticks -= 1
      
    while len(hp_bar) < 25:
      hp_bar += " "
    
    while mp_ticks > 0:
      mp_bar += "█"
      mp_ticks -= 1
      
    while len(mp_bar) < 10:
      mp_bar += " "
      
    hp_string = str(self.hp) + "/" + str(self.maxhp)
    current_hp = ""
    
    if len(hp_string) < 9:
      decreased = 9 - len(hp_string)
      
      while decreased > 0:
        current_hp += " "
        decreased -= 1
      
      current_hp += hp_string
      
    else:
      current_hp = hp_string
      
    mp_string = str(self.mp) + "/" + str(self.maxmp)
    current_mp = ""
    
    if len(mp_string) < 7:
      decreased = 7 - len(mp_string)
      
      while decreased > 0:
        current_mp += " "
        decreased -= 1 
      
      current_mp += mp_string 
    
    else:
      current_mp = mp_string
    
    print("                       _________________________          __________")
    print(bcolors.BOLD + self.name  +  "|" +
          current_hp + " |" + bcolors.OKGREEN + hp_bar + bcolors.ENDC + "|" +
          current_mp + " |" + bcolors.OKBLUE + mp_bar + bcolors.ENDC + "|")
  
  def choose_enemy_spell (self):
    # This generates a random number based on the amount of spells available in the magic spell list
    magic_choice = random.randrange(0, len(self.magic)) 
    spell = self.magic[magic_choice]
    magic_dmg = spell.generate_damage()
      
    # This checks to see if the enemy still has MP remaining and then recalls the function recursively  
  
    pct = self.hp / self.maxhp * 100
    
    # If the enemy does not have any MP or if the enemy has over 50 HP, then they're not going to use white magic to cure themselves
    if self.mp < spell.cost or spell.type == "white" and pct > 50:
      spell, magic_dmg = self.choose_enemy_spell()
      return spell, magic_dmg
    else:
        return spell, magic_dmg
    

    
    
      
    
    
Here's the magic.py file:

import random

class Spell:
  def __init__(self, name, cost, dmg, type):
    # self: instance of the class
    self.name = name
    self.cost = cost
    self.dmg = dmg
    self.type = type
    
  def generate_damage(self):
    low = self.dmg - 15
    high = self.dmg + 15
    return random.randrange(low, high)
Here's the inventory.py file:

class Item:
  def __init__(self, name, type, description, prop):
    self.name = name
    self.type = type
    self.description = description
    self.prop = prop
  
  
Reply
#2
What came of this? I've been using it as a base for my own project and would like to know if you ever finished it
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  Difference between os.system("clear") and os.system("cls") chmsrohit 7 16,495 Jan-11-2021, 06:30 PM
Last Post: ykumar34
Question Difference between Python's os.system and Perl's system command Agile741 13 6,658 Dec-02-2019, 04:41 PM
Last Post: Agile741

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020