Creating dynamic variables for a class from within the class - python

For context, I'm working on an inventory system in an RPG, and I'm prototyping it with python code.
What I don't understand is how to make separate variables for each instance of an item without declaring them manually. For a short example:
class Player(object):
def __init__(self):
self.Items = {}
class Item(object):
def __init__(self):
self.Equipped = 0
class Leather_Pants(Item):
def __init__(self):
#What do i place here?
def Pick_Up(self, owner):
owner.Items[self.???] = self #What do i then put at "???"
def Equip(self):
self.Equipped = 1
PC = Player()
#Below this line is what i want to be able to do
Leather_Pants(NPC) #<-Create a unique instance in an NPC's inventory
Leather_Pants(Treasure_Chest5) #Spawn a unique instance of pants in a treasure chest
Leather_Pants1.Pick_Up(PC) #Place a specific instance of pants into player's inventory
PC.Items[Leather_Pants1].Equip() #Make the PC equip his new leather pants.
If I did something silly in the above code, please point it out.
What I want to do if the code doesn't make it clear is that I want to be able to dynamically create variables for all items as I spawn them, so no two items will share the same variable name which will serve as an identifier for me.
I don't mind if I have to use another class or function for it like "Create_Item(Leather_Pants(), Treasure_Chest3)"
What's the best way to go about this, or if you think I'm doing it all wrong, which way would be more right?

As a general rule, you don't want to create dynamic variables, and you want to keep data out of your variable names.
Instead of trying to create variables named pants0, pants1, etc., why not just create, say, a single list of all leather pants? Then you just do pants[0], pants[1], etc. And none of the other parts of your code have to know anything about how the pants are being stored. So all of your problems vanish.
And meanwhile, you probably don't want creating a Leather_Pants to automatically add itself to the global environment. Just assign it normally.
So:
pants = []
pants.append(Leather_Pants(NPC))
pants.append(Leather_Pants(chests[5]))
pants[1].pickup(PC)
The pants don't have to know that they're #1. Whenever you call a method on them, they've got a self argument that they can use. And the player's items don't need to map some arbitrary name to each item; just store the items directly in a list or set. Like this:
class Player(object):
def __init__(self):
self.Items = set()
class Item(object):
def __init__(self):
self.Equipped = 0
class Leather_Pants(Item):
def __init__(self):
pass # there is nothing to do here
def Pick_Up(self, owner):
self.owner.Items.add(self)
def Equip(self):
self.Equipped = 1

Abernat has tackled a few issues, but I thought I weigh in with a few more.
You appear to be using OOP, but are mixing a few issues with your objects. For example, my pants don't care if they are worn or not, I care though for a whole host of reasons. In python terms the Pants class shouldn't track whether it is equipped (only that it is equippable), the Player class should:
class CarryableItem:
isEquipable = False
class Pants(CarryableItem):
isEquipable = True
class Player:
def __init__(self):
self.pants = None # Its chilly in here
self.shirt = None # Better take a jumper
self.inventory = [] # Find some loot
def equip(self,item):
if is.isEquipable:
pass # Find the right slot and equip it,
# put the previously equipped item in inventory, etc...
Also, its very rare that an item will need to know who its owner is, or that its been grabbed, so verbs like that again should go onto the Player:
class Player:
maxCarry = 10
def take(Item):
if len(inventory) < maxCarry:
inventory.append(item)
Lastly, although we've tried to move most verbs on to actors which actually do things, sometimes this isn't always the case. For example, when instantiating a chest:
import random
class StorageItem:
pass
class Chest(StorageItem):
__init__(self):
self.storage = random.randint(5)
self.loot = self.spawnLoot()
def spawnLoot(self):
for i in range(self.storge):
# Lets make some loot
item = MakeAnItem # Scaled according to type level of dungeon, etc.
loot.append(item)
def remove(item):
self.loot[self.loot.index(item)]=None
Now the question about what to do when a Player wants to plunder a chest?
class Player:
def plunder(storage):
for item in storage.loot:
# do some Ui to ask if player wants it.
if item is not None and self.wantsItem(item) or \
(self.name="Jane" and self.wantsItem(item) and self.doesntWantToPay):
self.take(item)
storage.remove(item)
edit: Answering the comment:
If you are curious about calculating armor class, or the like, that again is a factor of the user, not the item. For example:
class Player:
#property
def clothing(self):
return [self.pants,self.top]
#property
def armorClass(self):
ac = self.defence
for item in self.clothing:
def = item.armorClass
if self.race="orc":
if item.material in ["leather","dragonhide"]:
def *= 1.5 # Orcs get a bonus for wearing gruesome dead things
elif item.material in ["wool","silk"]:
def *= 0.5 # Orcs hate the fineries of humans
ac += def
return ac
pants = Pants(material="leather")
grum = Player(race="orc")
grum.equip(pants)
print grum.armorClass
>>> 17 # For example?

