1

So I made a game and I'm trying to create an inventory for the player, and I can't change a variable called "equipped_weapon". I've tried everything and or I get an Exception or when printing the player's inventory it shows the previous weapon. I'm a beginner too so if I'm missing something please tell me.

# Existing items
# Need a funtion to create a random item with random stats and a random name within a certain range.

yes_list = ["yes", "yeah", "sure", "why not"]

add_attack = "Attack:"
add_defense = "Defense:"
add_HP = "HP added:"
rarity = "Rarity"

weapon_rusty_sword = {
    "Name:": "Rusty Sword",
    add_attack: 1,
    add_defense: 0,
    rarity: 1
}

weapon_sword_wooden = {
    "Name:": "Wooden Sword",
    add_attack: 1.5,
    add_defense: 0,
    rarity: 1
}

weapon_sword_iron = {
    "Name:": "Iron Sword",
    add_attack: 2,
    add_defense: 0,
    rarity: 2
}

armor_chest_rusty_mail = {
    "Name:": "Rusty Mail",
    add_attack: 0,
    add_defense: 1,
    rarity: 1
}

armor_legs_adventurers = {
    "Name:": "Adventurer's Leggings",
    add_attack: 0,
    add_defense: 1,
    rarity: 1
}

armor_legs_rash_leggings = {
    "Name:": "Rash Leggings",
    add_attack: 0,
    add_defense: 0.5,
    rarity: 1
}

armor_head_rusty_cap = {
    "Name:": "Rusty Cap",
    add_attack: 0,
    add_defense: 0.5,
    rarity: 1
}
potion_hp = {
    "Name:": "HP Potion,",
    add_HP: 4
}

class Person:

    #global equipped_weapon
    equipped_weapon = weapon_rusty_sword
    ui_equipped_weapon = {"Equipped Weapon: {}".format(weapon_rusty_sword): ""}

    equipped_armor = {
    "Head Protection:": {"Name": armor_head_rusty_cap["Name:"], "Defense": armor_head_rusty_cap[add_defense]},
    "Chest Protection:": {"Name": armor_chest_rusty_mail["Name:"], "Defense": armor_chest_rusty_mail[add_defense]},
    "Legs Protection:": {"Name": armor_legs_rash_leggings["Name:"], "Defense": armor_legs_rash_leggings[add_defense]},
    }

    ATTACK = 1 + equipped_weapon[add_attack]
    DEFENSE = 1
    HP = 20

    gold = 10

    potions = {"Potions: ": [potion_hp["Name:"]]}
    ui_gold = {"Gold: ": gold}

    def __init__(self):
        self.name = "Cavalex"

    inv = [
        equipped_armor,
        ui_equipped_weapon,
        potions,
        ui_gold
    ]

    def see_inventory(self):
        for element in self.inv:
            for k, v in element.items():
                if type(v) == list:
                    print(k, ' '.join(v))
                else:
                    print(k, v)

#    def equip_weapon(self, new_weapon_code):
#        global equipped_weapon
#        eq_val = input(
#            "Do you want to equip this weapon? ->( {} )<-\nNote that your current weapon ( {} )will be discarded.".format(new_weapon_code["Name:"], self.equipped_weapon["Name:"]))
#        if eq_val.lower() in yes_list:
#            #del self.equipped_weapon
#            self.equipped_weapon = new_weapon_code
#            print("The weapon you had was discarded.")
#        else:
#            print("The new weapon was discarded.")





# See total amount of defense.
def defense_points():
    return sum(value["Defense"] for key, value in player.equipped_armor.items())


def add_gold(amount):
    player.gold += amount

# Adding to inventory
def new_potion_to_inv(potion):
    player.potions["Potions: "].append(potion["Name:"])


def equipp_weapon(new_weapon_code):
    # global player.equipped_weapon
    eq_val = input(
        "Do you want to equip this weapon? ->( {} )<-\nNote that your current weapon ( {} )will be discarded.".format(
            new_weapon_code["Name:"], player.equipped_weapon["Name:"]))
    if eq_val.lower() in yes_list:
        del player.equipped_weapon
        player.equipped_weapon = new_weapon_code
        print("The weapon you had was discarded.")
    else:
        print("The new weapon was discarded.")

player = Person()

# game loop
while True:
    print("Your name: ", player.name)
    player.see_inventory() # Can't put print this function, else will print "None" in the end.
    print("\nThis is your total armor defense: {}".format(defense_points()))
    print()
    new_potion_to_inv(potion_hp)
    player.see_inventory()
    print(player.ATTACK)
    print()
    print("Now we are changing your weapon.")
    equipp_weapon(weapon_sword_iron)
    print()
    print(player.ATTACK)
    player.see_inventory()



    break

