Is there a way to get a random number to repopulate inside of a function after when calling the function again? - python-3.x

I am fairly new to Python and I know values called in a function are only there inside the function. I am trying to have a battle between a player and a boss in a small text game I am writing; however, It just keeps populating the same information each time the function is called. I feel like I am missing something. Any help would be appreciated.
The classes:
class Character:
def __init__(self, name, stats):
self.name = name
self.stats = stats
name = {
"Name": ""
}
stats = {
"Dexterity": "",
"Strength": "",
"Health": 20,
"AC": 16,
"Weapon": "",
}
damage = 2 * random.randrange(1, 7)
ability_check = random.randrange(1, 20)
initiative = random.randrange(1,20)
class Boss:
def __init__(self, name, stats):
self.name = name
self.stats = stats
name = {
"Name": "Gargamel"
}
stats = {
"AC": 16,
"Health": 15,
"Weapon": "Sword"
}
damage = random.randrange(1, 6)
initiative = random.randrange(1,20)
The functions:
def battle():
choice = input("Do you wish to continue fighting or run? F or R ")
if (choice.lower() == 'f'):
boss_battle()
if (choice.lower() == 'r'):
pass
def boss_battle():
print("The skeletal creature grabs a sword from the wall and takes a swing at you...\n")
print(f"Boss init {Boss.initiative}, Character init {Character.initiative}")
while Boss.stats["Health"] > 0 or Character.stats["Health"]:
if (Boss.initiative > Character.initiative):
boss_damage = Boss.damage
current_player_health = (Character.stats["Health"] - boss_damage)
Character.stats.update({"Health": current_player_health})
print(f"The boss did {boss_damage} damage. You now have {current_player_health} hitpoints left.")
if (Character.stats["Health"] <= 0):
print('You died!')
break
battle()
elif (Character.initiative > Boss.initiative):
player_damage = Character.damage + stat_block_str(int)
current_boss_health = Boss.stats["Health"] - player_damage
Boss.stats.update({"Health": current_boss_health})
print(f"You attacked the creature with your {Character.stats['Weapon']} and dealt {player_damage} damage.")
if (Boss.stats["Health"] <= 0):
print(f'Congratulations {Character.name["Name"]}! You have beaten the boss and claimed the treasure!')
break
battle()

You have declared classes with class variables, but have made no class instances, so the values are all fixed due to being initialize once when the class was defined.
To make a class instance, you "call" the class using parentheses, which calls your __init__ function on the instance, which sets instance variables.
Here's a small example:
import random
class Character:
def __init__(self,name):
self.name = name
self.health = 20
self.damage = 2 * random.randrange(1,7)
def attack(self,target):
print(f'{self.name} attacks {target.name}...')
target.health -= self.damage
print(f'{target.name} has {target.health} health remaining.')
# defines how to display the class instance when printed.
def __repr__(self):
return f"Character(name={self.name!r}, health={self.health}, damage={self.damage})"
fred = Character('Fred')
george = Character('George')
print(fred)
print(george)
print(f'{fred.name} can deliver {fred.damage} damage.')
fred.attack(george)
Output:
Character(name='Fred', health=20, damage=4)
Character(name='George', health=20, damage=10)
Fred can deliver 4 damage.
Fred attacks George...
George has 16 health remaining.

While true, that your using class variables when you should better use instance variables (see Instance variables vs. class variables in Python amongst others for details) I don't think it is enough to achieve your goal.
What you describe in the comment happens because the class is only setup once and from there on the class variables like damage etc. are static, meaning the random call is only executed once for the whole program. I would suggest to convert the damage variables to functions like this:
class Character:
def __init__(self,name):
self.name = name
self.health = 20
# just as a simple example
self.base_damage = 2
def damage(self):
return self.base_damage * random.randrange(1,7)
This way you will obtain a new random value for call to the function (battle round) and not just have a static value for damage inside the class.
Assuming you made instances for the character and the boss the call can look like this:
current_boss_health = bossInstance.stats["Health"] - characterInstance.damage()
If you want to take this on step further and make this a bit more pythonic you can use the #property decorator for damage:
#property
def damage(self):
return self.base_damage * random.randrange(1,7)
This way the usage of the function will look even more like your original and it hides the function call:
current_boss_health = bossInstance.stats["Health"] - characterInstance.damage

