Python and variable inside Variables - python

I have this object Troll.
I made a random selection for his 'weapon'.
Every time he appears his weapon will change.
Under his object status section, I have a variable 'base_att' I want it to be the damage variable from his 'weapon'
How can I have the base_att variable get updated by the 'weapon' variable random.choice
note that I listed all my 'Weapons' in a different .PY file hence the items.weapon_for_troll calling.
class stick:
name = 'Wooden Stick'
damage = range (0,3)
effect = None
class rocks:
name = 'Rocks'
damage = range (0,5)
effect = None
class troll:
name = 'Troll'
class equipment:
weapon = random.choice(items.weapon_for_troll)
class inventory:
loot = None
class status:
level = 1
exp_point = 30
hp_point = 30
base_att =
base_def = 2
bonus_def = 0

You need to distinguis between ("static") class variables and ("non static") instance variables.
You are declaring all things static - thats great for things that are shared between all Trolls (like a "type" of monster - aka Troll) - not so great for things all "have" but are not "the same" - say like an inventory.
Try this approach:
import random
class Weapon: # no need to make each weapon its own class
def __init__(self,name,damage,effect):
self.name = name
self.damage = damage
self.effect = effect
# create some troll weapons to choose from
troll_weapons = [ Weapon("Wooden Stick", range(3), None), Weapon("Rocks",range(5), None) ]
class Equipment:
def __init__(self):
# every time you instantiate a new equipment, generate a random weapon
# for this instance you just created. They are not static, they differ
# between each Troll (who instantiates its own Equipment)
self.weapon = random.choice(troll_weapons)
class Troll:
name = 'Troll'
def __init__(self):
self.equipment = Equipment() # get a new instance of Eq for this troll
# instance
self.weapon = self.equipment.weapon # "link" the troll weapon to its equipment
# as shortcut - do smth similar to an
# instance of your "Stat" block
def __str__(self):
return f"Troll with: {self.weapon.name}"
# create a troll army
trolls = [Troll() for _ in range(10)]
for t in trolls:
print(t)
Output:
Troll with: Rocks
Troll with: Wooden Stick
Troll with: Rocks
Troll with: Rocks
Troll with: Wooden Stick
Troll with: Rocks
Troll with: Wooden Stick
Troll with: Wooden Stick
Troll with: Wooden Stick
Troll with: Wooden Stick
Reads to check:
Classes
Class variables vs Instance variables
Sidenotes:
there are convention about naming schemes, classes start with a Capital, members lowercase

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!

How to properly call a class

I'm a Python (and programming in general) newbie and I'm trying to make a text-based, endless rpg with random rooms/encounters based on a list. This code is (of course) not completed yet, It's just for testing. Note that I imported Enemies from another .py file:
import Enemies
import random
class Room:
# template definition to define all rooms
def __init__(self, intro):
self.intro = intro
class VsRoom(Room):
# room with an enemy
def __init__(self, enemy):
self.enemy = random.choice(Enemy_List)
super().__init__(intro = "There's an enemy here!")
class NullRoom(Room):
# empty, boring room
def __init__(self):
super().__init__(intro = "Nothing Interesting here.")
Rooms = [VsRoom, NullRoom] # All future room "types" will go here
def print_room():
# Tells the player which kind of room they are
print("You enter the next room...")
Gen_Room = random.choice(Rooms)
print(Gen_Room.intro)
I wanted print room() to print "You enter on the next room...", randomly pick a room from the list, and print its intro, but when I try to run it I get this:
You enter the next room...
[...]
print(Gen_Room.intro)
AttributeError: type object 'NullRoom' has no attribute 'intro'
Process finished with exit code 1
I'm trying to learn how classes work and any help would be great for me. I tried to follow PEP8 as much as I could, and I also tried to find similar questions, without success.
From what I observe you have a list of where you choose the enemies, so you do not need to enter that parameter:
class VsRoom(Room):
# room with an enemy
def __init__(self):
self.enemy = random.choice(Enemy_List)
super().__init__(intro = "There's an enemy here!")
To create an instance you must do it as follows:
{class name}()
So you must change:
Gen_Room = random.choice(Rooms)
To:
Gen_Room = random.choice(Rooms)()
Complete code:
import Enemies
import random
class Room:
# template definition to define all rooms
def __init__(self, intro):
self.intro = intro
class VsRoom(Room):
# room with an enemy
def __init__(self):
self.enemy = random.choice(Enemy_List)
super().__init__(intro = "There's an enemy here!")
class NullRoom(Room):
# empty, boring room
def __init__(self):
super().__init__(intro = "Nothing Interesting here.")
Rooms = [VsRoom, NullRoom] # All future room "types" will go here
def print_room():
# Tells the player which kind of room they are
print("You enter the next room...")
Gen_Room = random.choice(Rooms)()
print(Gen_Room.intro)

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.

Changing class attributes

