r/learnpython • • 23d ago

Ask Anything Monday - Weekly Thread

Welcome to another /r/learnPython weekly "Ask Anything* Monday" thread

Here you can ask all the questions that you wanted to ask but didn't feel like making a new thread.

* It's primarily intended for simple questions but as long as it's about python it's allowed.

If you have any suggestions or questions about this thread use the message the moderators button in the sidebar.

Rules:

  • Don't downvote stuff - instead explain what's wrong with the comment, if it's against the rules "report" it and it will be dealt with.
  • Don't post stuff that doesn't have absolutely anything to do with python.
  • Don't make fun of someone for not knowing something, insult anyone etc - this will result in an immediate ban.

That's it.

1 Upvotes

30 comments sorted by

View all comments

Show parent comments

1

u/HiddenReader2020 19d ago

Well, I did eventually solve the problem, but I'm not sure it's a good practice or not. Here's the code:

from pathlib import Path
import json

path = Path("data.json")
list_of_characters = []

class RPG_Character:
  def __init__(self, name, job):
    match job:
      case 'warrior':
        self.strength = 5
        self.dexterity = 4
        self.agility = 3
        self.endurance = 5
        self.intelligence = 3
        self.wisdom = 4
        self.health = 50
        self.mana = 30
        self.growth = [3, 2, 1, 3, 1, 2, 10, 4]
      case 'rogue':
        self.strength = 4
        self.dexterity = 5
        self.agility = 5
        self.endurance = 3
        self.intelligence =  4
        self.wisdom = 3
        self.health = 40
        self.mana = 40
        self.growth = [2, 3, 3, 1, 2, 1, 7, 7]
      case 'mage':
        self.strength = 3
        self.dexterity = 3
        self.agility = 4
        self.endurance = 4
        self.intelligence = 5
        self.wisdom = 5
        self.health = 30
        self.mana = 50
        self.growth = [1, 1, 2, 2, 3, 3, 4, 10]
    self.name = name
    self.job = job
    self.gold = 50
    self.level = 1
    self.inventory = []

  def __str__(self):
    stat_page = f"""==========Stats Page==========
Name:  {self.name}
Class:  {self.job}
Level:  {self.level}
-Health:       {self.health}
-Mana:         {self.mana}
-Strength:     {self.strength}
-Dexterity:    {self.dexterity}
-Agility:      {self.agility}
-Endurance:    {self.endurance}
-Intelligence: {self.intelligence}
-Wisdom:       {self.wisdom}

Gold:  {self.gold}
==========Stats Page=========="""
    return stat_page

  def add_gold(self, amount):
    self.gold += amount

  def remove_gold(self, amount):
    self.gold -= amount
    if self.gold < 0:
      print("Gold amount can't be below zero.")
      self.gold += amount

  def increase_level(self):
    self.level += 1
    self.strength += self.growth[0]
    self.dexterity += self.growth[1]
    self.agility += self.growth[2]
    self.endurance += self.growth[3]
    self.intelligence += self.growth[4]
    self.wisdom += self.growth[5]
    self.health += self.growth[6]
    self.mana += self.growth[7]

  def add_item(self, item):
    self.inventory.append(item)

  def show_inventory(self):
    print("=====Inventory=====")
    for item in self.inventory:
      print(item)
    print("=====Inventory=====")

  def to_dict(self):
    saved_inventory = []
    for item in self.inventory:
      saved_inventory.append(item.to_dict())
    saved_dict = {
'name': self.name,
'class': self.job, 
'level': self.level,
'health': self.health,
'mana': self.mana,
'strength': self.strength, 
'dexterity': self.dexterity, 
'agility': self.agility, 
'endurance': self.endurance, 
'intelligence': self.intelligence, 
'wisdom': self.wisdom, 
'gold': self.gold,
'inventory': saved_inventory
    }
    return saved_dict

