0

I am fairly new to Python and I know values called in a function are only there inside the function. I am trying to have a battle between a player and a boss in a small text game I am writing; however, It just keeps populating the same information each time the function is called. I feel like I am missing something. Any help would be appreciated.

The classes:

class Character:
    def __init__(self, name, stats):
        self.name = name
        self.stats = stats

    name = {
        "Name": ""
    }
    stats = {
        "Dexterity": "",
        "Strength": "",
        "Health": 20,
        "AC": 16,
        "Weapon": "",
    }

    damage = 2 * random.randrange(1, 7)
    ability_check = random.randrange(1, 20)
    initiative = random.randrange(1,20)


class Boss:
    def __init__(self, name, stats):
        self.name = name
        self.stats = stats
    
    name = {
        "Name": "Gargamel"
    }

    stats = {
    "AC": 16,
    "Health": 15,
    "Weapon": "Sword"
    }

    damage = random.randrange(1, 6)
    initiative = random.randrange(1,20)

The functions:

def battle():
    choice = input("Do you wish to continue fighting or run? F or R  ")
    if (choice.lower() == 'f'):
        boss_battle()
    if (choice.lower() == 'r'):
        pass

def boss_battle():
    print("The skeletal creature grabs a sword from the wall and takes a swing at you...\n")
    print(f"Boss init {Boss.initiative}, Character init {Character.initiative}")
    while Boss.stats["Health"] > 0 or Character.stats["Health"]:
        if (Boss.initiative > Character.initiative):
            boss_damage = Boss.damage
            current_player_health = (Character.stats["Health"] - boss_damage)
            Character.stats.update({"Health": current_player_health})
            print(f"The boss did {boss_damage} damage. You now have {current_player_health} hitpoints left.")
            if (Character.stats["Health"] <= 0):
                print('You died!')
                break
            battle()  
        elif (Character.initiative > Boss.initiative):
            player_damage = Character.damage + stat_block_str(int)
            current_boss_health = Boss.stats["Health"] - player_damage
            Boss.stats.update({"Health": current_boss_health})
            print(f"You attacked the creature with your {Character.stats['Weapon']} and dealt {player_damage} damage.")
            if (Boss.stats["Health"] <= 0):
                print(f'Congratulations {Character.name["Name"]}! You have beaten the boss and claimed the treasure!')
                break
            battle()
wcrich01
  • 1
  • 1
  • Please give an example output on what is actually happening vs. what you want to achieve. It's hard to judge what is actually going wrong otherwise. – FloWil Dec 15 '20 at 08:24
  • So it is currently printing out Boss init 4, Character init 1. The boss did 4 damage, You now have 12 hit points left. The next time the function runs it will print out Boss init 4, Character init 1. The boss did 4 damage, You now have 8 hit points left. I want the damage to change and not be stuck on 4 or whatever it rolled the first time. – wcrich01 Dec 15 '20 at 14:11

2 Answers2

0

You have declared classes with class variables, but have made no class instances, so the values are all fixed due to being initialize once when the class was defined.

To make a class instance, you "call" the class using parentheses, which calls your __init__ function on the instance, which sets instance variables.

Here's a small example:

import random

class Character:
    def __init__(self,name):
        self.name = name
        self.health = 20
        self.damage = 2 * random.randrange(1,7)

    def attack(self,target):
        print(f'{self.name} attacks {target.name}...')
        target.health -= self.damage
        print(f'{target.name} has {target.health} health remaining.')

    # defines how to display the class instance when printed.
    def __repr__(self):
        return f"Character(name={self.name!r}, health={self.health}, damage={self.damage})"

fred = Character('Fred')
george = Character('George')
print(fred)
print(george)
print(f'{fred.name} can deliver {fred.damage} damage.')
fred.attack(george)

Output:

Character(name='Fred', health=20, damage=4)
Character(name='George', health=20, damage=10)
Fred can deliver 4 damage.
Fred attacks George...
George has 16 health remaining.
Mark Tolonen
  • 166,664
  • 26
  • 169
  • 251
0

While true, that your using class variables when you should better use instance variables (see Instance variables vs. class variables in Python amongst others for details) I don't think it is enough to achieve your goal.

What you describe in the comment happens because the class is only setup once and from there on the class variables like damage etc. are static, meaning the random call is only executed once for the whole program. I would suggest to convert the damage variables to functions like this:

class Character:

    def __init__(self,name):
        self.name = name
        self.health = 20
        # just as a simple example
        self.base_damage = 2 

    def damage(self):
        return self.base_damage * random.randrange(1,7)

This way you will obtain a new random value for call to the function (battle round) and not just have a static value for damage inside the class. Assuming you made instances for the character and the boss the call can look like this:

current_boss_health = bossInstance.stats["Health"] - characterInstance.damage()

If you want to take this on step further and make this a bit more pythonic you can use the @property decorator for damage:

@property
def damage(self):
    return self.base_damage * random.randrange(1,7)

This way the usage of the function will look even more like your original and it hides the function call:

current_boss_health = bossInstance.stats["Health"] - characterInstance.damage
FloWil
  • 432
  • 5
  • 15