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.
Related
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)
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.
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.
I am currently making a game in Python. Whenever you want help in the game, you just type help and you can read the help section.
The only problem is, I need to add a function block for each level.
def level_01():
choice = raw_input('>>>: ')
if choice=='help':
level_01_help()
def level_012():
choice = raw_input('>>>: ')
if choice=='help':
level_02_help()
So I was wondering if is possible to make a global function block for all the levels?
When you enter help, you get to help(), and then it automatically goes back to the function block you just came from.
I really hope you understand what I mean, and I would really appreciate all the help I could get.
You can actually pass the help function as a paramater, meaning your code can become:
def get_choice(help_func):
choice = raw_input('>>>: ')
if choice == 'help':
help_func()
else:
return choice
def level_01():
choice = get_choice(level_01_help)
def level_02():
choice = get_choice(level_02_help)
Ideally you should have a separate Module for all interface related tasks, so that the game and interface will be two seperate entities. This should make those 2911 lines a bit more legible, and if you decide to change Interfaces (from Command Line to Tkinter or Pygame for example) you will have a much much easier time of it. Just my 2ยข
A really nice way to handle this kind of problem is with the built in python help. If you add docstrings to your function, they are stored in a special attribute of the function object called doc. You can get to them in code like this:
def example():
'''This is an example'''
print example.__doc__
>> This is an example
you can get to them in code the same way:
def levelOne():
'''It is a dark and stormy night. You can look for shelter or call for help'''
choice = raw_input('>>>: ')
if choice=='help':
return levelOne.__doc__
Doing it this way is a nice way of keeping the relationship between your code and content cleaner (although purists might object that it means you can't use pythons built-in help function for programmer-to-programmer documentation)
I think in the long run you will probably find that levels want to be classes, rather than functions - that way you can store state (did somebody find the key in level 1? is the monster in level 2 alive) and do maximum code reuse. A rough outline would be like this:
class Level(object):
HELP = 'I am a generic level'
def __init__(self, name, **exits):
self.Name = name
self.Exits = exits # this is a dictionary (the two stars)
# so you can have named objects pointing to other levels
def prompt(self):
choice = raw_input(self.Name + ": ")
if choice == 'help':
self.help()
# do other stuff here, returning to self.prompt() as long as you're in this level
return None # maybe return the name or class of the next level when Level is over
def help(self):
print self.HELP
# you can create levels that have custom content by overriding the HELP and prompt() methods:
class LevelOne (Level):
HELP = '''You are in a dark room, with one door to the north.
You can go north or search'''
def prompt(self):
choice = raw_input(self.Name + ": ")
if choice == 'help':
self.help() # this is free - it's defined in Level
if choice == 'go north':
return self.Exits['north']
A better solution would be to store the level you are on as a variable and have the help function handle all of the help stuff.
Example:
def help(level):
# do whatever helpful stuff goes here
print "here is the help for level", level
def level(currentLevel):
choice = raw_input('>>>: ')
if choice=='help':
help(currentLevel)
if ...: # level was beaten
level(currentLevel + 1) # move on to the next one
Sure, it's always possible to generalize. But with the little information you provide (and assuming "help" is the only common functionality), the original code is extremely straightforward. I wouldn't sacrifice this property only to save 1 line of code per level.
I'm a beginner, writing a Python Blackjack script, and got confused about whether or not a function (dealPlayer) needs a parameter. It works either way, with a parameter or without. I'm not sure if I've had a brain fart, or I've not learned something along the way. Here's the code:
import random
dealer = []
player = []
c = ""
deck = [2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,6,6,6,6,7,7,7,7,8,8,8,8,
9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,11]
def dealPlayer(deck):
cardOne = random.choice(deck)
cardTwo = random.choice(deck)
player.append(cardOne)
player.append(cardTwo)
deck.remove(cardOne)
deck.remove(cardTwo)
The question is, do I need (deck) as a parameter in the function? It works with or without (deck) as a parameter. I've gone back over different tutorials, and other's code, but I'm still confused. Thanks for any help.
The reason your code works with or without deck as a parameter is because there is a global variable named deck, so when you reference deck inside your function, the function will first look for the local variable (the parameter) and then if it doesn't find it, it will look for the global variable.
It's best to refactor your code to not use global variables at all -- define deck initially inside a function and then pass that as a result or argument to other functions as needed. If you don't want to do that, then at least make sure your argument does not shadow (have the same name as) the global variable, to avoid confusion further on. Or remove the argument entirely and use the global variable only, if that's appropriate for your program.
did i get you right that if your function is:
def dealPlayer():
the code still works? this should raise a undefined deck error. EDIT: this was wrong of course its global. And just works without it. but thats a bad practice.
def dealPlayer():
deck = []
this should raise a Index Error.
cardOne = random.choice()
This raises a TypeError.