Another Name error for my text adventure game Python - python-3.x

So i am making a text adventure game, and currently i am making the enemies. My class random_enemies makes trash mobs for your character to fight and i have a function in it called weak, normal, strong, etc... that scales with your character depending on which one it is. When i call random_enemies.weak it says (Name Error: global variable "p" is not defined) even though it should be.
import random
from character import *
from player import *
class random_enemies(character):
def __init__(self,name,hp,maxhp,attack_damage,ability_power,exp):
super(random_enemies,self).__init__(name,hp,maxhp)
self.attack_damage = attack_damage
self.ability_power = ability_power
self.exp = exp
def weak():
self.hp = random.randint(p.maxhp/10, p.maxhp/5)
self.attack_damage = None
self.ability_power = None
self.exp = None
from character import*
class player(character):
def __init__(self,name,hp,maxhp,attack_damage,ability_power):
super(player,self).__init__(name, hp, maxhp)
self.attack_damage = attack_damage
self.ability_power = ability_power
This is my player class and below is the class that player gets "maxhp" from.
class character(object):
def __init__(self,name,hp,maxhp):
self.name = name
self.hp = hp
self.maxhp = maxhp
def attack(self,other):
pass
p=player(Players_name, 100, 100, 10, 5,)
while (p.hp>0):
a=input("What do you want to do?")
if a=="Instructions":
Instructions()
elif a=="Commands":
Commands()
elif a=="Fight":
print("Level",wave,"Wave Begins")
if wave < 6:
b = random_enemies.weak()
print("A",b,"Appeared!")
print("Stats of",b, ": \n Health=", b.hp,"Attack Damage=",b.attack_damage)
continue
I just made this really quickly just to test if everything I had was working until I got the error. This is also the place where random_enemies.weak() was called. Also in this is where I defined what "p" was.

So, first of all, follow a naming convention. For python code I recommend that you use pep8 as a convention.
You have a problem with classes vs. instances in your code. First, you need an instance of a class before you can use it:
enemy = random_enemy() # a better name would be RandomEnemy
In Python, all methods start with self, and you need to pass to the method the arguments that it needs to do its work. weak is a method, so it should be more like this:
def weak(self, player):
# the method for weak ... weak attack ?
# remember to change p to player, which is more meaningful
...
Now that you have your instance and it has a method weak which receives a player as argument, you can use it as follows:
# you can't use random_enemy here as you tried because it is a class
# you need a random_enemy instance, the enemy that I talked about earlier
b = enemy.weak(player) # renamed p to player because it is more meaningful
For this all to work, you will need one more thing. weak() needs to return something. Right now you are using what it returns, nothing! The code that you posted is b = random_enemies.weak(). Because weak() does not have a return clause, b will always be None.
Some notes: Avoid one-letter variables unless there is a long standing convention (like using i for loop counter). It is easier to understand what you are trying to do if you define player instead of just p.
Python has a really great tutorial for all this stuff.

Related

Change the class inside a class with different arguments