and this is the output:

C:\Users\mateu\AppData\Local\Programs\Python\Python37-32\python.exe C:/Users/mateu/PycharmProjects/Trabalhos_Python/TESTING_THINGS/testing_armor_without_weapons_and_armor.py
Your name:  Cavalex
Head Protection: {'Name': 'Rusty Cap', 'Defense': 0.5}
Chest Protection: {'Name': 'Rusty Mail', 'Defense': 1}
Legs Protection: {'Name': 'Rash Leggings', 'Defense': 0.5}
Equipped Weapon: {'Name:': 'Rusty Sword', 'Attack:': 1, 'Defense:': 0, 'Rarity': 1} 
Potions:  HP Potion,
Gold:  10

This is your total armor defense: 2.0

Head Protection: {'Name': 'Rusty Cap', 'Defense': 0.5}
Chest Protection: {'Name': 'Rusty Mail', 'Defense': 1}
Legs Protection: {'Name': 'Rash Leggings', 'Defense': 0.5}
Equipped Weapon: {'Name:': 'Rusty Sword', 'Attack:': 1, 'Defense:': 0, 'Rarity': 1} 
Potions:  HP Potion, HP Potion,
Gold:  10
2

Now we are changing your weapon.
Do you want to equip this weapon? ->( Iron Sword )<-
Note that your current weapon ( Rusty Sword )will be discarded.yes
Traceback (most recent call last):
  File "C:/Users/mateu/PycharmProjects/Trabalhos_Python/TESTING_THINGS/testing_armor_without_weapons_and_armor.py", line 158, in <module>
    equipp_weapon(weapon_sword_iron)
  File "C:/Users/mateu/PycharmProjects/Trabalhos_Python/TESTING_THINGS/testing_armor_without_weapons_and_armor.py", line 139, in equipp_weapon
    del player.equipped_weapon
AttributeError: equipped_weapon

Process finished with exit code 1
2
  • This is a too long piece of code. Could you please reduce it to a minimal working example (MWE) so that it would ease the diagnistic? Commented Sep 14, 2018 at 15:41
  • I see you worked my code into your see_inventory cool to see the whole thing you are working on, going on this problem in a minute Commented Sep 14, 2018 at 16:10

1 Answer 1

3

The problem is that you made equipped_weapon a class attribute, not an instance attribute. del instance.attribute only deletes instance attributes, it doesn't even look for class attributes.

If the goal is just to change the instance, you can just remove the del entirely; the next line will add (if no instance attribute exists, shadowing the class attribute) or replace (if an instance attribute already exists) the instance's specific weapon.

If the goal is to change the class, you need to change the assignment to operate on the class:

type(player).equipped_weapon = new_weapon_code

or explicitly naming the class:

Player.equipped_weapon = new_weapon_code

Either way, the del isn't necessary; assigning the new value replaces the old one, implicitly dropping the reference to the old value in the same way that del does explicitly. It would be perfectly legal to do del type(player).equipped_weapon or del Player.equipped_weapon to delete the class attribute specifically, but it's pointless; whatever the desired behavior, simple assignment to either the class or the instance attribute will discard the old reference anyway.

