I have been hacking away at a dungeon crawler for a few months now. The player has a pack, self.pack =[], which eventually contains a list of weapon objects that are bought or accumulated during the game. The items are chosen from a dictionary and then appended:
def blacksmith_sale(self):
sale_items_dict = {'1': short_sword,
'2': broad_sword,
'3': quantum_sword
}
...
sale_item_key = input("Enter item number to buy, your pack (I)nventory, or (E)xit the
blacksmith:").lower()
...
sale_item = (sale_items_dict[sale_item_key])
if self.gold >= sale_item.sell_price and self.level >= sale_item.minimum_level:
print(f"You buy a {sale_item}")
self.gold -= sale_item.buy_price
self.pack.append(sale_item)
time.sleep(1)
self.hud()
continue
Then I have an inventory function to view self.pack contents:
def inventory(self):
while True:
if len(self.pack):
self.hud()
print("Your pack contains:")
print("Item Quantity")
print()
self.pack.sort(key=lambda x: x.damage_bonus) # sort each weapon by damage_bonus
stuff_dict = {}
for item in self.pack:
stuff_dict[item] = self.pack.count(item)
for key, value in stuff_dict.items():
print(key, 's', ': ', value, sep='') # add an 's' and show quantity
print()
else:
print("Your pack is empty")
...
This seems to work well while the game is running. However, after saving and reloading the player object with pickle, I am having weird sorting issues. While the game is running I get this kind of output:
Your pack contains:
Item Quantity
Short Swords: 1
Broad Swords: 1
However, after saving and reloading, if I were to append a 'short_sword' instance of the Sword class to self.pack, I get something like this:
Item Quantity
Short Swords: 1
Short Swords: 1
Broad Swords: 1
The problem gets ever more weird as more items are added and removed and the player is saved/loaded.
CodePudding user response:
self.pack.append(sale_item)
In above statement sale_item refers an object stored in a dictionary. and when you append sale_item/short_sword again and again, you are actually appending same object multiple times.
Hence, in below loop, the dict will treat all short_swords as same. and work fine
for item in self.pack:
stuff_dict[item] = self.pack.count(item)
But when you dump player profile to persistence and re-load, it will create different object for different entry from file, in pack list. and when added to the dictionary, they will be treated different. so short_sword will be displayed multiple times.
Example:
>>> class test:
... def __repr__(self):
... return "test"
...
>>> t1=test()
>>> t2=test()
>>>
>>> # Case 1: same object added multiple times
>>> dict={}
>>> dict[t1]=1
>>> dict[t1]=2
>>>
>>> print(dict)
{test: 2} <-- Note second one replaced first
>>>
>>> # Case 2: different objects added
>>> dict={}
>>> dict[t1]=1
>>> dict[t2]=2
>>>
>>> print(dict)
{test: 1, test: 2} <-- Note both objects added.
>>>
CodePudding user response:
The items in the inventory are instances of custom classes. By default, such instances are hashable, that is they can be used as keys in dictionaries. However by default they also compare unequal, so if pack
is
[short_sword, dagger, short_sword]
then your dictionary will end up with three keys.
A simple workaround is to use collections.Counter to build stuff_dict
:
from collections import Counter
...
# I use name here, but use whatever attribute should be
# used to mark instances as being the same thing.
stuff_dict = Counter(item.name for item in self.pack)
The resulting object will be a dict mapping name
(or whatever) to the count of items in self.pack
with that name.
Another approach would be to fix the way self.pack.count
works. As it is, it will return 1
for each element in the list as they all compare unequal. Adding an __eq__
method to the Item
class would fix this:
class Item:
...
def __eq__(self, other):
# See comment on using "name" in the Counter example
return self.name == other.name
This will result in self.pack.count(item)
returning the correct result, but instances will no longer be hashable unless you add a __hash__
method, so the key in stuff_dict
would been to be the item's name
(or equivalent). Adding __eq__
and __hash__
methods can lead to some tricky bugs if done incorrectly; I would be inclined to avoid this approach for now.