What is the best way to structure relationships stored across classes? - python

I am attempting to build a game environment in python that I can then utilize for a reinforcement learning application. To accomplish this, I see that applying an OOP approach will integrate best with common Reinforcement Learning methodologies. I am new to OOP, however, and have some questions regarding the class relationships. Here are my class definitions:
class Game:
def __init__(self, n_players, avg_hands_blinds, starting_chips, table_cnt, verbose):
self.n_players = n_players * table_cnt
self.starting_chips = starting_chips
self.table_cnt = table_cnt
self.tables = []
for table in range(self.table_cnt):
self.tables.append(Table(int(n_players/table_cnt)))
self.players = []
for i, player in enumerate(range(self.n_players)):
table_no = int(i/table_cnt)
self.players.append(Player(i, starting_chips, table_no))
def __repr__(self):
self.out_list = []
self.out_list.append(f'Number of players: {self.n_players}')
return self.out_list
class Tables:
def __init__(self, n_players):
self.n_players = n_players
self.table_hands = []
class Player:
def __init__(self, player_no, starting_chips, table_no):
self.player_no = player_no
self.table_no = table_no
self.chips = Chips(starting_chips)
def __repr__(self):
return f'player_no: {self.player_no} table_no: {self.table_no} chips: {self.chips}'
My question relates to how I should store information about the table assignments. For example, I have defined Player instances at the game level to facilitate awareness of their overall status in a game. During the game, players will need to be reassigned to different tables to re-balance tables as players are knocked out.
At the same time, each Table instance will need to have an "awareness" of which players are sitting at the table (to execute Hand instances, for example).
My question is this: how do I enable both transfer of players between tables and enable my Table instances to conduct operations on the relevant players?

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.

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.

All class objects become one when calling a function upon them python [duplicate]

This question already has answers here:
How to avoid having class data shared among instances?
(7 answers)
Closed 5 years ago.
During experimenting, I had encountered a really bizarre problem which made me very confused. After creating several class objects by iterating through a range, I called a function from another class that would append 1 string to the primary class objects. After looking at all of the objects' lists, I saw that they were all the same, even though they should've been different, and there wasn't just one string for each class, but the number of strings matched the number of the class objects themselves.
class Stack():
cards = []
setUp = False
def set_up(self):
cardTypes = ["A", "B", "C", "D"]
for cardType in cardTypes:
for x in range(3):
self.cards.append(cardType)
self.setUp = True
def deal(self, player, amount):
if self.setUp:
for x in range(amount):
card = self.cards[0]
self.cards.remove(card)
player.cards.append(card)
else:
self.set_up()
self.deal(player, amount)
class Player():
cards = []
class Game():
def start(self):
stack = Stack()
players = [Player() for player in range(int(input("How many players?")))]
for player in players:
stack.deal(player, 1)
for player in players:
print(player.cards)
#all players have the same numbers of cards (the amount of players = number of cards)
#when all players should only have 1 card each since I put 1 in stack.deal
#it's also as if all players become one when I call that function.
I ran the code using Game().start().
This is a common error to people comming into Python from Java or C++:
attributes declared at the class body are actually class attributes, and will be the same object visible for all instances of that class.
To create new and independent attributes for each instance of your class, you have to attribute them inside the __init__ method:
class Stack():
def __init__(self):
self.setup = False
self.cards = []
...
(Also, another "Javaism" in there: not all code needs to run inside methods in Python - so, tehre is no sense in having a start method in the game class to be used as you do - if ther is a single method in it, just do away with the "Game" class and move that code to a function (def game(...):) and just call it to get going).

Class variables in OOP