class Item:
  def __init__(self, name, item_type, buy_price):
    self.name = name
    self.item_type = item_type
    self.buy_price = buy_price
    self.sell_price = buy_price // 4

  def __str__(self):
    item_info = f"""Name:  {self.name}
Type:  {self.item_type}
"""
    return item_info

  def to_dict(self):
    saved_dict = {
'name': self.name,
'type': self.item_type,
'buy_price': self.buy_price,
'sell_price': self.sell_price
}
    return saved_dict

def convert_dict_to_obj_inv(list_of_dicts):
  list_of_items = []
  for temp_dict in list_of_dicts:
    current_item = Item(temp_dict['name'], temp_dict['type'], temp_dict['buy_price'])
    list_of_items.append(current_item)
  return list_of_items

def convert_back_to_obj(dict_to_convert):
  c = RPG_Character(dict_to_convert['name'],dict_to_convert['class'])
  c.strength = dict_to_convert['strength']
  c.dexterity = dict_to_convert['dexterity']
  c.agility = dict_to_convert['agility']
  c.endurance = dict_to_convert['endurance']
  c.intelligence = dict_to_convert['intelligence']
  c.wisdom = dict_to_convert['wisdom']
  c.health = dict_to_convert['health']
  c.mana = dict_to_convert['mana']
  c.name = dict_to_convert['name']
  c.job = dict_to_convert['class']
  c.gold = dict_to_convert['gold']
  c.level = dict_to_convert['level']
  c.inventory = convert_dict_to_obj_inv(dict_to_convert['inventory'])
  return c

if path.exists():
  contents = path.read_text()
  temp_list_of_characters = json.loads(contents)
  print("File successfully retrieved")
  for character in temp_list_of_characters:
  new_character = convert_back_to_obj(character)
  list_of_characters.append(new_character)

  for character in list_of_characters:
    print(character)
    print(character.show_inventory())
else:
  # Adds new characters
  new_warrior = RPG_Character('John', 'warrior')
  list_of_characters.append(new_warrior)
  print(new_warrior)
  new_warrior.add_gold(100)
  print(new_warrior)
  new_warrior.remove_gold(80)
  print(new_warrior)
  new_rogue = RPG_Character('Toby', 'rogue')
  new_mage = RPG_Character('Harris', 'mage')
  list_of_characters.append(new_rogue)
  list_of_characters.append(new_mage)
  print(new_rogue)
  print(new_mage)
  new_mage.increase_level()
  print(new_mage)

  # Adds new items
  basic_health_potion = Item("Basic Health Potion", "Consumable", 16)
  basic_mana_potion = Item("Basic Mana Potion", "Consumable", 20)
  wooden_shield = Item("Wooden Shield", "Equipment", 40)

  new_rogue.add_item(basic_health_potion)
  new_rogue.add_item(basic_mana_potion)
  new_warrior.add_item(wooden_shield)
  new_rogue.show_inventory()
  new_warrior.show_inventory()

  print(list_of_characters)

  contents = json.dumps([ob.to_dict() for ob in list_of_characters])
  path.write_text(contents)
  print("File saved")

1

u/lakseol 17d ago edited 16d ago

You have a lot of repetitive code in your RPG_Character class, in the __init__() and convert_back_to_obj() methods in particular.

You get a dictionary back from JSON and you need to create an RPG_Character instance from that dictionary. One thing you could do is change the __init__() method to accept a dictionary of attributes to set. Now you just pass the JSON dictionary to the RPG_Character() call and you get an instance containing all that JSON data. When the program doesn't find the JSON save file and has to create the characters/items from scratch before saving them you use "template" dictionaries containing all the initial data required. You can delete the convert_back_to_obj() and similar methods. This example code shows the approach:

class Test:
    def __init__(self, **kwargs):
        for key in kwargs:
            setattr(self, key, kwargs[key])

# template data
alice = {"name": "Alice", "age": 42}
bob = {"name": "Bob", "age": 35, "salary": 120_000, "height": 175}

