3
\$\begingroup\$

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.

\$\endgroup\$
1
\$\begingroup\$

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()
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Thank you for taking the time to write such a detailed response! \$\endgroup\$ – Carcanken Apr 20 '17 at 21:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.