Related

How in Pyhton's OOP a class's method access to another class's method?

I am new to Python OOP and I am trying to learn the basics of Python OOP and I came across a video on YouTube that teaches the basics of it. The code is an example code from the video. I understood all of it but I am not able to understand how the class "Course's" "get_average_grade()" method is accessing the class "Student's" "get_grade()" method? Any help is highly appreciated.
class Student:
def __init__(self, name, age, grade):
self.name = name
self.age = age
self.grade = grade # 0-100
def get_grade(self): #<---- This method was used inside the Course class
return self.grade
class Course:
def __init__(self, name, max_students):
self.name = name
self.max_students = max_students
self.students = []
def add_student(self, student):
if len(self.students) < self.max_students:
self.students.append(student)
return True
return False
def get_average_grade(self):
value = 0
for student in self.students:
value = value + student.get_grade() #<---- This Method is from the Student class
return value / len(self.students)
s1 = Student("Tim", 19, 95)
s2 = Student("Bill", 19, 75)
s3 = Student("Jill", 19, 65)
course = Course("Science", 2)
course.add_student(s1)
course.add_student(s2)

Why does my code say NameError: name 'self' is not defined'

Sorry if this is a really stupid question since I am terrible at python and most of my knowledge consists of the very restricted things you are taught before post 16 education. Basically I'm trying to do a coding project in preparation for when my classes start in September, and so far I've managed to get by teaching myself classes using online websites and people's online forum questions. However, I've come into a bit of a roadblock because my code keeps throwing an error. I've looked on websites and forums but they seem to be in very different situations compared to me and some of them just seem to tell me what I've done is right. The exact error given is: line 34, in returnBarbarianStats
print(self.name,"the barbarian's stats:")
NameError: name 'self' is not defined
import random
def getName():
syllables = ['en','da','fu','ka','re','toh','ko','noh','tuk','el','kar']
firstName = (random.choice(syllables))
secondName = (random.choice(syllables))
thirdName = (random.choice(syllables))
global generatedName
generatedName = firstName+'-'+secondName+'-'+thirdName
# Classes-all creatures have names generated the same way and have the same amount of health.
# The way I have selected how each subclass will be randomly chosen is having the code select a random value
# from the list and depending on which is chosen it will give a subclass.
class preset():
def _init_(self, creature, name, health=100):
self.name = generatedName
self.health = 100
getName()
self.name=generatedName
#Gives different attributes to each sub-class
class barbarian(preset):
def _init_(self, name, power=70, specialAttackPower=20, speed=50):
preset._init_(self, creature, name, health=100)
self.power = power
self.specialAttackPower = specialAttackPower
self.speed = speed
self.name = name
def returnBarbarianStats():
print(self.name,"the barbarian's stats:")
print("Health:",self.health)
print("Power damage:",self.power)
print("Special attack power damage:",self.specialAttackPower)
print("Speed:",self.speed)
class elf(preset):
def _init_(self, name, power=30, specialAttackPower=60, speed=10):
preset._init_(self, creature, name, health=100)
self.power = power
self.specialAttackPower = specialAttackPower
self.speed = speed
class wizard(preset):
def _init_(self, name, power=50, specialAttackPower=70, speed=30):
preset._init_(self, creature, name, health=100)
self.power = power
self.specialAttackPower = specialAttackPower
self.speed = speed
class dragon(preset):
def _init_(self, name, power=90, specialAttackPower=40, speed=50):
preset._init_(self, creature, name, health=100)
self.power = power
self.specialAttackPower = specialAttackPower
self.speed = speed
class knight(preset):
def _init_(self, name, power=60, specialAttackPower=10, speed=60):
preset._init_(self, creature, name, health=100)
self.power = power
self.specialAttackPower = specialAttackPower
self.speed = speed
#10 randomly generated characters.
i = 0
army = []
while i < 10:
creatures = ['barbarian','elf','wizard','dragon','knight']
creatureType = (random.choice(creatures))
if creatureType == 'barbarian':
army.append(barbarian())
elif creatureType == 'elf':
army.append(elf())
elif creatureType == 'wizard':
army.append(wizard())
elif creatureType == 'dragon':
army.append(dragon())
elif creatureType == 'knight':
army.append(knight())
i = i + 1
barbarian.returnBarbarianStats()
I've just given the whole code as I don't want to miss any important details out.
you missed self in the parameters buddy :)
[line 32]
def returnBarbarianStats():
correct it to
def returnBarbarianStats(self):

