How to access a list outside a class? - python

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.

Related

Video game save/load problems with python pickle (not loading instances)

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.

Need function to assign object name in class

I'm trying to make a class, Player, and I'd like the user to create objects, say to add to their team.
Every tutorial will have something like this to create new players, say Jordan.
class Player:
def __init__(self, name):
self.name = name
p1 = Player('Jordan')
p2 = Player('Kobe')
But I want the user to have the ability to create new players, and they're not going to code, right?
And I would rather the object variable be just the player name, like, "Jordan", or "Kobe".
So if everything was manual, I could say,
jordan = Player('Jordan')
kobe = Player('Kobe')
So to come up with a function to have users create players, what should it look like? And what would the variable be? Any way to get it assigned as the player name? Or at least a serialized number like p1, p2, p3, ...?
def create_player():
new_player = input("Which player would you like to create? ")
name_of_variable_for_player = Player(new_player)
Ok, so follow on question.
What happens when you just have a static variable in the create function?
def create_player():
p = Player(input("What player would you like to make? ")
Use a dict instead of dynamic variables. For more details see How do I create a variable number of variables?
In this case that might look something like this:
class Player:
def __init__(self, name):
self.name = name
def __repr__(self): # Adding this for better output
cls_name = type(self).__name__
return '{}({!r})'.format(cls_name, self.name)
my_team = {}
name = input("Which player would you like to create? ")
my_team[name] = Player(name)
print(my_team)
Example run:
Which player would you like to create? Shaq
{'Shaq': Player('Shaq')}
How to turn that into a function might vary based on what you're trying to do, but you could start here:
def add_player_to_roster(roster):
name = input("Which player would you like to create? ")
roster[name] = Player(name)
my_team = {}
add_player_to_roster(my_team)
print(my_team)

Variables dont update to new values between classes

I am making a basic RPG style game. I have made different classes for the various parts of the code, one for each of the main items involved (hero, door, monsters etc.)
For both the hero and door, i assign them random locations, shown below in the code, but for the door I run a while loop which makes sure that the door is a certain distance from the hero (using pythagorus).
However the while loop in the door class won't work as it always uses a value of 0 for both heroC and heroR (row and column of the hero). I am relatively new to using classes, but it doesnt seem to make sense as in HeroLocation I assign a random integer to these variables, and HeroLocation is called before DoorLocation.
Any help would be greatly appreciated!!
class Hero(Character):
def __init__(self):
super(Hero, self).__init__(10, 10, 1, 1, 0, 1)
self.herolocations = list(range(1,6)) + list(range(10,14))
self.heroC = 0
self.heroR = 0
def HeroLocation(self):
#place hero
self.heroC = random.choice(self.herolocations)
self.heroR = random.choice(self.herolocations)
class Door:
def __init__(self):
self.hero = Hero()
self.doorC = 0
self.doorR = 0
def DoorLocation(self):
while ((self.hero.heroC-self.doorC)**2+(self.hero.heroR-self.doorR)**2) <= 128:
self.doorC = random.randint(1, 13)
self.doorR = random.randint(1, 13)
class game:
def __init__(self, parent):
self.hero = Hero()
self.door = Door()
def MakeMap(self):
self.hero.HeroLocation()
self.herol = self.Main_game.create_image(15+30*self.hero.heroC,15+30*self.hero.heroR, image = self.heroimage)
self.door.DoorLocation()
self.doorl = self.Main_game.create_image(15+30*self.door.doorC,15+30*self.door.doorR, image = self.exitdoor)
NB there is a lot more code, but i have only posted what i felt was the relevant stuff, if you need more to crack the puzzle message me!
You are not calling the good Hero instance in Door.DoorLocation.
Btw I really advice you to change class & methods name following Pep 8.
In Door.__init__, first line:
self.hero = Hero()
Here, you are instantiating a new Hero's instance. But, in game.MakeMap you are calling self.hero.HeroLocation().
This self.hero instance is not the same, because it was instantiated in game.__init__ and not in Door.__init__.
I didn't try, but check what behaviour gives this update:
class game:
def __init__(self, parent):
self.door = Door()
self.hero = self.door.hero
With this you now are calling the instance defined in Door.__init__, so when doing self.hero.HeroLocation() in game and (self.hero.heroC-self.doorC [...] in Door you are pointing the same instance.
Last thing, this solution may works, but is surely not what you really wants, I think a door should not store a hero, a hero should not store a door too, but here is more complex question about patterns.

Python - Can't create instances from a list or random instances

I am making a small text RPG to help me learn the Python language, and I am trying to create multiple instances of a class from a list of names.
I have a class of enemies (Named: Enemy) and would like to create between 1 and 3 "goblin" enemies at at time.
class Enemy:
def __init__(self, health):
self.health = health
How I have approached the problem so far is to use a for loop to run through letters 'a', 'b', and 'c' and append a list of enemies.
for i in ['a', 'b', 'c']:
enemylist.append('goblin' + (i))
This gives me a list of three goblins:
['goblina', 'goblinb', 'goblinc']
Now I would like to take each newly appended "goblin" in the list and create an instance of the enemy class using that goblin's name (Example: "goblina = enemy(10)"... "goblinb = enemy(10)...)
But when I try to create an instance using any number of ways including the following (which is probably the absolutely wrong way to):
for i in range (1, 3):
enemylist[i] = enemy(10)
All that I get is a single instance named enemylist[i].
Can someone please help me. Like I said, I am new to the language so please be gentle with the explanation but I am a fast learner and willing to read and research.
I spent the better part of 2 days (on and off) trying to get to the bottom of this and could not find a solution that worked.
Perhaps it would make more sense to keep the enemy's name as a member of the Enemy class as well:
import random
class Enemy:
def __init__(self, name, health):
self.name = name
self.health = health
def create_enemies():
enemies = []
for i in ['a', 'b', 'c']:
name = 'goblin'+i
health = random.randint(10,20)
enemies.append(Enemy(name, health))
It's hard to make a suggestion without knowing how you're going to use the enemies. If you want to be able to look an enemy up by name, a dictionary would be a better data structure in which to store them:
def create_enemies():
enemies = {} # Initialize empty dict
for i in ['a', 'b', 'c']:
name = 'goblin'+i
health = random.randint(10,20)
enemies[name] = Enemy(name, health)
return enemies
def main()
enemies = create_enemies()
ga = enemies['goblina']
I think adding a name to the class is good, but creating a dict with a key that's equal to the same name, but also storing the name and health as a dict value seems a bit redundant (as shown in answer above).
class Enemy:
def __init__(self, name, health):
self.name = name
self.health = health
def __repr__(self):
return self.name
This will give you a name to access. You can still store each instance of the enemy class in a list, using list comprehension if you like (or any other list creation):
for i in enemylist:
i = Enemy(i, 10)
enemies.append(i)
Now you have a list enemies, consisting of three instantiated objects of the class Enemy, that have a name that can be accessed. For instance
goblina.__name__
will return goblina and
isintance(goblina, Enemy)
will return True.

In python. How do I have a user change a dictionary value, when that dictionary is in a class?

So I had a similar question that was answered in another thread.
How do I update a dictionary value having the user choose the key to update and then the new value, in Python?
Basically, how did one get a nested dictionary value changed via raw_input. I used the solution and it worked well, but I wanted to write the program using classes. So I made a class with a method for editing the dictionary using essentially the same code, however when i try run it in the class method it gives me a "key error" now.
So in the main function this works the solution in the above linked question works great. But in a class method:
class team: # create a class where each team will be an instance
def __init__(self, name):
self.name = name #name of team will be passed from main
self.list_of_players = [] # create a list of the players
self.position1 = {} # create a dictionary for each of the positions on that team
self.position2 = {}
self.roster = [self.position1, self.position2]
def addplayer(self, player_name): # the name of the player is passed to this method from main
print 'add stats' # fill out the appropriate stats through raw_input
stat1 = raw_input('stat1: ')
stat2 = raw_input('stat2: ')
pos = raw_input('POS: ')
vars()[player_name] = {'stat1' : stat1, 'stat2' : stat2, 'POS' : pos} #create a dictionary
# for the player where all his stats are kept
player = {player_name : vars()[player_name]} # create a dictionary that will show the
# player's name as a string and his stats which are held in the dictionary named after him
self.list_of_players.append(player) # append the new player to the list of players
if pos == 'p1': # add the player and his stats to the appropriate position on the team
self.position1[player_name] = player
elif pos == 'p2':
self.position2[player_name] = player
else:
pass
def editplayer(self, player_name): # player's name is passed to the edit function from main
print self.list_of_players # player's name shows up in the list of players for the team
edit_stat = raw_input('which stat? ') # choose which stat(key) to edit via raw input
new_value = raw_input('new value: ') # choose the new value to apply to the chosen key
vars()[player_name][edit_stat] = new_value # here is where it gives a key error! this worked
#in fact even trying to call and print the players name gives the key error.
#player = vars()[player_name]
#print player
def main(): # the main function
loop1 = 0 # creating a loop so one can come back and edit the teams after creating them
list_of_teams = [] # initializing list of teams
while loop1 < 1:
print list_of_teams # show the user what teams are available to choose from
team_option = raw_input('new team or old: ') # create a new team or work with an old one
if team_option == 'new':
team_name = raw_input('team name? ') # get the team name from raw_input
vars()[team_name] = team(team_name) #create an instance of this team name
list_of_teams.append(team_name) # add the team to the list
else:
team_name = raw_input('which team? ') # choose which existing team to work with
player_choice = raw_input('new player or old? ') # choose to create or edit existing player
player_name = raw_input('player_name? ') # choose which player from raw_input
if player_choice == 'new':
vars()[team_name].addplayer(player_name) # give player_name to addplayer method
print vars()[team_name].list_of_players # shows the new player in the appropriate
# instance's roster. This method seems to be working fine
else:
vars()[team_name].editplayer(player_name) # gives the player's name to the editplayer
# method for the appropriate instance. But the player name just raises a key error in
# edit player method. I am baffled.
print vars()[team_name].list_of_players
if __name__ == '__main__':
main()
When it was all one long function this worked but looked like a disaster. Trying to learn better OOP practices but I can't figure out how to call up that dictionary with by the player's name to change the value. I've spent the past few days reviewing tutorials and questions on classes and dictionaries, but clearly I am misunderstanding something about how variables are passed from function to methods.
The fact that it wont even assign the dictionary vars()[player_name] to a var to be printed out means its not recognizing it as the dictionary that was created in the addplayer methond I think. But the fact that it still lists that dictionary in the list of players means it is existing in that instance. So why isn't it recognizing it when i try to address it in the editplayer method? And how do i call up the embeded dictionary created in one method, to change a value in that dictionary in the second method?
Karl pointed out good points that need clarifying: Here's what the attribues I want are.
self.name- i want an instance for each team created
self.list of players - each team should have its own list of players which are dictionaries holding that persons stats. so team1 should have its own list. team2 a different list etc
self.position1/2 - the players on each team would be filed in their various position dictionaries. so Player joe montana's dictionary of statistics would be found in that team's Quarterbacks dictionary
self.roster - should be that team's roster grouped by positions. So a call to print team1.roster should print those players grouped by positions
1) vars() is a dictionary of local variables within a function.
When you are in a method in Python, the contents of the object that you called the method on are not local variables. That's why you have to have a self parameter.
If you want to look up the players by name, then do that. Don't have a list of players, but instead a dict of players.
2) vars() is something you should almost never be using. It is used so that you can pretend that a string is a variable name. You do not need to do this for anything that you're doing here. In fact, you do not need a variable at all in most of the places where you're using one. You have more to learn about than just OO here.
Consider this part for example:
vars()[team_name] = team(team_name)
list_of_teams.append(team_name)
Instead of trying to remember the team by name in vars(), again, look up the teams by name. Have a dict of teams instead of a list. To get the names of teams, you can just print the keys of the dictionary.
Simple is better than complicated. Creating variables on the fly is complicated. Using dictionaries is simple.
I hate spoon-feeding this much code, but it seems like the only way to get the idea(s - I didn't really say everything above) across this time:
# Just like we want a class to represent teams, since those are "a thing" in our
# program, we want one for each player as well.
class player(object):
__slots__ = ['name', 'stats', 'pos']
def __init__(self, name, stats, pos):
self.name = name
self.stats = stats
self.pos = pos
# Asking the user for information to create an object is not the responsibility of
# that class. We should use external functions for this.
def create_player(name):
print 'add stats' # fill out the appropriate stats through raw_input
stat1 = raw_input('stat1: ')
stat2 = raw_input('stat2: ')
pos = raw_input('POS: ')
# Now we create and return the 'player' object.
return player(name, {'stat1': stat1, 'stat2': stat2}, pos)
class team(object):
__slots__ = ['name_to_player', 'position_to_player']
def __init__(self):
# We don't make any lists, just dicts, because we want to use them primarily
# for lookup. Notice how I've named the attributes. In particular, I **don't**
# talk about type names. That's just an implementation detail. What we care about
# is how they work: you put a name in, get a player out.
self.name_to_player = {}
self.position_to_player = {}
# Again, we don't ask the questions here; this just actually adds the player.
def add_player(self, player):
self.name_to_player[player.name] = player
self.position_to_player[player.pos] = player
# Again, we don't ask the questions here; this just does the actual edit.
def edit_player(self, name, stat, new_value):
self.name_to_player[name].stats[stat] = new_value
def main(): # the main function
teams = {} # dict from team name to team object.
while True:
print teams.keys()
# Your human interface was needlessly awkward here; you know from the supplied name
# whether it's a new team or an old one, because it will or won't be in your
# existing set of teams. Similarly for players.
team_name = raw_input('team name? ')
if team_name not in teams.keys():
teams[team_name] = team() # create a new team
else: # edit an existing one
team = teams[team_name]
player_name = raw_input('player name? ')
if player_name in team.name_to_player.keys(): # edit an existing player
stat = raw_input("stat? ")
value = raw_input("value? ")
team.edit_player(player_name, stat, value)
else: # add a new player
team.add_player(create_player(player_name))
if __name__ == '__main__':
main()
This still isn't doing everything "right", but it should give you more than enough to think about for now.
First of all, the traceback that accompanies the Key error, will tell you which line in your program triggered it, and if it is not obvious from reviewing the code, then inserting a print statement before that line should make it obvious.
Second, you are using user input as a key. User input is not reliable. You WILL have key errors all the time so your code should be dealing with that, either by using try: except: to catch the exception, or by checking every time using if key in mydict: before actually using the key to lookup the dictionary.
Third, what you are doing with vars() is very, very weird. If your app uses a global variable, then it should know the name and have no need to refer to vars. Have you forgotten to declare a global variable in some method?
def method(self,name):
global bigdict
bigdict[name] = "set at least one time"

Categories

Resources