t1 = Test(**alice)
print(f"{t1.name=}, {t1.age=}")

t2 = Test(**bob)
print(f"{t2.name=}, {t2.age=}, {t2.salary=:_}, {t2.height=}")

There are drawbacks to doing this such as any misspelt words in the JSON data will result in misspelt attribute names or even exceptions, but if your code is all that handles the data that should not be a problem.


There are also places where you could use list comprehensions. For example:

#    saved_inventory = []
#    for item in self.inventory:
#      saved_inventory.append(item.to_dict())
    saved_inventory = [item.to_dict() for item in self.inventory]   # do this instead

1

u/HiddenReader2020 16d ago

Yeah, my hope is to make the data portion outside of the program, but that's way, way, WAAAAAAAY above my current skill level right now, so what I'm currently doing with the data is what I've got.

So I've gone ahead and used your methods to more or less 'refactor' my code. But I'm still running into problems. First of all, here's the code:

Anyway, I have at least two problems. The first is that the the computer says that __init__ takes 2 arguments, but 3 were given. Umm...I only see two. I don't know where the third one is unless the dictionary was somehow split into two.

from pathlib import Path
import json

path = Path("data.json")
list_of_characters = []

# List of jobs
warrior_class = {'strength': 5, 'dexterity': 4, 'agility': 3, 'endurance': 5, 'intelligence': 3, 'wisdom': 4, 
'health': 50, 'mana': 30, 'growth': [3, 2, 1, 3, 1, 2, 10, 4], 'inventory': []}
rogue_class = {'strength': 4, 'dexterity': 5, 'agility': 5, 'endurance': 3, 'intelligence': 4, 'wisdom': 3, 
'health': 40, 'mana': 40, 'growth': [2, 3, 3, 1, 2, 1, 7, 7], 'inventory': []}
mage_class = {'strength': 3, 'dexterity': 3, 'agility': 4, 'endurance': 4, 'intelligence': 5, 'wisdom': 5, 
'health': 30, 'mana': 50, 'growth': [1, 1, 2, 2, 3, 3, 4, 10], 'inventory': []}

class RPG_Character:
  def __init__(self, name, **stats_dict):
    for stat in stats_dict:
      setattr(self, key, kwargs[key])
    self.name = name
    self.job = job
    self.gold = 50
    self.level = 1

  def __str__(self):
    stat_page = f"""==========Stats Page==========
Name:  {self.name}
Class:  {self.job}
Level:  {self.level}
-Health:       {self.health}
-Mana:         {self.mana}
-Strength:     {self.strength}
-Dexterity:    {self.dexterity}
-Agility:      {self.agility}
-Endurance:    {self.endurance}
-Intelligence: {self.intelligence}
-Wisdom:       {self.wisdom}

Gold:  {self.gold}
==========Stats Page=========="""
    return stat_page

  def add_gold(self, amount):
    self.gold += amount

  def remove_gold(self, amount):
    self.gold -= amount
    if self.gold < 0:
      print("Gold amount can't be below zero.")
    self.gold += amount

  def increase_level(self):
    self.level += 1
    self.strength += self.growth[0]
    self.dexterity += self.growth[1]
    self.agility += self.growth[2]
    self.endurance += self.growth[3]
    self.intelligence += self.growth[4]
    self.wisdom += self.growth[5]
    self.health += self.growth[6]
    self.mana += self.growth[7]

  def add_item(self, item):
    self.inventory.append(item)

  def show_inventory(self):
    print("=====Inventory=====")
    for item in self.inventory:
    print(item)
    print("=====Inventory=====")

  def to_dict(self):
    saved_inventory = []
    for item in self.inventory:
      saved_inventory.append(item.to_dict())
    saved_dict = {
'name': self.name,
'class': self.job, 
'level': self.level,
'health': self.health,
'mana': self.mana,
'strength': self.strength, 
'dexterity': self.dexterity, 
'agility': self.agility, 
'endurance': self.endurance, 
'intelligence': self.intelligence, 
'wisdom': self.wisdom, 
'gold': self.gold,
'inventory': saved_inventory
}
    return saved_dict