how to access to data from a class which is stored in another class in python?

This is my code. I got a problem when i want to print the information inside the class 'pokemon'
class trainer(object):
def __init__(self, name, pokemons = [], money = 0):
self.name = name
self.pokemons = pokemons
self.money = money
this is my first class which has every pokemon per trainer
class pokemon(object):
def __init__(self, name, attribute, attacks = {}, health = '==========='):
self.name = name
self.attribute = attribute
self.health = health
self.attacks = attacks
The other class where I take the pokemon to import to the other class
class fight():
def __init__(self, fighter1, fighter2):
self.fighter1 = fighter1
self.fighter2 = fighter2
def fighting(self):
if len(Trainer1.pokemons) >= 1 and len(Trainer2.pokemons) >= 1:
print('{} wanna fight against {}'.format(Trainer1.name, Trainer2.name))
keepgoing = True
print('{} got this Pokemons: '.format(Trainer1.name))
i = 0
for i in Trainer1.pokemons:
print(i)
#while (keepgoing):
else:
print('You gotta have pokemons to fight')
return False
I thought that creating a class named fight for getting in battle would be the most wise idea but I'd like to know another method to do it
Pokemon1 = pokemon('Charizard', 'Fire', attacks={'1':'ball fire', '2':'cut', '3':'fire blast', '4':'mega kick'})
Pokemon2 = pokemon('Charmander', 'fire', attacks={'1':'blast', '2':'scratch', '3':'heat', '4':'tear'})
Trainer1 = trainer('Santiago', pokemons=[Pokemon1, Pokemon2])
Pokemon3 = pokemon('Charizard', 'Fire', attacks={'1':'ball fire', '2':'cut', '3':'fire blast', '4':'mega kick'})
Pokemon4 = pokemon('Charmander', 'fire', attacks={'1':'blast', '2':'scratch', '3':'heat', '4':'tear'})
Trainer2 = trainer('Alejandra', pokemons=[Pokemon3, Pokemon4])
Okay my problem is in the class fight. when i want to print the names of the pokemons i get the following message:
Santiago got this Pokemons:
<__main__.pokemon object at 0x000002AAD9B64D00>
<__main__.pokemon object at 0x000002AAD9B92DF0>
i know that the pokemon class has various instances, but how can i access to them?
To make your life easier, I recommend that you implement the __str__ dunder method on pokemon. This will resolve the issue that you are seeing right now, and make future prints of pokemon much easier.
That would look something like this:
class pokemon(object):
def __init__(self, name, attribute, attacks = {}, health = '==========='):
self.name = name
self.attribute = attribute
self.health = health
self.attacks = attacks
def __str__(self):
return "Pokemon: %s (Health: %11s)" % (self.name, self.health)
When you print the 'Charmander' pokemon, it'll look something like this:
Pokemon: Charmander (Health: ===========)
Of course, you can change the return of the __str__ to return whatever you want out of the pokemon.

How to remove an object from list by a value

My problem is that I created a list of students with name and number. The task is now to remove a student by his number. My problem is that my code doesn't work.
Another problem is that it always shows the memory address instead of the value of the object.
Thanks in advance
class Student:
def __init__(self, name, number):
self.name = name
self.number = number
from .student import Student
class Course:
def __init__(self, name, code, credit, student_limit):
self.name = name
self.code = code
self.credit = credit
self.student_limit = student_limit
students = []
def add_student(self, new_student):
self.student = new_student
self.students.append(new_student)
print("Student added" +str(self.students))
def remove_student_by_number(self, student_number):
self.student_number = student_number
if student_number in self.students: self.students.remove(student_number)
print("Student removed" + str(self.students))
from .course import Course
class Department:
def __init__(self, name, code):
self.name = name
self.code = code
courses = []
def add_course(self, course):
self.course = course
self.courses.append(course)
print("Course added" +str(self.courses))
from python import *
def main():
alice = Student("Alice", 1336)
bob = Student("Bob", 1337)
math_dept = Department("Mathematics and Applied Mathematics", "MAM")
math_course = Course("Mathematics 1000", "MAM1000W", 1, 10)
math_dept.add_course(math_course)
math_course.add_student(bob)
math_course.add_student(alice)
math_course.remove_student_by_number(alice.number)
if __name__ == "__main__":
main()
self.students is a list of Student instance so it will print the instance's memory address if the method __str__ is not implemented (see here for example). You should try to print each property like student.name or student.number explicitly.
Anyway you are trying to find student_number in list of Student so of course it will never run the remove line. Instead use if student_number in [student.number for student in self.students] which is looking up the student's number list, not the student list itself. However in this case, you also want to remove the student with the student_number as the input so you may need to use a loop like this:
def remove_student_by_number(self, student_number):
for student in self.students:
if student.number == student_number:
print("Student removed" + str(student.name))
self.students.remove(student)
break

