I'm currently working on a text adventure in python (this language just because it's the one I know), and I'm finding that creating and loading savefiles removes some of the mecahnics I've made. I'll include the problematic code here, rather than all of elements that work fine. it's mainly to do with classes and how instances are 'pickled'.
Here are some of the classes I've created:
class Player:
def __init__(self, name):
self.sack = []
self.kit = []
self.equipped = []
self.log = []
self.done = []
class Weapon:
def __init__(self, name, price, minattack, maxattack):
self.name = name
self.price = price
self.minattack = minattack
self.maxattack = maxattack
class Food:
def __init__(self, name, price, healthadd):
self.name = name
self.price = price
self.healthadd = healthadd
class Quest:
def __init__(self, name, requirement, num, gold, npc, place, give_text, prog_text, comp_text, done_text):
self.name = name
self.requirement = requirement
self.num = num
self.score = 0
self.gold = gold
self.npc = npc
self.place = place
self.give_text = give_text
self.prog_text = prog_text
self.comp_text = comp_text
self.done_text = done_text
The instances in the Player class I've included here are just the ones that are appended by other mechanics with Weapons, Food and Quests. The code includes regions where Weapons, Food and Quests are populated (though working on a separate assets file might tidy things up a bit).
Here's how the save/load functions work currently:
def save(lastplace):
clear()
with open('savefile', 'wb') as f:
PlayerID.currentplace = lastplace.name
pickle.dump(PlayerID, f)
print("Game saved:\n")
print(PlayerID.name)
print("(Level %i)" % (PlayerID.level))
print(lastplace.name)
print('')
option = input(">>> ")
goto(lastplace)
def load():
clear()
if os.path.exists("savefile") == True:
with open('savefile', 'rb') as f:
global PlayerID
PlayerID = pickle.load(f)
savedplace = PlayerID.currentplace
savedplace = locations[savedplace]
goto(savedplace)
else:
print("You have no save file for this game.")
option = input('>>> ')
main()
It's worth noting that upon entry to the game, PlayerID (you) becomes a global variable. You might begin to see some of the issues here, or rather the overarching issue. Essentially, the pickling process serialises all of the possible class types stored in lists within the class of Player just get appended by their names, thus removing their class properties when loaded back into the game.
Is there a pythonic way to ensure that class instances are saved for a future load so that they can still behave as classes, particularly when stacked inside the class of Player? I appreciate this is more of an editorial rather than a question by its length, but any help would be hugely appreciated.
Related
I am making a text based adventure game in python. Once the game begins, I would like to create an instance of a class called "Character" which is the player's character object. I would like the user to be able to choose the race of the character they want to play. So far I have:
class Race:
def __init__(self, name, passive, hp):
self.name = name
self.passive = passive
self.hp = hp
and
class Lizard(Race):
def __init__(self, name, passive, hp):
super().__init__(name, passive, hp)
self.name = 'Lizardman'
self.passive = 'Regrowth'
self.hp = 20
def regrowth(self):
if 0 < self.hp <= 18:
self.hp += 2
and
def race_select():
races = ['Lizard']
while True:
for i, j in enumerate(races):
print(f"[{i + 1}]", j)
choice = int(input('Pick a race:'))
if choice <= len(races):
print('You are a ', races[choice - 1])
return races[choice - 1]
else:
continue
If I understand correctly, if I wanted the race to be a Lizard, I would still have to do
character = Lizard('Lizardman', 'Regrowth', 20)
Is there an easy way to let the user choose the race and the object to be created accordingly? Thanks
A simple solution would be to map a name to a class using a dictionary. As a simple example:
race_map = {"lizard": Lizard,
"human": Human} # I'm adding a theoretical other class as an example
choice = input('Pick a race:')
race_initializer = race_map.get(choice, None) # Get the chosen class, or None if input is bad
if race_initializer is None:
# They entered bad input that doesn't correspond to a race
else:
new_creature = race_initializer(their_name, their_passive, their_hp)
new_creature is now the new object of the chosen class.
You may want to standardize the input using choice.lower() to ensure that capitalization doesn't matter when they enter their choice.
I changed it to allow for specifying a race by a string name instead of a number. If you wanted a number, you could keep your list, but apply the same idea. Something like:
race_list = races = [('Lizard', Lizard), ('human', Human)]
choice = int(input('Pick a race:'))
try:
race_initializer = race_list[choice][1] # 1 because the class object is the second in the tuple
new_creature = race_initializer(their_name, their_passive, their_hp)
except IndexError:
# Bad input
I included the name in the race_list so that you can loop over the list and print out index->name associations for the user to pick from.
You may also want to use a more robust structure than a plain tuple to store name->initializer mappings, but it works well in simple cases.
I am currently developing a short text-based adventure so I can learn how to use Classes within Python. As part of this, I am trying to create a combat system where the player could choose an NPC to attack.
The aim is that the player can enter the name of the NPC and the weapon they want to use. A method in the target's class will then be called, to lose health based on the damage of the weapon.
My current code is below:
class npc:
def __init__(self, name, alliance):
self.name = name
self.alliance = alliance
def loseHealth(self, health, dmg):
self.dmg = dmg
self.health = self.health - dmg
def usePotion(self, health, pType):
if pType == "great":
self.health = min(self.health + 50,self.maxHealth)
elif pType == "normal":
self.health = min(self.health + 25,self.maxHealth)
else:
pass
def attack(self, target, weaponDmg):
if target in npcList:
target.loseHealth(self.health, weaponDmg)
class human(npc):
maxHealth = 100
health = 100
def __init__(self, name, alliance):
super().__init__(name, alliance)
class orc(npc):
maxHealth = 200
health = 200
def __init(self, name, alliance):
super().__init__(name, alliance)
weaponDmg = {'sword':10,'axe':20}
alice = human("alice","good")
bob = orc("bob","evil")
npcList = [alice, bob]
target = input("Enter Target:")
weapon = input("Enter weapon:")
for x in range(3):
alice.attack(target,weaponDmg[weapon]) #using alice temporarily until I have a person class sorted
print(target.health)
The simple and pythonic answer is to use a dict of NPCs keyed by name, the same way you’re already doing it with weapons:
npcs = {‘alice’: alice, ‘bob’: bob}
target = input("Enter Target:")
weapon = input("Enter weapon:")
for x in range(3):
alice.attack(npcs[target], weaponDmg[weapon])
print(target.health)
And if you want to look up the attacking NPC by user-supplied name as well as the attackee, you can do the same thing there:
npcs[attacker].attack(npcs[target], weaponDmg[weapon])
If you really want to do this inside the attack method you can keep passing in target as a name (string) and do this:
if target in npcs:
npcs[target].loseHealth(self.health, weaponDmg)
... but that probably isn’t a very good design. It means you’re sharing a global variable, and your NPC objects all “know” about that global dict and all the NPCs in it, which doesn’t seem like part of their responsibility.
You can make this a little less repetitive by creating the dict with a comprehension:
npcs = {npc.name: npc for npc in (alice, bob)}
... or by just creating them directly in the dict instead of in variables that you’re probably never going to otherwise use:
npcs = {}
npcs[‘alice’] = human("alice","good")
npcs[‘bob’] = orc("bob","evil")
You can call a method on an instance by using getattr, here is an example:
>>> class Test:
... def my_method(self, arg1, arg2):
... print(arg1, arg2)
...
>>> t = Test()
>>> getattr(t, 'my_method')('foo', 'bar')
foo bar
So I generated a bunch of places with the Place class, then in the Player class I tried to make a method that looks at the current location and look at the connected locations through the places list I made to travel west, however since I am very new the OOP I am not sure how to give access to the Player function to the list made in main()
def main():
import random
places = []
places.append(Place("Boston", "Sunny - 55°F", ("Worcester", None), "645,966", "Marty Walsh"))
places.append(Place("Worcester", "Sunny - 64°F", ("Springfield", "Boston"), "182,544", "Joseph Petty"))
places.append(Place("Springfield", "Sunny - 67°F", ("Pittsfield", "Worcester"), "153,703", "Domenic Sarno"))
places.append(Place("Pittsfield", "Sunny - 63°F", (None, "Springfield"), "44,057", "Linda Tyer"))
This is where the player is generated in main() as well:
player = Player(name, random.choice(places))
Here is the Place class constructor:
class Place(object):
def __init__(self, name, weather, cl, pop, mayor):
self.name = name
self.weather = weather
self.connectedLocation = cl
self.population = pop
self.mayor = mayor
Here is the Player class constructor:
class Player(object):
def __init__(self, name, curLoc):
self.name = name
self.curLoc = curLoc
Later in the Player class I attempted to make this method to no avail, since to my dismay the class cannot access the list of places made in main()
def goWest(self):
for place in places:
if self.curLoc.connectedLocation[0] == place.name:
self.curLoc = place
You need to pass the places list to the goWest function by adding a places parameter to it. It would look something like this:
def go_west(self, places):
for place in places:
if self.cur_loc.connected_location[0] == place.name:
self.cur_loc = place
break
I added the break statement because I am assuming that once you find the current location you don't need to keep iterating of the list.
class player(object):
def __init__(self):
self.health = 100
self.stats = {'STR': 0,
'DEF':0}
class create_character(object):
def choose_class(self):
inp = input("Please choose a class!: ").lower()
s = ("Knight, Archer, or Mage")
print(s)
if inp == 'knight':
self.stats['STR'] = 15
self.stats['DEF'] = 15
print(self.stats)
The problem I'm facing currently, is that when I reference self.stats under create_character, it tells me that specific class doesn't have a 'self.stats'. If I try player.self.stats or player.stats, it tells me the same thing, that the class doesn't have those attributes. How can I reference a dictionary or a variable from another class, and change its properties. Also, when I print self.health, or self.stats, or self.name or whatever properties the player class hold, it gives me a bunch of unneeded information. Is there a way to exclude the extra information?
I believe that this will do the job that you are looking for:
class player(object):
def __init__(self):
self.health = 100
self.stats = {'STR': 0,
'DEF':0}
def choose_class():
inp = input("Please choose a class (Knight, Archer, or Mage): ").lower()
if inp == 'knight':
# create the player object
p = player()
# changes the values for the attributes in the player object
p.stats['STR'] = 15
p.stats['DEF'] = 15
print(p.stats)
# calls the function to choose the class
choose_class()
This is not really a good coding style though, so I would recommend following an OOP Python tutorial.
So I was having some trouble with some code I'm doing for a basic game that I'm writing in my learning of Python (original question here if it helps).
After a lot of playing around with it, I realized my problem. I don't actually know how to do a "has-a" when trying to let one object (a character) "have" an object of another class (such as a weapon).
If I want, say a Char (character) called dean to do an action specific to the weapon he has. How would I give that object (the Char dean) an object of class "Weapon"?
Thank you.
Edit:
I've found that I can do this by passing the "Weapon" in question as an argument. i.e.:
dean = Char("Dean", hanzo_blade)
Then having Char init with (self, name, weapon).
However, I want the user to choose, from a selection, which weapon the character gets. So I'm not sure if the content of the Char(_____) can be determined on a dynamic basis from user input. If it's possible, how can I do that? If not, what do I do?
Thanks again.
Edit 2: Here is the pertinent code:
class Char(object):
def __init__(self, name):
self.name = name
self.hp = 300
self.mp = 10
self.strn = 1
self.dex = 1
self.armor = 0
self.xp = 0
self.items = []
self.spells = []
self.weapon = sword() # Assume sword is the default. Alternatively, how might I let this default to nothing?
class Weapon(Equip):
impact = 1
sharp = 1
def __init__(self, name):
self.name = name
hanzo_blade = Weapon("Hanzo Blade")
hanzo_blade.wgt = 3
hanzo_blade.impact = 1
hanzo_blade.sharp = 9
dean = Char("Dean")
dean.strn = 3
dean.dex = 8
If hanzo_blade is one option, how could I let the player, for instance, select that Weapon for the Char dean?
Are you aware of the raw_input builtin? (input in Python 3)
# We assume we already have sword, fists, and stern_glare objects
# representing weapons.
weapons = {'sword': sword, 'fists': fists, 'stern glare': stern_glare}
# Prompt for a choice, and keep prompting until you get a valid choice.
# You'll probably want a more user-friendly prompt.
valid_choice = False
while not valid_choice:
weapon_choice = raw_input('Select your weapon')
valid_choice = weapon_choice in weapons
# Create the character.
dean = Char('Dean', weapons[weapon_choice])