UPDATE: As you noted, removing the del prevents the exception, but your output (from see_inventory) never changes. That's because see_inventory is looking at Player.inv, which in turn contains a reference to the original value of Player.ui_equipped_weapon. Problem is, while ui_equipped_weapon and equipped_weapon are initialized based on the same default weapon, they're not synced to one another; changing one has no effect on the other (and therefore changing equipped_weapon, on the class or instance, doesn't affect inv, so see_inventory never changes).

Really, the solution here is to make a sane class. None of your class attributes make sense; they're all logically instance attributes and should be defined as such. In a few cases, they're just convenience-oriented transformations of another attribute; in those cases, they shouldn't be attributes at all, but rather propertys, which dynamically recompute their value based on another attribute, preventing them from getting out of sync.

Here's a really quick (not tested, might have small typos) rewrite that moves all your justified attributes to the instance, and converts the rest to read-only properties that derive their value from another instance attribute:

import copy

# Factored out to avoid repetition
def clean_armor(armor):
    return {k.rstrip(':'): v
            for k, v in armor.items() if k in {'Name:', 'Defense:'}}

class Person:
    DEFAULT_WEAPON = weapon_rusty_sword
    DEFAULT_ARMOR = {
        "Head Protection:": clean_armor(armor_head_rusty_cap),
        "Chest Protection:": clean_armor(armor_chest_rusty_mail),
        "Legs Protection:": clean_armor(armor_legs_rash_leggings),
    }

    def __init__(self, name="Cavalex",
                 equipped_weapon=DEFAULT_WEAPON, equipped_armor=DEFAULT_ARMOR,
                 base_attack=1, base_defense=1,
                 hp=20, gold=10,
                 potions=(potion_hp["Name:"],)):
        self.name = name
        # deepcopy is defensive (so changing defaults doesn't retroactively
        # alter existing instance); can be removed if you guarantee defaults
        # won't be changed, or you want changes to defaults to affect
        # Players still wielding default items
        self.equipped_weapon = copy.deepcopy(equipped_weapon)
        self.equipped_armor = copy.deepcopy(equipped_armor)
        self.base_attack = int(base_attack)
        self.base_defense = int(base_defense)
        self.HP = int(hp)
        self.gold = int(gold)
        # potions only used as dictionary, but it's silly to store it as one
        # Just store list of potion names in protected attribute,
        # property can construct the expected dict on demand
        self._potions = list(potions)

    @property
    def ATTACK(self):
        return self.base_attack + self.equipped_weapon[add_attack]

    @property
    def DEFENSE(self):
        return self.base_defense + sum(armor['Defense'] for armor in self.equipped_armor.values())

    @property
    def potions(self):
        return {"Potions: ": self._potions}

    @property
    def ui_gold(self):
        return {"Gold: ": self.gold}

    @property
    def ui_equipped_weapon(self):
        return {"Equipped Weapon: {}".format(self.equipped_weapon): ""}

    @property:
    def inv(self):
        return [
            self.equipped_armor,
            self.ui_equipped_weapon,
            self.potions,
            self.ui_gold,
        ]

    def see_inventory(self):
        for element in self.inv:
            for k, v in element.items():
                if isinstance(v, list):
                    print(k, ' '.join(v))
                else:
                    print(k, v)

This still has a lot of questionable choices (inconsistent attribute/property naming; weird key names in the dicts that seem to have been chosen to make display easier, but make all non-display uses more annoying; using top-level functions for things that only make sense as instance methods, etc.), but I preserved those quirks to keep it a drop-in replacement for your existing class (but with all per-player attributes set on the instance, not the class, and all derived attributes computed via propertys rather than set statically, since independent static attributes increase the odds of the base and derived attributes getting out of sync).

I preserved the behavior of allowing you to create a player without providing any arguments, though the name should really be a non-optional argument (unless "Cavalex" is such a common name that you can safely assume most if not all players have that name).

I realize it seems kind of tedious to spell out __init__ and define propertys when you can just copy over the data from attribute to derived attribute, but in terms of code that you can actually use in a meaningful way without extreme repetition (and the accompanying risk of typos and logic errors), understand the behavior of, etc., this is the only way that makes sense; a class that is 99% class attributes (and all the methods effectively ignore per-instance state) may as well have just been a bunch of globals and top-level functions, not a class at all.

The reason you make classes is to allow you to create many instances of the class with different attributes, but shared behaviors; making the class a quasi-singleton by using purely class-level state makes the class pointless, at least in Python where the multiparadigm language design means the singleton pattern is rarely necessary, if ever (and using class level state defined at definition time removes the sole advantage of the pattern, deferring initialization of the singleton until it's needed).

Sign up to request clarification or add additional context in comments.

5 Comments

I took the delete and did it like you said but it prints the old weapon instead of the new one and it gives no error. What should i do?
@Cavalex: Your posted code never prints the weapon aside from in the prompt for changing to a new one, so I have no idea where your problem is. As long as all of your code consistently works with player.equipped_weapon, this will work fine; you'd have a problem only if you mixed use of Player.equipped_weapon (class attr, unchanged) and player.equipped_weapon (instance attribute if set, changes).
Checking further, it looks like Player.see_inventory might be your problem. It's looking at the contents of Player.inv, which contains the value of ui_equipped_weapon at the time the class was defined. But changing equipped_weapon (on class or instance) doesn't change ui_equipped_weapon or inv; there is no live association between the attributes (inv does store a reference to the mutable ui_equipped_weapon, so if ui_equipped_weapon were updated in place, not reassigned, see_inventory would change, but equipped_weapon is unrelated.
@Cavalex: I've updated my answer to address the cause of your seemingly static/unchanged weapon state (namely, your use of multiple attributes with the same initial values, but with no ongoing connection, so updating one doesn't change any of the others). Hope it helps.
Thanks man! I'll look into the code and play around it, that really helped!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.