Related

Inheriting class attributes without declaring attributes or better OOP

I am attempting to construct classes to play out a game of MTG (A card game). I have three relevant classes: MTGGame(...), MTGCard(...), and AbilityList(). An object of MTGGame has several attributes about the player (turn, mana,..., deck).
A player must have a deck of cards to play, so I create a list of MTGCard objects for each player that is a deck, and create an MTGGame object for each from the respective decks. The cards have abilities, and when creating the cards I store abilities as functions/params into each MTGCard. However, I need the abilities to inherit and access methods/attributes from MTGGame and update them, but if I use super().__init__, then I will need to call my deck as a parameter for AbilityList when making MTGCards, which I wouldn't have yet.
Can this be achieved? If not, any suggestions improving my OOP logic to achieve this task?
I am aware that I can do something like this:
class MTGGame():
def __init__(self, deck, turn = 0, mana = 0, lifeTotal = 20, cavalcadeCount = 0, hand = [], board = []):
self.turn = turn
self.mana = mana
self.lifeTotal = lifeTotal
...
def gainLife(self, lifeGained):
self.lifeTotal = self.lifeTotal +lifeGained
def combatPhase(self):
for card in self.board:
card.attackingAbility()
class MTGCard():
def __init__(self, name, CMC, cardType, power, toughness, castedAbility, attackingAbility, activatedAbility, canAttack = False):
....
self.attackingAbility = attackingAbility
Class abilityList():
def healersHawkAbility(self, lifeAmt):
MTGGame.gainLife(lifeAmt)
But this would affect all instances of MTGGame, not the specific MTGGame object this would've been called from. I'd like it to simply update the specific object in question. I'd like to do something like this but I don't know how abilityList methods could access MTGGame attributes/methods ('AbilityList' object has no attribute 'gainLife'):
Class abilityList():
def healersHawkAbility(self, lifeAmt):
#How do I access methods/attributes in MTGGame from here? self?
self.gainLife(lifeAmt)
aL = abilityList()
#One example card:
card1 = MTGCard("Healers Hawk",1,'Creature',1,1, aL.nullAbility(), aL.healersHawkAbility, aL.nullAbility())
whiteDeck = [list of constructed MTGCard() objects, card1, card2,...,cardLast]
player1 = MTGGame(whiteDeck)
...
#Call the ability in a method contained in MTGGame:
player1.combatPhase()
#Would call something like this inside
card.attackingAbility()
#Which may refer to card.healersHawkAbility() since we stored healersHawkAbility() as an attribute for that MTGCard,
#and would declare gainLife(), which refers to self.lifeTotal or player1.lifeTotal in this case.
This is an excellent start and clearly you have already thought a lot of this through. However, you haven't thought through the relationship between the classes.
First thing to note:
MTGGame.gainLife(lifeAmt) is a method call accessed via the class rather than an instance. This means that the self paramter is not actually filled in i.e. you will get an error becuase your method expects 2 arguments but only receive one.
What you perhaps meant to do is the following:
class MTGGame:
lifeTotal = 20 # Notice this is declared as a class variable
def __init__(self, ...):
...
#classmethod
def healersHawkAbility(cls, lifeGained):
cls.lifeTotal = cls.lifeTotal + lifeGained
However, this requires class variables which here defeats the point of having an instance.
Your naming throughout the program should suggest that your classes are a little off.
For instance player1 = MTGGame(). Is player a game? No, of course not. So actually you might want to rename your class MTGGame to Player to make it clear it refers to the player, not the game. A seperate class called MTGGame will probably need to be created to manage the interactions between the players e.g. whose turn it is, the stack holding the cards whilst resolving.
The main focus of your question: how to deal with the cards accessing the game/player object.
Cards should be able to access instances of the player and game classes, and if the player has a is_playing attribute, the card should not have this. The rule of thumb for inheritance is 'is a'. Since card 'is not a' player, it should not inherit from it or MTGGame. Instead, card should be like this for example:
game = RevisedMTGGame()
player1 = Player()
player2 = Player()
class Card:
def __init__(self, name, text, cost):
self.name = name
self.text = text
self.cost = cost
self.owner = None
self.game = None
class Creature(Card):
def __init__(self, name, text, cost, power, toughness):
super().__init__(self, name, text, cost)
self.power = power
self.toughness = toughness
def lifelink(self):
self.owner.heal(self.power) # NOTE: this is NOT how I would implement lifelink, it is an example of how to access the owner
healersHawk = Creature("Healer's Hawk", "Flying, Lifelink", 1, 1, 1)
healersHawk.game = game
healersHawk.owner = player1
You can see from this incomplete example how you can set up your cards easily, even with complex mechanics, and as the base classes have been defined you can avoid repitition of code. You might want to look into the event model in order to implement the lifelink mechanic, as an example. I wish you luck in continuing your game!