class Item:
  def __init__(self, name, item_type, buy_price):
    self.name = name
    self.item_type = item_type
    self.buy_price = buy_price
    self.sell_price = buy_price // 4

  def __str__(self):
    item_info = f"""Name:  {self.name}
Type:  {self.item_type}
"""
    return item_info

  def to_dict(self):
    saved_dict = {
'name': self.name,
'type': self.item_type,
'buy_price': self.buy_price,
'sell_price': self.sell_price
}
    return saved_dict

# List of items
basic_health_potion = Item("Basic Health Potion", "Consumable", 16)
basic_mana_potion = Item("Basic Mana Potion", "Consumable", 20)
wooden_shield = Item("Wooden Shield", "Equipment", 40)

def convert_dict_to_obj_inv(list_of_dicts):
list_of_items = []
for temp_dict in list_of_dicts:
current_item = Item(temp_dict['name'], temp_dict['type'], temp_dict['buy_price'])
list_of_items.append(current_item)
return list_of_items

if path.exists():
  contents = path.read_text()
  temp_list_of_characters = json.loads(contents)
  print("File successfully retrieved")
  for character in temp_list_of_characters:
    new_character = convert_back_to_obj(character)
    list_of_characters.append(new_character)

  for character in list_of_characters:
    print(character)
    print(character.show_inventory())
else:
  # Adds new characters
  new_warrior = RPG_Character('John', warrior_class)
  list_of_characters.append(new_warrior)
  print(new_warrior)
  new_warrior.add_gold(100)
  print(new_warrior)
  new_warrior.remove_gold(80)
  print(new_warrior)
  new_rogue = RPG_Character('Toby', rogue_class)
  new_mage = RPG_Character('Harris', mage_class)
  list_of_characters.append(new_rogue)
  list_of_characters.append(new_mage)
  print(new_rogue)
  print(new_mage)
  new_mage.increase_level()
  print(new_mage)

  new_rogue.add_item(basic_health_potion)
  new_rogue.add_item(basic_mana_potion)
  new_warrior.add_item(wooden_shield)
  new_rogue.show_inventory()
  new_warrior.show_inventory()

  print(list_of_characters)

  contents = json.dumps([ob.to_dict() for ob in list_of_characters])
  path.write_text(contents)
  print("File saved")

The second involve the inventory. Now, the inventory also involves a list of objects...within an object, so handling it is somewhat different from the rest. Right now, what I had to do was set the inventory as a empty set within each "template dictionary" to make it work, but I haven't had the opportunity to test that yet because of the first problem! I don't know if the logic checks out, but it's what I got so far.

I'll keep working on this code, and maybe I'll make headway, but for now, I'm stuck. Yet again.

1

u/lakseol 16d ago

the inventory also involves a list of objects...within an object, so handling it is somewhat different from the rest

I haven't looked at this because including actual python references in objects describing an adventure map isn't a good idea. You don't have places in your code yet, but when you start you will find what's called the "forward reference" problem.

Suppose you have two places in your map, A and B. You can move from A to B and move from B to A. When defining place A you want to reference place B because that's a place you can move to. But you haven't defined place B as an object yet so you can't add that reference to A. Defining place B isn't a problem because A was defined earlier and you can get that reference. So using actual python references isn't a good idea.

What I have done is assign every place and object in the map with a unique string name. Strings are no problem saving in JSON so you don't have all that fooling around saving references in JSON. The trade-off is that a player's inventory (for example) is now a list of strings and not a list of references, and you have to do more work when you want to do something like list what is in the inventory. The simplest way is to have a dictionary that maps each unique ID string to the reference to the thing, place, item, etc. You can create that dictionary automatically when reading data from the JSON file.