Variable X not updating when variables that should effect X change

Viewed 2468

I am a beginner at python. As most beginners, I am trying to make a text based rpg. I have finally gotten to the leveling up system and am at another roadblock. When the character levels up, they get to allot x skill points to a skill (add x to a variable x times). two of these variables effect the health of the character, however, when these two variables are changed by the user, the health variable stays the same. I have simplified my code for ease of reading (as my prose above is not that clear to begin with lol):

class Char:

    def __init__(self, x, y):
        self.str = x
        self.con = y
        self.hp = (self.con + self.str) / 2


player = Char(20, 20)


def main(dude):
    print("strength:     " + str(dude.str))
    print("constitution: " + str(dude.con))
    print("hp: " + str(dude.hp))
    print("------")
    action = input("press 1 to change str, 2 to change con")
    if action == "1":
        dude.str = dude.str + 10
        main(dude)
    elif action == "2":
        dude.con = dude.con + 10
        main(dude)
    else:
        main(dude)


main(player)

although these variables can increase by increments of ten in this situation, the hp remains at 20

how can I remedy this problem?

Thanks!

5 Answers

Whenever the Char's attributes are updated, the code needs to re-compute the HP.
All this sort of code is best put inside the Char object:

class Char:
    def __init__(self, x, y):
        self.str = x
        self.con = y
        self.setHP()

    def __str__(self):
        text = "strength:     " + str(self.str) + "\n" +\
               "constitution: " + str(self.con) + "\n" +\
               "hp:           " + str(self.hp)
        return text

    def setHP(self):
        self.hp = (self.con + self.str) / 2

    def adjustStr(self, amount):
        self.str += amount
        self.setHP()

    def adjustCon(self, amount):
        self.con += amount
        self.setHP()


def main(dude):
    print(str(dude))
    print("------")
    action = input("press 1 to change str, 2 to change con")
    if action == "1":
        dude.adjustStr(10)
        main(dude)
    elif action == "2":
        dude.adjustCon(10)
        main(dude)
    else:
        main(dude)


player = Char(20, 20)

main(player)

The hp attribute does not change when the str or con change. The only time it is set is in the constructor. You could define an update method to Char like this:

class Char:
    def __init__(self, x, y):
        self.str = x
        self.con = y
        self.update()
    def update(self):
        self.hp = (self.con + self.str) / 2

and call it at the end of main:

def main(dude):
    print("strength:     " + str(dude.str))
    print("constitution: " + str(dude.con))
    print("hp: " + str(dude.hp))
    print("------")
    action = input("press 1 to change str, 2 to change con")
    if action == "1":
        dude.str = dude.str + 10
        main(dude)
    elif action == "2":
        dude.con = dude.con + 10
        main(dude)
    else:
        main(dude)
    dude.update()

Because you evaluate the hp attribute only in the __init__() method, i. e. only in your statement

player = Char(20, 20)

The most quick fix (not a very nice one, but a very simple) is to create a new instance of Char after each change:

if action == "1":
    dude.str = dude.str + 10
    dude = Char(dude.str, dude.con)     # new dude (with current str and con)
    main(dude)
elif action == "2":
    dude.con = dude.con + 10
    dude = Char(dude.str, dude.con)     # new dude (with current str and con)
    main(dude)

You have to update the hp each time, since you used self.str and self.con only once to compute hp and stored it independently from self.str and self.con. You could use a get_hp for that.

class Char:

    def __init__(self, x, y):
        self.str = x
        self.con = y

    def get_hp(self):
        return (self.con + self.str) / 2

player = Char(20, 20)


def main(dude):
    print("strength:     " + str(dude.str))
    print("constitution: " + str(dude.con))
    print("hp: " + str(dude.get_hp()))
    print("------")
    action = input("press 1 to change str, 2 to change con")
    if action == "1":
        dude.str = dude.str + 10
        main(dude)
    elif action == "2":
        dude.con = dude.con + 10
        main(dude)
    else:
        main(dude)

I don't agree with the other answers, as the best way for me would have been to use a property. Properties work just like getters, but has the benefit to allow it to be dynamically calculated, which is exactly what you are looking for.

class Char:

    def __init__(self, x, y):
        self.str = x
        self.con = y

    @property
    def hp(self):
        # todo handle negative hp!
        return (self.con + self.str) / 2.


def test_hp():
    player = Char(20, 20)
    assert player.hp == 20


def test_hp_with_changes_to_con_or_str():
    player = Char(20, 20)
    player.con += 10
    assert player.hp == 25
    player.str += 10
    assert player.hp == 30

Also, as you can see, it is better to just write a test than to test stuff with output to stdout.

scratch_12.py ..                                                         [100%]

========================== 2 passed in 0.20 seconds ===========================

The above hp property is read-only, but you can make a setter for it as well if needed. Read up on property

PS: also, use full names, like Character as it is better to be explicit than implicit :)

Related