Game with turns, need a way to receive a fuction's return in another turn

I'm making a game where I can gather resources or build when I send Workers, but I can't think of a way to receive those resources or finish building depending on the turn and the time(turns) it takes to finish those actions.
I've already made a Worker class, and it has a method to gather and it gives a random value that I save in a Player class. Also, my Game class keeps track of the turn I and the computers are.
class Game:
def __init__(self, player = None):
self.player = player
self.turn = 1
class Player:
def __init__(self):
self.workers = [Worker(), Worker(), Worker()]
self.resourcers = 0
class Worker:
def __init__(self):
self.hp = 100
def gather(self):
return randint(MIN_CANTIDAD_RECURSO, MAX_CANTIDAD_RECURSO)
player = Player()
game = Game()
game.player = player
for worker in player.workers:
player.resources += worker.gather
game.turn +=1
Gathering should give the result the next turn and build should give it depending on the building.
In a general sense, you store the values you need in the relevant object and pass them as parameters to whatever method requires those values. For example, you would need to store the turn duration of an action in the return value of that action, e.g in class Worker
def gather(self):
# Some code that determines gather_value and duration...
return [gather_value, duration]
and then the resource usage would look something like
def use_gather(gather, turn): # Pass in (return value from gather, game.turn)
# Use parameters...
With such a vague question, it's hard to say anything more.

Better way to create my lists in classes?

