Objects, lists, and my aged brain - python

To aid in some mental rehabilitation after a nasty hip accident I decided to try and teach myself to program in Python. So I've just started getting my head around defining functions and classes.
I have a basic enemy class that allows me to create an object like this.
enemy01=Enemy("Goblin",10,100,2,5,1,2)
To get the enemy name I can use
foe=enemy01.get_enemyName()
My problem is I want to use a list of enemies which I append as they get killed off and the variable 'foe' to refer to whatever enemy is in play.
So I tried creating a list of the enemy objects, like
currentEnemy=[enemy01, enemy02, enemy03]
and do
foe=currentEnemy.....
But I cant work out how to attach the .get_enemyName()
I was trying things like this to concatenate it
foe=(currentEnemy, ".get_enemyName()")
But nothing I am trying is working when I type 'print(foe)' which is what would be in the main body of code.
I have tried searching online and here but it's really hard as a beginner to put it into words what I am trying to do. Maybe i'm just going about it the wrong way to start with.
I hope I'm making sense and thanks for reading :)
Simon

You seem to conflate the list of all enemies and a variable referring to a particular one.
Here's what you could do:
enemies = [enemy01, enemy02, enemy03]
for currentEnemy in enemies:
eName = currentEnemy.get_enemyName()
print('The current enemy is', eName)
Later on you would probably decide that creating individual enemies is too tedious and use another loop for that:
# create three identical goblins
enemies = [Enemy("Goblin",10,100,2,5,1,2) for _ in range(3)]

If your class :
class Enemy:
def __init__(self,name):
self.name=name
def get_enemyName(self,):
return self.name
and you have initialised currentEnemy as :
enemy01=Enemy('Goblin')
enemy02=Enemy('Rhino')
enemy03=Enemy('DR.Octopus')
currentEnemy=[enemy01, enemy02, enemy03]
And you want to get list of all enemy names into a list foe. Then try :
foe=[x.get_enemyName() for x in currentEnemy]
print foe
i.e. list comprehensions
i= foe.index('Rhino') #find index of Rhino
del[currentEnemy[i]] #let's kill Rhino
print 'current enemies at play {}'.format([x.get_enemyName() for x in currentEnemy])

Sorry - real silly error on my part. I just missed the list index.
I sorted it
enemylist = [enemy01, enemy02, enemy03]
currentEnemy=enemylist[0]
foe=currentEnemy.get_enemyName()
print(foe)

Related

Python 3: self variable in init can't be changed?

Im programing a game for school right now. Its going pretty well so far, so Im now trying to implement an undo option after each players turn.
My board is a 7x7 matrix and is defined in __ init __ as self.board.
The board will be manipulated by each players turn.
Besides I have self.saved_states = [] defined in __ init __. This list is supposed to store copies of every state of the game after every players turn.
So far I have a method that actually saves state into the list mentioned in 3.
def save_state(self):
copied_board = copy.copy(self.board)
self.saved_states.append(copied_board)
I tested this and this seems to work. The problem is somewhere else.
So I will get input from user, if he inputs 'u' instead of actual valid game input I want to do the following:
if input == 'u':
print('Undoing last turn!')
self.board = self.saved_states.pop()
self.player_2()
I just want to change the current state of the game to the previous one and have player 2 take their turn (again).
Whats the problem here? Every time each player take its turn Im manipulating elements of the board too. Why can't I just replace the whole board with a previous one? I don't see the difference here.
Thanks for any help or idea!
This is kind of a wild guess: If your self.board is "a 7x7 matrix", it's probably a list of lists, so you should use copy.deepcopy to copy the board. Otherwise, the inner lists will still be the same, and as you modify the current board, you also modify all the "copies".
def save_state(self):
copied_board = copy.deepcopy(self.board)
self.saved_states.append(copied_board)
Alternatively, instead of storing the board layouts for each turn, store the moves performed by the players and replay the game accordingly.

Accessing variable of class-object instantiated in other file