I'm a structured programming guy. So my attempts with object oriented programming are always "work in progress..."
My intent is to have a class which will adapt itself according to an external input. I saw in another post (which I was unable to find again) that I can change the class of an object, so I made this MWE, which works:
class Base:
def __init__(self, name):
self.name = name
def set_text(self, text):
self.text = text
class Terminator(Base):
terminator = '!'
def __init__(self):
super().__init__('terminator')
def get(self):
return self.text + terminator
class Prefix(Base):
def __init__(self):
super().__init__('prefix')
def get(self):
return str(len(self.text)) + self.text
class_list = {
'terminator': Terminator,
'prefix': Prefix
}
class Selector():
def __init__(self, option):
self.__class__ = class_list[option]
def main():
selection = input("Choose 'terminator' or 'prefix': ")
obj = Selector(selection)
obj.set_text('something')
print(obj.get())
if __name__ == '__main__':
main()
Terminator is a class to produce a text terminated with a special character (!); Prefix produces the same text prefixed with its length.
With Selector, I can use o = Selector('prefix') to get o as a Prefix instance.
The question
My question is if I can add extra arguments to Selector and pass them to the respective class. For example:
o = Selector('prefix', number_of_digits = 2) # '05hello' intead of '5hello'
or
o = Selector('terminator', terminator = '$') # use '$' instead of '!'
For now, I couldn't figure out how to accomplish this task. I tried to use *args and **kwargs, but unsuccessfully.
Additional information
The code I'm working on is intended to undergraduate students and I want to make it simple for teaching purposes, so Selector should be used to hide other classes and their details from the students (to hide Terminator and Prefix, for example).
I expect to have about 15 distinct classes to hide behind Selector.
Also, I'm ready to hear I'm completely wrong about this approach if there are alternatives.
Try calling the appropriate class's __init__() manually, and set the variables like you otherwise would:
class Terminator(Base):
# make terminator an instance variable instead of a class variable,
# and set it as an overridable default arg for the constructor
def __init__(self, terminator='!'):
super().__init__('terminator')
self.terminator = terminator
def get(self):
return self.text + self.terminator
class Selector():
def __init__(self, option, *args, **kwargs):
self.__class__ = class_list[option]
self.__class__.__init__(self, *args, **kwargs)
...
o = Selector('terminator', terminator='$')
o.set_text("Hello World")
print(o.get())
# Hello World$
I should leave a disclaimer: what you're trying to do is essentially a version of the Factory method pattern, which is usually easier to maintain if you bundle it into a method instead of messing with class types and reflection:
def Selector(option: str, *args, **kwargs) -> Base:
return class_list[option](*args, **kwargs)
# this will do .__new()__ and .__init__() normally,
# and is indistinguishable from normal class creation
Using a method to do this instead of overriding the class metadata also has the advantage of being easy to fit into a type system (see the type hinting in the above snippet), which is difficult to do with .__init__(). This is a common design pattern in Java, for example, which is very strongly and statically typed, requires a factory method to have a signature with the superclass of anything it could possibly return, and makes it impossible for an object to change its own type at runtime.
The disadvantage of your current approach, dynamically changing .__class__, is that the .__new__() and .__init__() methods which were called on the resulting object will not match with each other (it would be using Selector.__new__() but Terminator.__init__(), for example), which may cause weird and hard-to-diagnose problems in the future. It's a fun experiment, but be knowledgeable of the risks before using this in something you'll have to maintain for a long time.

Trouble working with and updating dictionary using a class and function in Python 3 [Newbie]

I am somewhat new to coding. I have been self teaching myself for the past year or so. I am trying to build a more solid foundation and am trying to create very simple programs. I created a class and am trying to add 'pets' to a dictionary that can hold multiple 'pets'. I have tried changing up the code so many different ways, but nothing is working. Here is what I have so far.
# Created class
class Animal:
# Class Attribute
classes = 'mammal'
breed = 'breed'
# Initializer/Instance Attribrutes
def __init__ (self, species, name, breed):
self.species = species
self.name = name
self.breed = breed
# To get different/multiple user input
#classmethod
def from_input(cls):
return cls(
input('Species: '),
input('Name: '),
input('Breed: ')
)
# Dictionary
pets = {}
# Function to add pet to dictionary
def createpet():
for _ in range(10):
pets.update = Animal.from_input()
if pets.name in pets:
raise ValueError('duplicate ID')
# Calling the function
createpet()
I have tried to change it to a list and use the 'append' tool and that didn't work. I am sure there is a lot wrong with this code, but I am not even sure what to do anymore. I have looked into the 'collections' module, but couldn't understand it well enough to know if that would help or not. What I am looking for is where I can run the 'createpet()' function and each time add in a new pet with the species, name, and breed. I have looked into the sqlite3 and wonder if that might be a better option. If it would be, where would I go to learn and better understand the module (aka good beginner tutorials). Any help would be appreciated. Thanks!
(First of all, you have to check for a duplicate before you add it to the dictionary.)
The way you add items to a dictionary x is
x[y] = z
This sets the value with the key y equal to z or, if there is no key with that name, creates a new key y with the value z.
Updated code:
(I defined this as a classmethod because the from_input method is one as well and from what I understand of this, this will keep things working when it comes to inheriting classes, for further information you might want to look at this)
#classmethod
def createpet(cls):
pet = cls.from_input()
if pet.name in cls.pets:
raise ValueError("duplicate ID")
else:
cls.pets[pet.name] = pet