I've been working on making my text based game more realistic, and one design I would like to implement is to make sure the rooms stay 'static' to a point (i.e. a player uses a potion in the room, if they come back to that room that potion should no longer be there.)
This is how my code is basically set up (I have been using "Learn Python The Hard Way" by Zed Shaw so my code is set up much in the same way):
class Scene(object):
def enter(self):
print "This scene is not yet configured. Subclass it and implement enter()."
class Room(Scene):
potions = []
for i in range(3):
potions.append(Potion())
def enter(self):
...
When I run this, I get a NameError: global name 'potions' is not defined. I know I can fix this one of two ways: 1. make potions a global variable, but then I would have to make a new list for each room that contains potions (There are 36 rooms in total, set up as a 6x6 grid) OR
2. Put this line of code in the enter function, but that would cause the list to reset to 3 potions each time the user enters the room.
potions = []
for i in range(3):
potions.append(Potion())
If there's no other way, I suppose declaring a new variable for all the rooms that contain potions (There's only 5). But my question is if there's another way of making this work without making it a global.
Thanks for your help!
First, let's look at your example (I'll simplify it):
class Room(Scene):
potions = [Potion() for x in range(3)]
What you have done there is create a class attribute potions that are shared among all instances of Room. For example, you'll see my potions in each of my rooms are the same instances of potions (the hex number is the same!). If I modify the potions list in one instance, it modifies the same list in all of the Room instances:
>>> room1.potions
[<__main__.Potion instance at 0x7f63552cfb00>, <__main__.Potion instance at 0x7f63552cfb48>, <__main__.Potion instance at 0x7f63552cfb90>]
>>> room2.potions
[<__main__.Potion instance at 0x7f63552cfb00>, <__main__.Potion instance at 0x7f63552cfb48>, <__main__.Potion instance at 0x7f63552cfb90>]
>>>
It sounds like you want potions to be a unique attribute of each instance of a Room.
Somewhere you will be instantiating a room, e.g., room = Room(). You need to write your constructor for your Room in order to customize your instance:
class Room(Scene):
def __init__(self): # your constructor, self refers to the Room instance.
self.potions = [Potion() for x in range(3)]
Now when you create your room instance, it will contain 3 potions.
You now need to think about how you will make your rooms instances persist between entrances by your characters. That will need to be some sort of variable that persists throughout the game.
This idea of object composition will extend through your game. Perhaps you have a Dungeon class that has your 36 rooms:
class Dungeon(object):
def __init__(self):
self.rooms = [[Room() for x in range(6)] for x in range(6)]
Or perhaps your rooms have up to four doors, and you link them up into something potentially less square:
class Room(Scene):
def __init__(self, north_room, east_room, south_room, west_room):
self.north_door = north_room
self.east_door = east_room
[... and so on ...]
# Note: You could pass `None` for doors that don't exist.
Or even more creatively,
class Room(Scene):
def __init__(self, connecting_rooms): # connecting_rooms is a dict
self.connecting_rooms = connecting_rooms
Except both examples will get you a chicken and egg problem for connecting rooms, so it is better to add a method to add each room connection:
class Room(Scene):
def __init__(self):
self.rooms = {}
# ... initialize your potions ...
def connect_room(self, description, room):
self.rooms[description] = room
Then you could do:
room = Room()
room.connect_room("rusty metal door", room1)
room.connect_room("wooden red door", room2)
room.connect_room("large hole in the wall", room3)
Then perhaps your dungeon looks like this:
class Dungeon(Scene):
def __init__(self, initial_room):
self.entrance = initial_room
Now in the end, you just have to hold onto your dungeon instance of Dungeon for the duration of the game.
btw, this construct of "rooms" connected by "paths" is called a Graph.

Creating a circular reference

I need to create several objects that have (often circular) references to each other. In general, several objects might be involved, but here's one simple case where I need just a pair of cars that refer to each other:
class Car:
def __init__(self, position, speed):
self.position = position
self.speed = speed
def set_reference(self, other_car):
self.other_car = other_car
# ...
def main():
# ...
car1 = Car(pos1, spd1)
car2 = Car(pos2, spd2)
car1.set_reference(car2)
car2.set_reference(car1)
A car without a reference to the other car is not a valid object. So ideally, I'd like to perform set_reference from inside the __init__ method; this would be both safer (no chance of using invalid objects) and cleaner (all initialization will be performed in __init__ as one might expect).
Is there any neat solution that achieves this goal? I don't mind creating a pair of cars at once, but each car is a standalone entity, and so it needs to be an instance in its own right.
I'm also aware that circular references are troublesome for GC; I'll deal with that.
Use case:
Each car serves as a backup for the other. Car instances are "smart", i.e., they can do a lot of work. It's annoying if the a car instance doesn't know its backup, since it prevents a car from completing actions without requiring a reference from outside every time.
I don't think there is a good way to move the set_reference() call into __init__(), because the other car might not yet exist. I would probably do something like this.
class Car:
def __init__(self, position, speed):
self.position = position
self.speed = speed
def set_reference(self, other_car):
self.other_car = other_car
#classmethod
def create_pair(cls, car1_args, car2_args):
car1 = cls(*car1_args)
car2 = cls(*car2_args)
car1.set_reference(car2)
car2.set_reference(car1)
return car1, car2
def main():
car1, car2 = Car.create_pair((pos1, spd1), (pos2, spd2))
Here is how you could expand this same concept for a larger circular reference structure:
class Car:
# ...
#classmethod
def create_loop(cls, *args):
cars = [cls(*car_args) for car_args in args]
for i, car in enumerate(cars[:-1]):
car.set_reference(cars[i+1])
cars[-1].set_reference(cars[0])
return cars
You could then call it like this (with any number of cars):
car1, car2, car3 = Car.create_loop((pos1, spd1), (pos2, spd2), (pos3, spd3))
You should up with the references set up like this:
>>> car1.other_car is car2 and car2.other_car is car3 and car3.other_car is car1
True
I'd suggest separating the car data structure from the data structure that references a sequence of cars. other_car doesn't really seem like data that strictly belongs in car, but rather represented in some iterable sequence of cars. Thus the simplest and most logically consistent solution would be to define a car, then put it in a sequence of cars.
If you really don't want to use a factory method, then the only real option is to force the user to configure them afterwards. (Taking on-board what you said about potentially wanting loops of dependency in the comments):
class Car:
def __init__(self, position, speed):
self.position = position
self.speed = speed
#staticmethod
def setup(*args):
for car, next in zip(args, args[1:]):
car.other_car = next
args[-1].other_car = args[0]
def main():
...
car1 = Car(pos1, spd1)
car2 = Car(pos2, spd2)
Car.setup(car1, car2)
Another option is to split off the grouping:
class CarGroup(list):
def other_car(self, car):
index = self.index(car)+1
index = 0 if index >= len(self) else index
return self[index]
class Car:
def __init__(self, position, speed, group):
self.position = position
self.speed = speed
self.group = group
self.group.append(self)
#property
def other_car(self):
return self.group.other_car(self)
def main():
...
group = CarGroup()
car1 = Car(pos1, spd1, group)
car2 = Car(pos2, spd2, group)

