Question

The program is rather self-explanatory. I've begun playing around with the basics of Python, and am really lost on this one. I am used to C++ and the wonderful ability to pass things by reference. But, in this, the class variable I'm trying to change (Fighter.statHEALTH) won't change, and I've read that it's because integers are immutable and it just creates a new object locally. So how in the world can I make the change apply to the original variable? I've Googled and Googled, but to no avail. I don't want to perform some ugly manuever like making a list and passing it if I don't have to.

#python 3.2.2

#   Create a small test project to have combat between two entities.    #
#   Combat should include 3 different stats: statATK, statDEF, and statHEALTH.  #
#   The two entities should be of the same class.   #

class Fighter:
    def __init__(self):
        self.statHEALTH = 10
        self.statATK = 3
        self.statDEF = 3

    def attack(self, enemyhealth):
        enemyhealth = (enemyhealth - self.statATK)
        return enemyhealth

    def defend(self):
        statDEF += 1


def main():
    James = Fighter()
    Keaton = Fighter()
    while James.statHEALTH > 0:
        print("Do you wish to attack or defend?")
        print("1. Attack")
        print("2. Defend")
        choice = input()
        if choice == "1":
            James.attack(Keaton.statHEALTH)
            print("You did", James.statATK, "damage!") 
            Keaton.attack(James.statHEALTH)
            print("Keaton has", Keaton.statHEALTH, "health left.")
            print("Keaton did", Keaton.statATK, "damage!")
            print("You have", James.statHEALTH, "health left.")
        #elif choice == "2":
            #James.defend()
            #Keaton.attack(James.statHEALTH)

main()
Was it helpful?

Solution

def attack(self, enemyhealth):
    enemyhealth = (enemyhealth - self.statATK)
    return enemyhealth

This would actually work if you changed your call to be

Keaton.statHEALTH = James.attack(Keaton.statHEALTH)

.. since you return the damage from the attack. Obviously this is ugly; repeating yourself is not good. Instead you can make attack look like:

def attack(self, other):
    other.statHEALTH -= self.statATK

And then just do

James.attack(Keaton)

when you call it.

OTHER TIPS

Maybe you could think of it differently. In your example, Fighter.attack() just returns the value of the enemies health after the attack. So really, it should be a method call on the enemy's object. You could add a method that decreases the fighter's health when they get attacked:

def attack(self, enemy):
    enemy.getAttacked(self.statATK)

def getAttacked(self, ATK):
    self.statHEALTH -= ATK

Try doing:

while James.statHEALTH > 0:
    #print statements
    if choice == "1":
        the_attack = James.attack(Keaton)

Then define your classes as:

class Fighter(object):
    def __init__(self):
        self.statHEALTH = 10
        self.statATK = 3
        self.statDEF = 3

    def attack(self,target):
        attack_details = target.takedamage(self.statATK)
        return attack_details

    def takedamage(self,dmg):
        modified_dmg = dmg-self.statDEF
        self.statHEALTH -= modified_dmg
        return modified_dmg

This has the added benefit of being easily expandable, e.g. you can add a hit table (for i in random.randint(1,100) if i < 20: #miss; elif 20 <= i < 80: #hit; elif 80<= i: #crit) or resistances for certain elements or add a flag that allows your defender to counterattack in their takedamage function (perhaps calling a new function getcountered to prevent infinite looping).

The problem isn't really that you can't pass things by references in Python. In fact, you always pass values by reference; Python never copies values unless you ask it to.

Or, more accurately, that whole C-based terminology is misleading in Python.

Anyway, when you do this:

James.attack(Keaton.statHEALTH)

This doesn't make a copy of the value in Keaton.statHEALTH, it passes a reference to the exact same value. So, when attack starts, your enemyhealth variable is a name for that value.

And then you do this:

enemyhealth = (enemyhealth - self.statATK)

… the enemyhealth - self.statATK returns a new value, and then you bind that new value to the enemyhealth name. This has no effect on any other names for the old value.

There are two ways to solve this.


First, you don't actually need to mutate anything here. You're already doing return enemyhealth at the end. That means the caller can get the new value that you calculated. So why not just use that?

Keaton.statHEALTH = James.attack(Keaton.statHEALTH)

And then everything works.


Of course in C++, you can mutate integer values. This seems a little silly when you think about it—turning the number 24 into the number 19 would break a lot of math, and probably make the universe stop working, and Python just isn't that powerful.

But you can easily build a type that can be sensibly mutated. In fact, you've already built one: a Fighter holds an integer health value, but can be changed to hold a different one instead. So, you could write this:

def attack(self, enemy):
    enemy.health = enemy.health - self.statATK

And then:

James.attack(Keaton)

Just as calling the old method with Keaton.health made enemyhealth into another reference to the same number, calling the new method with Keaton makes enemy into a reference to the same Fighter. If you just reassigned a new value to enemy, that wouldn't have any effect on the old value, the one that Keaton refers to. But if you change the value in-place, obviously Keaton is still referring to that now-changed value.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top