http://i.imgur.com/9Xin1x0.png
Here are the classes and their relations in this test case of Black Jack.
In python IDLE I import "BlackJackGame" and bind "game" variable to "BlackJackGame()", and thus I have initialized a game of Black Jack.
Now what if I want to initialize many games of Black Jack, and I want the "Card" class to have different attributes in every game?
For example, in one Black Jack game, the cards would have the classic suits, such as ['Diamond', 'Club', 'Heart', 'Spade'], and in other they would have made up suits, such as ['Test1', 'Test2', 'Test3', 'Test4'].
In short, how can I modify "Card" classes attributes when I'm binding BlackJackGame() to a variable, so that different games of Black Jack have different kinds of cards?
EDIT: I have coded a test case to show this problem in practice
class Card(object):
def __init__(self, attribute = 'Test 1'):
self.attribute = attribute
class Player(object):
def __init__(self):
self.card = Card()
class Deck(object):
def __init__(self, card = Card()):
self.card = card
class MainDeck(object):
def __init__(self, deck = Deck()):
self.deck = deck
class BlackJackGame(object):
def __init__(self, maindeck = MainDeck(), player = Player()):
self.maindeck = maindeck
self.player = player
#Initializing first game
game1 = BlackJackGame()
#We access "Card" classes that reside in "Deck" and "Player",
#to test "Card" class's variable.
print(game1.maindeck.deck.card.attribute) #Prints 'Test 1'
print(game1.player.card.attribute) #Prints 'Test 1'
#Initializing second game, but this time so that "Card" class's
#attribute has a different value, when card is in "Deck"
game2 = BlackJackGame(maindeck = MainDeck(deck = Deck(card = Card(attribute = 'Test 2'))))
#We test if the attribute assignment works correctly
print(game2.maindeck.deck.card.attribute) #Prints 'State 2'
Now you see, that in the second Black Jack game, I want the Card to have different attribute value than in the first one, but my method of doing this is very painful to write, and it only changes the Cards that are in Deck, I want the Card to have different kind of attribute value in different games of Black Jack.
Card = namedtuple('Card', 'attribute value')
class Deck(object):
def __init__(suits, basic_card):
'''Take list of suits'''
self.cards = [basic_card._replace(suit=suit, value=value)
for value in range(1, 14)
for suit in suits]
Usage:
poker = BlackJackGame(Deck(suits=['Hearts', 'Spades', 'Clubs', 'Diamonds'],
basic_card=Card(attribute='Test 1', value=0))
uno = BlackJackGame(Deck(suits=['Red', 'Yellow', 'Green', 'Blue'],
basic_card=Card(attribute='Test 2', value=0)))

class method returns wrong number in Python 2.7.1

I'm writing a mini game to learn Python. I created a weapons class that can be imported into my main.py file.
Here is the class I made:
class weapon(object):
def __init__(self, name):
self.weaponName = name
def weaponStrength(self, level, strength):
self.weaponLevel = level
self.weaponStrength = strength
damage = self.weaponStrength * level
print "Damage is equal to %r" % damage
return damage
Here are the objects that are created using the weapons class.
# Creates an Object called sword using the weaponsClass
sword = weapon("sword")
# Calls a method of the weaponsClass to calculate weapon Strength. Returns a int
sword.weaponStrength(3, 20)
# Creates an Object called Magic Staff using the weaponsClass
magicStaff = weapon("Magic Staff")
# Calls a method of the weaponsClass to calculate weapon Strength. Returns a int
magicStaff.weaponStrength(5, 30)
# Sets a variable
swordStrength = sword.weaponStrength
# Sets a variable
magicStaffStrength = magicStaff.weaponStrength
# Prints the variable
print swordStrength
# Prints the variable
print magicStaffStrength
I'm trying to figure out why the swordStrength and magicStaffStrength are equal to the strength value passed to the method.
Any help is much appreciated.
Thanks.
you're overwriting weaponStrength in the weapon namespace:
self.weaponStrength = strength
and
def weaponStrength(...):
are actually conflicting. Maybe think about your naming conventions
This method is attempting to do two things:
Storing the weaponLevel and weaponStrength
Calculating and returning damage
I think this is a bad design, since the first purpose suggests the function should be called something like setWeaponLevelAndStrength while the second suggests it should be called calculateDamage. weaponStrength is obviously a terrible choice of name if you also wish to have an attribute with that name
def weaponStrength(self, level, strength):
self.weaponLevel = level #first purpose
self.weaponStrength = strength #first purpose
damage = self.weaponStrength * level #second purpose
print "Damage is equal to %r" % damage #second purpose
return damage
I'd suggest you split the method into two
def setWeaponLevelAndStrength(self, level, strength):
self.weaponLevel = level
self.weaponStrength = strength
def calculateDamage(self)
damage = self.weaponStrength * self.weaponLevel
print "Damage is equal to %r" % damage
return damage

Categories