How to change instance methods of a super class in subclasses?

While i was doing exercises on inheritance concept of OOP, a question appeared in my mind, the question is about the below exercise:
import random
import time
class Character():
Character_list = []
def __init__(self, name, life=100, power=100):
self.name = name
self.life = life
self.power = power
def print(self):
print("\n{}\n{} character details:".format("-" * 80, self.name))
print("Name: {}\nLife: {}\nPower: {}\n{}".format(self.name, self.life, self.power, "-" * 80))
def attack(self):
attack = random.randrange(self.power + 1)
if self.power <= 0:
print("{} can't attack because power is empty".format(self.name))
return False
else:
print("{} is attacking...".format(self.name))
for i in range(80):
time.sleep(0.05)
print("-", end="", flush=True)
self.power -= attack
print("\n- Spent power: {}".format(attack))
print("- Remaining power: {}\n".format(self.power))
if attack == 0:
print("{} missed.".format(self.name))
return attack
def attacked(self, attack):
if attack != 0:
print("{} injured.".format(self.name))
self.life -= attack
print("- Remaining life: {}".format(self.life))
if self.life <= 0:
print("{} died.".format(self.name))
self.Character_list.remove(self)
class Barbarian(Character):
Character_list = []
class Wizard(Character):
Character_list = []
The question i would like to ask about the above codes is: There’s a super class called Character. The later created Wizard and Barbarian subclasses inherit all the functions of the super class. Suppose to be that the name of the power instance attribute from the super class is different for each subclass. For example, for the Barbarian subclass, it is strength, and for the Wizard subclass it is magic instead of power.
Should instance methods of the super class be copied to the subclasses, in order that each subclass uses the instance attribute of its own class instead of the power name, the super class instance methods should be renamed in the subclasses with their names changed? Or, for the action I want to do, how would you suggest a method other than rewriting the super class instance methods to subclasses by changing the name of the power? Thank you.
Full disclosure - I'm a c# dev trying to answer a python question (so this answer may turn out to be unintentionally hilarious...)
The two easiest avenues I can think of:
A) Declare a variable in your base class and then change your constructor to require a 'PowerWord' be passed in (and then put its value into your variable.) Any time anything tries to declare a new instance of some sort of character, they need to pass in what the PowerWord for that character will be (though I'd imagine you'd just have the derived classes pass it in.)
B) Use an abstract property. This basically says, "Hey, if you want to write a class that derives from me, you'd better have a 'PowerWord' property." Then, this lets you use that PowerWord up in the base class, without having to specify exactly what the PowerWord is (because every derived class will have an answer to what it's supposed to be.)
... anyway, yeah - you had the right idea. You should definitely not copy/paste those functions into each derived class just to change 'power' to 'strength' or to 'magic'.

Passing a Function as a Parameter for a Class, eventually turning it into a method

Sorry if the title is confusing. I'm writing a minimalist game engine, and trying to define a class called "Area" where if the player enters the area, a function defined by the user happens. For example, one could create an instance
Area(location,function) that would fire function on the player when the player enters location (for the sake of simplicity, let it be a point or something).
Note: in pseudo-python
# in init.py
...
def function(player):
kill player
deathZone = Area(location,function)
--------------------------------------
# in player.update()
...
for area on screen:
if player in area:
Area.function(player)
The point of this is that the developer (aka me) can use any function they choose for the area. Is there anyway to do this, or should I try a better approach?
Sure, this kind of thing is certainly possible. In python, everything is an object, even a function. So you can pass around a function reference as a variable. For example try the following code:
import math
def rectangle(a, b):
return a*b
def circle(radius):
return math.pi * radius**2
class FunctionRunner(object):
def __init__(self):
self.userFunction = None
self.userParams = None
def setUserFunction(self, func, *params):
self.userFunction = func
self.userParams = params
def runFunction(self):
return self.userFunction(*self.userParams)
if __name__ == '__main__':
functionRunner = FunctionRunner()
functionRunner.setUserFunction(rectangle, 6, 7)
print(functionRunner.runFunction())
functionRunner.setUserFunction(circle, 42)
print(functionRunner.runFunction())
Here you have two functions that are defined for an area, and a class called FunctionRunner which can run any function with any number of input arguments. In the main program, notice that you need only pass the reference to the function name, and any input arguments needed to the setUserFunction method. This kind of thing will allow you to execute arbitrary code on the fly.
Alternatively, you could also replace a method on your class with a reference to another function (which is what you are asking), though this seems less safe to me. But it is certainly possible. For example you could have a class like this:
class FunctionRunner2(object):
def __init__(self):
pass
def setUserFunction(self, func):
self.theFunction = func
def theFunction(self, *params):
pass
And then do this:
if __name__ == '__main__':
functionRunner2 = FunctionRunner2()
functionRunner2.setUserFunction(rectangle)
print(functionRunner2.theFunction(6,7))
functionRunner2.setUserFunction(circle)
print(functionRunner2.theFunction(42))

Entry Validation going wrong with tkinter

I am currently learning the tkinter basics and I'm building a small, super-simple program to test my knowledge on some of the most basic widgets.
I am having a problem with validation and an entry, possibly because of my lack of understanding in the matter... This poses three questions:
1 - How to do what was done here: https://stackoverflow.com/a/4140988/2828287 without the class part. Just doing it when the script runs.
2 - What are all those self. and .self doing there? Which ones are there because that is a class, and which ones are there because of the validating method itself??
3 - What's wrong in my code? based in this explanation >> http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
from tkinter import *
from tkinter import ttk
# function that should take the '%d' replacer and only validate if the user didn't delete
def isOkay(self, why):
if why == 0:
return False
else:
return True
okay = entry.register(isOkay) # didn't understand why I had to do this, but did it anyway...
entry = ttk.Entry(mainframe, validate="key", validatecommand=(okay, '%d'))
# the mainframe above is a ttk.Frame that contains all the widgets, and is the only child of the usual root [ = Tk()]
entry.grid(column=1,row=10) # already have lots of stuff on upper rows
The error I'm getting goes like this:
"NameError: name 'entry' is not defined"
I've tried to change the order of things, but there's always one of these errors.. It points to the line where I do the .register() stuff
--EDITED CODE--
This doesn't throw me an error, but still allows me to delete...
def isOkay(why):
if (why == 0):
return False
else:
return True
okay = (**root**.register(isOkay), "%d")
entry = ttk.Entry(mainframe, validate="key", validatecommand=okay)
entry.grid(column=1,row=10)
(where the 'root' part is written between ** **, does it have to be the root? Or it can be any parent of the widget that is going to use that? Or it has to be the immediate parent of it?
for instance, I have:
root >> mainframe >> entry. Does it have to be root, mainframe, or could be both?)
All usages of self are due to the use of classes. They have absolutely nothing to do with the validation. Nothing at all.
Here's an example without using classes, and without the long comment describing the validation function:
import Tkinter as tk
def OnValidate(d, i, P, s, S, v, V, W):
print "OnValidate:"
print "d='%s'" % d
print "i='%s'" % i
print "P='%s'" % P
print "s='%s'" % s
print "S='%s'" % S
print "v='%s'" % v
print "V='%s'" % V
print "W='%s'" % W
# only allow if the string is lowercase
return (S.lower() == S)
root = tk.Tk()
vcmd = (root.register(OnValidate),
'%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
entry = tk.Entry(root, validate="key",
validatecommand=vcmd)
entry.pack()
root.mainloop()
Note: the point of registering a command is to create a bridge between the underlying tcl/tk engine and the python library. In essence it creates a tcl command that calls the OnValidate function, giving it the supplied arguments. This is necessary because tkinter failed to provide a suitable interface to the input validation features of tk. You don't need to do this step if you don't want all of the fancy variables (%d, %i, etc).
The error NameError: name 'entry' is not defined is because you are using entry before you define what entry is. One of the benefits of using classes is that it allows you define methods further down in the file than where you use them. By using a procedural style you are forced to define functions before they are used*.
* technically speaking, you always have to define functions before they are used. In the case of using classes, you don't actually use the methods of a class until after you've created the instance of the class. The instance isn't actually created until very near the end of the file, which lets you define the code well before you use it.

Resources