Python elevator simulation problem

I have a homework assignment that's really baking my noodle. It involves an elevator simulation that takes user inputs for the number of floors and the number of people using the elevator. the people's starting floor and destination floors are random numbers within the floors.
I realize that my code is very sparse and that there's quite a few gaps, but I really don't know where to go from here.
I need help within the building class, such as how to make the run() and output() sections work. any other tips would be greatly appreciated and helpful. Note that i am not looking for someone to do the code for me, but to kind of hold my hand and tell me which way to go. Classes seem to be completely mystifying to me.
import random
floors=raw_input('Please enter the number of floors for the simulation:')
while floors.isalpha() or floors.isspace() or int(floors) <=0:
floors=raw_input('Please re enter a digit for number of floors:')
customers=raw_input('Please enter the number of customers in the building:')
while customers.isalpha() or customers.isspace() or int(customers) <0:
customers=raw_input('Please re enter a digit for number of customers:')
count = 1
class building:
def num_of_floors():
num_of_floors = floors
def customer_list():
customer_list = customers
def run(self):
def output(self):
print elevator.cur_floor
class elevator:
def num_of_floors():
building.num_of_floors
def register_list():
register_list = []
def cur_floor(building):
cur_floor = 1
def direction(self):
if elevator.cur_floor == 1:
direction = up
if elevator.cur_floor == floors:
direction = down
def move(self):
if elevator.direction == up:
cur_floor +=1
if elevator.direction == down:
cur_floor -=1
def register_customer(self, customer):
register_list.append(customer.ID)
def cancel_customer (self, customer):
register_list.remove(customer.ID)
class customer:
def cur_floor(customer):
cur_floor = random.randint(0,int(floors))
def dst_floor(customer):
dst_floor = random.randint(0,int(floors))
while dst_floor == cur_floor:
dst_floor = random.randint(0,int(floors))
def ID():
cust_id = count
count+=1
def cust_dict(cust_id,dst_floor):
cust_dict = {cust_id:dst_floor}
def in_elevator():
in_elevator = 0
if customer.ID in register_list:
in_elevator = 1
def finished():
if customer.ID not in register_list:
pass
You need to understand the self
parameter to all methods.
You need to understand __init__,
the constructor.
You need to understand self.varible
for your member variables.
You need to understand how to setup a
main function.
You need to understand how to
return a value from a function or
method.
You need to understand how to assign to global variables from within a function or method.
Maybe your building class should start like this.
class building:
def __init__(self, floors, customers):
self.num_of_floors = floors
self.customer_list = customers
self.elevator = elevator()
You should definately spend some time on Python Tutorial or Dive into Python.
The first parameter of every method is a reference to the object and is usually called self. You need it to reference instancemembers of an object.
Second, referencing global variables from inside a class is considered a bad idea. You can better pass them to a class via the constructor or parameters.

Categories