I am trying to develop a car-dealership-customer model to really understand OOP using Python.
The problem I am running into is that I'd like to define Car as a separate class object from the Dealer class. This causes confusion; how can I use Car attributes and upload them into the dealership inventory, for example, and then make transaction updates depending on customer acquisition?
I broke it down to just these two classes for now (Car & Dealer). I'd like to create an inventory dictionary with model being the key and the output from my retail_cost function being the cost of vehicle. How do i insert instances of car into the dealership inventory in the class dealer?
For example:
class Car(object):
"""This class basically defines a car in its simplest components
and then computes a retail price in the method after initializing"""
def __init__(self,model,engine,prod_cost):
self.model = model
self.engine = engine
self.prod_cost = prod_cost
def retail_cost(self):
return self.prod_cost *1.20
class Dealer(object):
"""Defines the dealership itself with its inventory and sales components"""
car_inventory = {}
def __init__(self, dealer_name):
self.dealer_name = dealer_name
The basic idea is that you add each instance of a car to the dealership, but note that I changed car_inventory to be an instance member of Dealer. This is because you want each new dealership you make to have its own car_inventory. If you left it as a class member of Dealer (the way you have it) then every dealership you make will have the same inventory.
class Car(object):
"""This class basically defines a car in its simplest components
and then computes a retail price in the method after initializing"""
def __init__(self,model,engine,prod_cost):
self.model = model
self.engine = engine
self.prod_cost = prod_cost
def retail_cost(self):
return self.prod_cost *1.20
class Dealer(object):
"""Defines the dealership itself with its inventory sales components"""
def __init__(self, dealer_name):
self.dealer_name = dealer_name
self.car_inventory = []
car1 = Car("ford", "fiesta", '18,000')
car2 = Car("ford", "fiesta", '12,000')
dealer = Dealer("tom's dealership")
dealer.car_inventory.append(car1)
dealer.car_inventory.append(car2)
print(dealer.car_inventory)
First, car_inventory should probably be an instance variable, not a class variable. This allows each dealer to have their own inventory.
def __init__(self, dealer_name):
self.dealer_name = dealer_name
self.car_inventory = {}
Now you can create Dealer objects:
dealer_alice = Dealer('Alice')
dealer_bob = Dealer('Bob')
Create Car objects:
car_1 = Car('Corolla', 'V4', 12000)
car_2 = Car('Focus', 'V4', 13000)
And add them to the dealers' inventories:
dealer_alice.car_inventory['Corolla'] = car_1
dealer_bob.car_inventory['Focus'] = car_2
Or instantiate new cars and add them directly to the inventories:
dealer_alice.car_inventory['Jetta'] = Car('Jetta', 'V6', 18000)
dealer_bob.car_inventory['Mustang'] = Car('Mustang', 'V8', 17000)
Each dealer now has an inventory dictionary with keys that are strings representing the model and values that are Car objects. You can print them like this:
for model,car in dealer_alice.car_inventory.items():
print(model, car)
Note that this will print a nice model name like 'Jetta', but the Car objects will only print basic information - namely, the name of the class and the object's location in memory. If you want nicer output, you'll have to define a __str__() method for the Car class. It would also be good to do that for the Dealer class so that you can just print(dealer_bob) and get tidy output.
I'd also recommend picking some other kind of key (like the VIN) - as it is, if the dealer gets another car of the same model it'll just overwrite the existing one.

Mixing class and instance variables together

I'm designing a simple role-playing game (similar to World of Warcraft, League of Legends, etc.) where you basically have a hero or a champion who fights with wild creatures and other heroes/champions.
This hero can then level up by gaining experience points from fights, and each level grants him a skill point, which allows him to level up one of his skills.
Each skill has its own purpose and does its own thing, an example of a skill would be:
class DamageAura(Skill):
"""
This skill increases damage dealt by your hero,
as well as deals damage back to the ones who attack your hero.
"""
name = 'Damage Aura'
description = '...'
# These methods are called when the actual event happens in game
def unit_attack(self, target, damage):
target._health -= (self.level / 100) * damage
def unit_defend(self, attacker, damage):
attacker.deal_damage((self.level / 100) * damage)
Now I would like the players of the game to be able to make their own heroes, and I need it to be simple.
The problem is, I don't want to do "bad" code simply to make creating heroes easier.
Here's what a hero class would ideally look like:
class Warrior(Hero):
name = 'Warrior'
description = 'Ancient Warrior is thirsty for blood.'
maximum_level = 30
agility = 20
intelligence = 10
strength = 30
class DamageAura(Skill):
name = 'Damage Aura'
description = '...'
def unit_attack(...):
...
class StrongerWeapons(Skill):
name = 'Stronger Weapons'
...
Now the problem is, I'd like the name variable to be an attribute of the actual hero (f.e.Warrior) instance, not a class variable.
Is there an easy, yet safe way to merge these two?
Basically the class variables would define the default values for a hero, but if I wanted to create a Warrior instance with a custom name, I could do so by simply doing warrior1 = Warrior(name='Warrior God', maximum_level=40)
This is what the class would look like if I didn't have to worry about subclasses or ease-of-creation:
class Hero(object):
def __init__(self, name, description='', maximum_level=50, skill_set=None, ...):
self.name = name
self.description = description
self.maximum_level = maximum_level
self.skill_set = skill_set or []
...
EDIT:
I forgot to mention; each player could be playing the exact same hero, Warrior in this case, so that's why Warrior is a class, not just an instance of Hero. They would all have the same skill set, but unique levels, etc.
EDIT 2:
I'm having no problem with Python classes or anything, normally I would NOT make the skills nested classes, or anything similar, but in this case I must invest a lot on ease of creating classes, so even the "noobs" can do it without knowing rarely any python at all.
You could allow the end users to create their Hero definitions as a dictionary:
my_hero = dict(name='Warrior'.
description='Ancient Warrior is thirsty for blood.',
maximum_level=30,
...)
Then you can easily create an instance like:
hero = Hero(**my_hero)
(If the syntax is unfamiliar, see What does ** (double star) and * (star) do for parameters?)
For both DamageAura and Warrior, it seems more appropriate for them to be instances than subclasses of Skill and Hero respectively.
Alternatively, consider letting the user create their characters outside of your code (e.g. in JSON, XML, YAML, ...), then parse the files and create appropriate instances from the input data.

Categories

Resources