Accessing items within a class in Python?

I am very new to Object oriented programming and I am having trouble accessing items in my class when I run my main method. My program is trying to allow a user to add item prices to a cart until they are finished and prints the number of items and total.
class CashRegister:
print("Welcome to shopping world!")
def __init__(self, price):
self.price = price
def addItem(self, price):
CashRegister.totalPrice = CashRegister.totalPrice + price
CashRegister.itemCount = CashRegister.itemCount + 1
#property
def getTotal(self):
return totalPrice
#property
def getCount(self):
return itemCount
def main():
selection = "Y"
while selection != "N":
selection = input("Would you like to add another item to the
cart Y or N")
selection = selection.upper()
if selection == "Y":
price = input("What is the price of the item?")
CashRegister.addItem(price)
else:
print(CashRegister.getCount)
print(CashRegister.getTotal)
print(selection)
main()
Here is the error I am getting when I select yes:
TypeError: addItem() missing 1 required positional argument: 'price'
Here is the output I am getting when I select no:
Welcome to shopping world!
Would you like to add another item to the cart Y or Nn
<property object at 0x0000022CFFCA2598>
<property object at 0x0000022CFFCA2548>
N
first, you don't use the class name for declaring variables in its methods: you have self for that (which you can rename to whatever you like, but 'self' is convention)
second, you have to initialize your class object in the main function, otherwise Python won't know what to do with the methods you call (when you define a method in a class, the first argument self stands for the class object, so each time you initialize an object and then call a method on that, the argument you pass inside the brackets is actually the second argument, first one being the object itself)
third: this is more of a style thing, but you don't really use CamelCase in python except for names of classes, all the rest is in snake_case
fourth: += is more readable and faster than example = example + 1
class CashRegister(object) :
def __init__(self) :
self.total_price = 0
self.item_count = 0
def add_item(self, price) :
self.total_price += int(price)
self.item_count += 1
def get_total(self) :
return self.total_price
def get_count(self) :
return self.item_count
def main() :
register = CashRegister()
selection = True
while selection :
selection = input("Would you like to add another item to the cart Y or N\n\t").upper()
if selection == "Y" :
price = input("What is the price of the item?\n\t")
register.add_item(price)
else :
print(register.get_total())
print(register.get_count())
print(selection)
selection = False
main()
this is how I would probably do it, I've taken out the #property decorators because I don't know if you really have a need for them there, you can just call the methods with brackets () at the end to get what you want
Then, there's a bunch more stuff you should do if you really want to to use this, and that would be exception catching, determine how the cash register behaves if a negative value is passed as price, and so on... good luck and enjoy Python
you have many mistakes you need the determine totalprice and itemcount in self, you need to determine a variable with cashregister class
class CashRegister:
print("Welcome to shopping world!")
def __init__(self):
self.totalPrice=0
self.itemCount=0
def addItem(self, price):
self.totalPrice = self.totalPrice + price
self.itemCount = self.itemCount + 1
#property
def getTotal(self):
return self.totalPrice
#property
def getCount(self):
return self.itemCount
def main():
selection = "Y"
box=CashRegister()
while selection != "N":
selection = input("Would you like to add another item to thecart Y or N\n\t:")
selection = selection.upper()
if selection == "Y":
price = input("What is the price of the item?\n\t:")
box.addItem(int(price))
else:
print(box.getCount)
print(box.getTotal)
print(selection)
main()

Resources