I'm quite green on Python and have been looking around for an answer to my particular question. Though I'm not sure if it's a Python specific question, or if I'm simply getting my OOP / design patterns confused.
I've got three files: main.py, board.py and player.py. Board and player each only hold a class Player and Board, main simply starts the game.
However I'm struggling with validating player positions when they are added to the board. What I want is to instantiate the board and consecutively new player object(s) in main.py, but check the board size in player.py when a new player is added to the board, to ensure the player is not outside of bounds upon creation.
As it is now I'm getting a TypeError (getX() missing 1 required positional argument: 'self') when attempting to access the board's size inside of player.py.
Most likely because the board isn't instantiated in that scope. But if I instantiate it in the scope that will be counted as a new object, won't it? And if I pass the board to the player as a variable that would surely be counted as bad practice, wouldn't it?
So how do I go about accessing the instance variables of one class from another class?
I have no idea if this will help, but I made a post on how to save and load using the Pickle import. In the saving function, it refers back to the Player class I created. It might help you, it might not. Here is the link anyway.
Your question is asking about a concept called "dependency injection." You should take some time to read up on it. It details the ways of making one object available to another object that wants to interact with it. While that's too broad to write up here, here are some of the basics:
You could have all objects you care about be global, or contained in a global container. They can all see each other and interact with each other as necessary. This isn't very object-oriented, and is not the best practice. It's brittle (all the objects are tightly bound together, and it's hard to change or replace one), and it's not a good design for testing. But, it is a valid option, if not a good one.
You could have objects that care about each other be passed to each other. This would be the responsibility of something outside of all of the objects (in your case, basically your main function). You can pass the objects in every method that cares (e.g. board.verify_player_position(player1)). This works well, but you may find yourself passing the same parameter into almost every function. Or you could set the parameter either through a set call (e.g. board.set_player1(player1)), or in the constructor (e.g. board = Board(player1, player2)). Any of these are functionally pretty much the same, it's just a question of what seems to flow best for you. You still have the objects pretty tightly bound. That may be unavoidable. But at least testing is easier. You can make stub classes and pass them in to the appropriate places. (Remember that python works well with duck typing: "If it walks like a duck and quacks like a duck, then it's a duck." You can make testing code that has the same functions as your board and player class, and use that to test your functions.)
A frequent pattern is to have these objects be fairly dumb, and to have a "game_logic" or some other kind of controller. This would be given the instances of the board and the two players, and take care of maintaining all of the rules of the game. Then your main function would basically create the board and players, and simply pass them into your controller instance. If you went in this direction, I'm not sure how much code you would need your players or board to have, so you may not like this style.
There are other patterns that will do this, as well, but these are some of the more basic.
To answer your direct questions: yes, the error you're seeing is because you're trying to invoke the class function, and you need it to be on an object. And yes, instantiating in that case would be bad. But no, passing an instance of one class to another is not a bad thing. There's no use in having objects if they don't interact with something; most objects will need to interact with some other object at some point.
You mentioned that you have code available, but it's a good thing to think out your object interactions a little bit before getting too into the coding. So that's the question for you: do you want player1.check_valid_position(board), or board.check_player(player1), or rules.validate_move(player, some_kind_of_position_variable)`. They're all valid, and they all have the objects inter-relate; it's just a question of which makes the most sense to you to write.
It's hard to know your exact issue without seeing some code, but I hope this is useful!
class Player:
def __init__(self, x, y, player_id):
self.x = x
self.y = y
self.id = player_id
class Board:
def __init__(self, width, height):
self.width = width
self.height = height
self.players = {}
def add_player(self, player):
"""keep track of all the players"""
self._validate_player(player)
# add the player to a dict so we can access them quickly
self.players[player.id] = player
def _validate_player(self, player):
"""whatever validation you need to do here"""
if player.x < 0 or player.x >= self.width:
raise ValueError("The player didn't land on the board! Please check X")
if player.y < 0 or player.y >= self.height:
raise ValueError("The player didn't land on the board! Please check Y")
# whatever other checks you need to do
# player is in a valid location!
def main():
# we can make as few or as many players as we'd like!
p1 = Player(10, 20, 0)
p2 = Player(-1, 10, 1) # invalid player
board = Board(50, 50) # we only need to make one board
board.add_player(p1)
board.add_player(p2) # value error
running = True
while running: # simple game loop
player.take_turn() # maybe prompt user to input something
board.update_players() # update player positions
board.display()
running = board.keep_playing() # check for win state
# whatever code you want to run
if __name__ == "__main__":
main()
Here we create an instance of a Player by assigning an x and y position, and in this case also a player ID which we can use to get that player when we need them. If there's only going to be one player, we could just do something like board.player.
In my example, a ValueError is raised when an invalid Player is provided, you can of course do whatever you'd like in the event that a Player is invalid, also your game could have any number of other cases for a Player being invalid.
I've added some method calls for some methods that might make sense for a board game.
As a side note, in python, you generally don't need to write getters/setters, it's perfectly okay to access Class fields directly.
player.x = 10
if player.y == 11: etc.
and if you have need for validation of some sort that could belong in a getter/setter, you can use the #property decorator.

Let a module file use a global variable?

Forgive me if this is just a super easy solution as I am pretty new to Python. Right now I'm trying to make a basic video game, and to save space I decided to make a module for a combat encounter -- so that all I have to do when writing the code for each encounter is run the function in that module, only having to write the unique variables of the enemy. However, the code needs to know things like the player's HP, or what kind of weapon the player has. I tried putting global before the variables in the function module, but of course it doesn't work, as that's referencing global variables in the module, not the main game file. Or is there another way to go about this? If you need me to enclose my code, I will gladly do so.
Edit: Heres the code, in the module (called combat). What I want it to do is the main file's code will just say:
combat.combat(3, "mysterious creature", 12, 2, 4, 3, "claws", 5, 0)
Which, based off my shallow understanding, is how i edit the variables for each oppoent, its from this line in the module file.
def combat(enemylevel, enemyname, enemyhp, enemydefense, enemystrength,
enemyattack, enemyweaponattack, enemygp, run):
Based off you guys' confusion I'm guessing I'm doing something pretty basic wrong. Forgive my (most likely) cringey and ineffecient code-writing:
import random
import math
def combat(enemylevel, enemyname, enemyhp, enemydefense, enemystrength,
enemyattack, enemyweaponattack, enemygp, run):
global xp
global hp
global maxhp
global gp
global weapon_attack
global weapon
levelandname = "level" , enemylevel, enemyname
print("You draw your weapon and prepare for battle. You are fighting a",
levelandname, ".")
while enemyhp > 0:
if enemyhp > 0:
print()
attackorrun = input("Do you wish to attack or run? ")
if attackorrun == "attack" or "a":
print("You" , weapon_attack , "your" , weapon, "at the",
enemyname) # this is where the first error happens,
# says weapon_attack isn't defined.
attackroll = random.randint(1, 20)
attackroll = (attackroll+(math.floor(attack/2)))
I'm probably still leaving something unclear, feel free to tell me to do something else or ask me something.
Using large numbers of global variables can get messy. It doesn't provide you much flexibility, and as you're discovering, its hard to access those variables from other modules.
Many programmers will avoid using the global statement in a function, any data the function needs should be provided by other means.
Using container objects might be a good start, keeping related variables together, perhaps in a dictionary. You could pass an enemy dict (with hp, defense, strength etc.) and a player dict (xp, hp, weapon etc.) in to your function. That would give you access to those values in the function, and the function would even be able to modify those values (because you would be passing an object reference).
enemy = {'hp': 100, 'weapon': 'axe', 'strength': 50}
player = {'xp': 22, 'hp': 150, 'weapon': 'sword'}
def combat(player, enemy):
#calculate results of combat
player['hp'] = player['hp'] - damage
Another strategy might be to use classes. Classes are object definitions that can contain functions and variables. You can instantiate multiple instances of your class. An Enemy object (instance of an Enemy class) for example would contain an enemy hp variable, and functions that modify it during combat.

Can I call a function from within a list? (python2.7)

I think I have all the terms right in my question...but I'm not sure. Basically what I am trying to do is get the little details of my inventory and equipment set in a text game.
What I want to do is have a weapon, lets go with a sword, and i want it to change certain aspects of the character itself. Lets go with the hp of the character increases by like...5, what i would have in this case is, (don't mind the sloppy work!)
global basehp
basehp = 10
global armorhp
armorhp = 0
global skillhp
skillhp = 0
global hpmod
hpmod = (skillhp + armorhp)
global righthand
righthand = 0
so setting the example with hp this is what i actually have in my program, now what i want to do is change armorhp to match what the armor would change it to, say i have my sword that adds 5 to my hp right? what i am working with now is just not working, at first i thought to just have it equip and add 5 to armorhp, then i started thinking about UNequiping it...the effect would be the same...i shall show you what i mean
def hpsword:
armorhp = (armorhp + 5)
def prompt():
x = raw_input("Type command>>")
if x == 'equip':
print "equip what?"
print inventory
y = raw_input(">>>>")
if y == 'sword':
if 'hpsword' in inventory:
global righthand
righthand = 1
inventory.remove ('hpsword')
equipmentlist.append ('hpsword')
Now from right here you'll notice i left some things out, like my lists, i'm getting to that but this is where i can really explain more of what i mean, i want to use each item like that, but i can't figure out how...i'm confusing i know, i have a lot on my mind, but i want to just do something like....
def this sword():
this sword does this thing!
then when it gets equiped add the effect it does, such as +5 health, then remove it and take the effect with it, without having to do 100 lines of code with a bunch of statements that are oh add this effect but then when you don't want to use it anymore reset righthand to 0 and then if it was this number there take this many away from that before you change it to number 2 and add 10 more.
i'm also trying to avoid the equip sword, gain 5 health, unequip, still at 5? good, equip, YAY 10!!!
if i make any sense at all i'm shocked...but if anyone can figure out what i mean and give me a few pointers that would be great!
EDIT:
Reading up on what was left in a comment, (Thanks for that Eevee!) I think I am starting to figure out classes a little...very little.
New question then, would i then make a class for the main character, like,
class character(object):
then add in the init
def __init__(self)
then would i define inventory like,
def __init__(self)
def inventory
or would i do this,
def __init__(self, inventory)
sorry if i ask too many questions, just trying to get it figured out and i've noticed people here are nice enough to at least look at questions from wannabe programmers like myself :D
ANOTHER EDIT!!!!
Again, thanks a ton Eevee! That makes some sense, though reading through it i'm still a little confused as to how i would add it to my code and then what i would need to change for some different items.
To try and explain some, when i say different items, you gave me
class Sword(object):
def visit_health_bonus(self, character):
return 5
and if i wanted a sword that did something different i would just create a new class with a different name, but then basically the same thing?
class Sword2(object):
def visit_strength_bonus(self, character):
return 2
and that would give me a sword that would add 2 to my strength right?
now onto my other question, when i attempt to use this, what would i really need to get it to work right, i'm seeing a lot of things that i don't think would run without having some other bits
class Sword(object):
def visit_health_bonus(self, character):
return 5
# Then, every turn, recalculate health:
health = player.base_health
for equipment in player.equipment: //would i need another class here? (player)
# Pretend this lists everything the player has equipped; you might
# have to check each hand manually
health += equipment.visit_health_bonus(player) //also going to think a class (equipment)
It sounds like you are missing out on some of the awesome utility of object oriented programming, similar to what Marcin said you might what to make a series of classes like so:
class Weapon:
def __init__(self, name, armor_hp_bonus = 0, skill_hp_bonus = 0):
self.skill_hp_bonus = skill_hp_bonus
self.armor_hp_bonus = armor_hp_bonus
self.__name__ = name
class Hero:
def __init__(self):
self.basehp = 10
self.armorhp = 0
self.skillhp = 0
self.righthand = None
self.inventory = []
def choose_weapon(self):
while True:
for idx, item in enumerate(self.inventory):
print '{i}: {weapon}'.format(i=idx, weapon=item.__name__)
choice = raw_input("Pick your weapon: ")
try:
self.equip(self.inventory.pop(int(choice)))
return
except IndexError:
print "Invalid choice"
continue
def equip(self, weapon):
if self.righthand:
print "Weapon already equipped to right hand!"
return
else:
self.righthand = weapon
self.armorhp += weapon.armor_hp_bonus
self.skillhp += weapon.skill_hp_bonus
def unequip(self):
if not self.righthand:
print "No weapon to unequip!"
return
self.skillhp -= self.righthand.skill_hp_bonus
self.armorhp -= self.righthand.armor_hp_bonus
self.inventory.append(self.righthand)
self.righthand = None
def acquire_weapon(self, weapon):
self.inventory.append(weapon)
def calc_effective_hp(self):
return self.basehp + self.skillhp + self.armorhp
A class lets you keep all the variables you are having trouble keeping track of in one place. The skill bonus of one weapong will never get confused with the skill bonus of another weapon this way, because the variable storing that information is contained within the object. One you set up your classes correctly, the rest of your code is a lot shorter and cleaner because you can just call a method and rest assured that everything is being done correctly:
sword_of_1000_truths = Weapon('sword_of_1000_truths', skill_hp_bonus = 1337)
gandalf = Hero()
gandalf.acquire_weapon(sword_of_1000_truths)
gandalf.choose_weapon()
>> 0 sword_of_1000_truths
>> Pick your weapon: 0
print gandalf.calc_effective_hp()
>> 1347
gandalf.unequip()
print gandalf.calc_effective_hp()
>> 10
The methods such as equip and unequip make sure to increment and decrement the Hero's hp correctly, while keeping track of the logic of the game, i.e. you cant have two weapons in one hand, and you cant unequip a weapon if you don't have one equipped etc. This helps eliminate those ugly 100 lines of code you mention.
Good luck! Hope this helps
I'm assuming you're familiar with objects. If not, you should really read up on them (LPTHW is really good) before continuing, as they're pretty central to Python. If so, you should really be using a Player object instead of half a dozen globals.
The really easy approach is to have the sword be an object that does stuff when equipped and unequipped:
class Sword(object):
health_bonus = 5
def on_equip(self, character):
character.health += self.health_bonus
def on_unequip(self, character):
character.health -= self.health_bonus
Then you call on_equip when the player equips the sword, and on_unequip when the player unequips it. Other weapons can have similar event hooks, maybe for different stats.
That's all well and good, but something about it bugs me: you have to remember to call on_unequip whenever the player loses an item, and there might be more than one way that happens. You also have to track the player's "base" health separately, and if it changes, updating the "calculated" health requires unequipping and reequipping the weapon, which doesn't make a lot of sense.
So let's try something else. The "visitor pattern" Marcin referred to means (roughly) to loop through all the parts of a larger whole and call the same method on them. In this case, you want to ask all of the player's equipment how much extra health it grants.
class Sword(object):
def visit_health_bonus(self, character):
return 5
# Then, every turn, recalculate health:
health = player.base_health
for equipment in player.equipment:
# Pretend this lists everything the player has equipped; you might
# have to check each hand manually
health += equipment.visit_health_bonus(player)
Now you calculate the total health every time it's needed, so you don't have to worry about updating it when, say, the player levels up. You also don't have to worry about doing any extra bookkeeping when something gets unequipped: as soon as it disappears from the list of equipment, it stops contributing.
This is still a little inflexible, as it doesn't easily support e.g. a sword that doubles your attack power when fighting dragons. The most general way to solve this involves a lot of tangling and extra objects and figuring out what effects need to happen first, and I've yet to make it work to my satisfaction. Hopefully the above approaches are a good enough starting point. :)
The most flexible way to solve this is to have the action of "attack with equipped weapon" itself be an object and have equipment, the player, the target, etc. all react to the attack by increasing stats. But then you have questions of priority that are really fiddly to resolve so let's not do that.
A couple of things:
You have 5 global variables. These should be moved into a class, and your function become a method on that class.
The usual way to work something like this is for the sword to support a particular interface defining what modifications it makes, which the character class can read. Alternatively, use the visitor pattern, and have the character pass itself to a method of the sword on equip/unequip.

python text rpg inventory system

I'm making a text-based rpg with python 3.3 and I am wanting a more dynamic inventory system for my game. I am currently using variables assigned to each item but with functions, the variables I am passing as arguments are becoming too many and quite cluttered. If there is a better way out there I would love help creating a better system.
Here is how my inventory call system looks right now:
print('Item1',item1)
print('Item2',item2)
print('Item3',item3)
and so on.
Well, I have an old text game I made and the inventory system I use is just a list named 'inv'. Then all you have to do when you gain an item of whatever type is to append the inventory list with the string.
inv = []
inv.append('Item1')
Then you can create a dictionary with the key as the item name and the value be a list you can reference. Try this:
itemvalues = {'Item1':[0,0,0]
}
And the list can be a bunch of different attributes such as buy sell price, strength or what have you. Hope this helps.
You could try and use a list:
inventory = []
inventory.append(some_item)
And you can implement an inventory cap:
if len(inventory) > inventory_size:
print('Inventory full.')
If you want to 'use' an item in the inventory, say, then it's also easy to remove (this is of course assuming the index exists):
# use the item
del inventory[index]
Obviously most of the functionality you need is accessible through standard list methods, so check the documentation out.

Categories

Resources