I'm still learning python so I decided to experiment by making a tiny text adventure. I'm only a student in highschool so I haven't had the chance to learn many common techniques used in code as most of my learning has been self taught, so I wanted to take this opportunity to have some kind of review of some of the techniques I use in this code.
Here it is in it's entirety:
class Character:
def __init__(self, class_name, strength, dexterity, intelligence):
self.class_name = class_name
self.strength = strength
self.dexterity = dexterity
self.intelligence = intelligence
self.health = strength*10
def print_stats(self):
return self.class_name + " Strength: " + str(self.strength) + " Dexterity: " + str(self.dexterity) + " Intelligence: " + str(self.intelligence)
def print_stat_strength(self):
return "Strength: " + str(self.strength)
def print_stat_dexterity(self):
return "Dexterity: " + str(self.strength)
def print_stat_intelligence(self):
return "Intelligence: " + str(self.strength)
def print_stat_health(self):
return "Max Health: " + str(self.health)
def change_stat_strength(self, new_value):
self.strength = new_value
def change_stat_dexterity(self, new_value):
self.dexterity = new_value
def change_stat_intelligence(self, new_value):
self.intelligence = new_value
def character_select():
global player_class
barbarian = Character("Barbarian", 20, 10, 10)
archer = Character("Archer", 10, 20, 10)
wizard = Character("Wizard", 10, 10, 20)
print("Welcome to TEST TEXT ADVENTURE RPG. Choose a character!")
print("(A): " + barbarian.print_stats())
print("(B): " + archer.print_stats())
print("(C): " + wizard.print_stats())
while True:
user_answer = input().upper()
if user_answer == "A":
player_class = barbarian
break
elif user_answer == "B":
player_class = archer
break
elif user_answer == "C":
player_class = wizard
break
else:
print("Invalid input! Try again.")
print("####CHARACTER CREATION COMPLETE####")
def main():
character_select()
print(player_class.print_stats())
main()
Looking at this, my main concern is how the main function is utilized. To my knowledge it is supposed to be relatively clean and made up of other functions, lacking a lot of original code of its own. Making the above, I came into an issue trying to use the player_class object that I created, since it was created in the character_select function, it could only be used there unless it was global. The global fix seems kind of clunky so I was wondering the proper way to implement this kind of thing.
One other concern that I can identity could be the built in functions in the Character class. Do I need to make a separate function for every single stat like strength, dexterity, intelligence, or is it possible to do something like this?
def change_stat(self, stat_to_change, new_number):
self.stat_to_change = new_number
Now I'm asking this question because I tried the above code on my own to try to cut down on the amount of similar functions for each individual stat, but I couldn't seem to use it how I imagined.
Those are my main concerns but anybody reading this is welcome to bring up any other improper coding technique. I really don't want bad habits to manifest themselves and continue to appear if I can help it.
1 Answer 1
There is a way to pass the attribute name which you want to change, you can use setattr
:
def change_stat(self, stat_to_change, new_number):
setattr(self, stat_to_change, new_number)
However, there is no difference to what would happen if you just say self.strength = 10
instead of self.change_stat("strength", 10)
.
Regarding the need for global
for the player character, there isn't. Just return the chosen class from the character_select
function:
def character_select():
barbarian = Character("Barbarian", 20, 10, 10)
archer = Character("Archer", 10, 20, 10)
wizard = Character("Wizard", 10, 10, 20)
print("Welcome to TEST TEXT ADVENTURE RPG. Choose a character!")
print("(A): " + barbarian.print_stats())
print("(B): " + archer.print_stats())
print("(C): " + wizard.print_stats())
while True:
user_answer = input().upper()
if user_answer == "A":
return barbarian
elif user_answer == "B":
return archer
elif user_answer == "C":
return wizard
else:
print("Invalid input! Try again.")
You could make it even nicer if you used a dictionary:
from collections import OrderedDict
def character_select():
classes = OrderedDict([("A", Character("Barbarian", 20, 10, 10)),
("B", Character("Archer", 10, 20, 10)),
("C", Character("Wizard", 10, 10, 20))])
print("Welcome to TEST TEXT ADVENTURE RPG. Choose a character!")
for key, cls in classes.items():
print("({}): {}".format(key, cls.print_stats()))
while True:
user_answer = input().upper()
try:
return classes[user_answer]
except KeyError:
print("Invalid input! Try again.")
You can then use it like this in your main
function:
def main():
player_class = character_select()
print(player_class.print_stats())
You should probably rename your Character.print_stats
function. Right now it sounds like it would actually print the stats, but it just returns them. So I would make your class like this:
class Character:
def __init__(self, class_name, strength, dexterity, intelligence):
self.class_name = class_name
self.strength = strength
self.dexterity = dexterity
self.intelligence = intelligence
self.health = strength*10
def __str__(self):
return "{self.class_name} " \
"Health: {self.health} " \
"Strength: {self.strength} " \
"Dexterity: {self.dexterity} " \
"Intelligence: {self.intelligence}".format(self=self)
If you are using Python 3.6, you can use f"{self.class_name} ..."
instead of "{self.class_name} ...".format(self=self)
, which uses f-strings.
Because I named the function __str__
, you can now do:
barbarian = Character("Barbarian", 20, 10, 10)
print(barbarian)
# Barbarian Health: 200 Strength: 20 Dexterity: 10 Intelligence: 10
And the loop in character_select
becomes this, because str.format
automatically calls str
on its arguments, which will then invoke the Character.__str__
function:
for key, cls in classes.items():
print("({}): {}".format(key, cls))
__str__
is one of the magic (or dunder) methods, which can give your class features so it acts like built-in classes. Have a look at this nice guide for a list and explanation of all of them.
Finally, you should add a if __name__ == "__main__":
guard to allow importing your functions from other scripts:
if __name__ == "__main__":
main()
-
\$\begingroup\$ Thank you for taking the time to write such a detailed response! \$\endgroup\$Carcanken– Carcanken2017年04月20日 21:15:01 +00:00Commented Apr 20, 2017 at 21:15