# Character Creation in Text Adventure; Proper use of Main and Global Values?

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:
player_class = barbarian
break
player_class = archer
break
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.

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:
return barbarian
return archer
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:
try:
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()

• Thank you for taking the time to write such a detailed response! – Carcanken Apr 20 '17 at 21:15