6
\$\begingroup\$

I just downloaded Python for the first time today, and I had a lot of fun playing around with it for like five hours.

I watched one YouTube video on how to make a text adventure, and then I decided to try my hand at it.

It's my first day, so I'm sure there are a million things that I could do to make my coding more efficient. I basically copied and pasted the majority of the code into every room of the adventure, so mainly I'm looking at shortcuts that would have allowed me to not be so repetitive, but any advice/feedback is appreciated.

def start():
    print('''
****************************************
*                                      *
*  Welcome to Aaron's Text Adventure!  *
*                                      *
****************************************
''')
    global gold
    gold = 2
    global health
    health = 10
    global energy
    energy = 10
    global melee
    melee = 1
    global ranged
    ranged = 0
    global ammo
    ammo = 0
    global fullhealth
    fullhealth = 10
    global attacknum
    attacknum = 0
    cottage()

## Game functions

def checkstats():
    print('''

********''')
    print("Gold:", gold)
    print("Health:", health,"/",fullhealth)
    print("Melee:", melee)
    print ("Energy:", energy)
    global ranged
    if ranged > 0:
        print("Ranged:", ranged)
        print("Ammo:", ammo)

def prompt():
    x = input("Type a command: ")
    return x



##Rooms*********************************************************


## Cottage////

def cottage():
    print("****************************************")
    print('''

You are in your cottage in a small village.
    ''')
    print('''Options:
1. Go Outside
2. Rest
9. Stats
''')

    command = prompt()
    if command == "1":
        hometown()
    elif command == "2":
        print("********")
        print ("You are back to full health. (",fullhealth,"/",fullhealth,")")
        global health
        health = fullhealth
        cottage()
    elif command == "9":
        checkstats()
        cottage()
    else:
        cottage()


## Hometown ////
def hometown():
    print("****************************************")
    print ('''

You are in your hometown. A few villager are walking the streets, going about their business as usual.
    ''')

    print('''Options:
1. Enter your cottage
2. Enter the forest
3. Walk to the coast
9. Stats
''')
    command = prompt()
    if command == "1":
        cottage()
    elif command == "2":
        forest()
    elif command == "3":
        coast()
    elif command == "9":
        checkstats()
        hometown()
    else:
        hometown()








## Forest ////
def forest():
    print('''****************************************

You are in the forest. It's dark and eerie.
    ''')

    ## This is what happens when you enter the forest for the first time
    global attacknum
    if attacknum == 0:
        print('''********
You are attacked by a wild beast! OH NO!!
(-1 health)


''')### It says that you have been attacked
        attacknum = 1 #It records that you have been attacked
        global health
        health = health - 1#Your health goes down


    ##This is what happens when you have already entered the forest before
    if attacknum == 1:
        print('''Options:
1. Go to your hometown
2. Pick up stones
3. Explore deeper into the forest
9. Stats
    ''') ##It gives you your options
        command = prompt()
        if command == ("1"):
          hometown()
        elif command == ("2"):
          find_stones()
          forest()
        elif command == "3":
            battle()
        elif command == "9":
          checkstats()
          forest()
        else:
          forest()






## Coast
def coast():
    print ('''****************************************

You at the coast. The wind blows your hair and the smell of salt fills your nostrils.
    ''')
    print ('''Options:
1. Go to hometown
2. Talk to fisherman
3. Pick up stones
9. Stats''')
    command = prompt()
    if command == "1":
        hometown()
    elif command == "2":
        fishconvo()
    elif command =="3":
        find_stones()
        coast()
    elif command =="9":
        checkstats()
        coast()
    else:
        coast()


##City
def city():
        print('''****************************************
    You arrived!!!!!
         ''')


## Events ***********************************************************
def find_stones():
    global ranged
    global ammo
    if ranged == 0:
        print('''********

You found 5 stones you can throw at an enemy.
(+1 ranged) (+5 ammo) ''')
        ranged = 1
        ammo = 5

    elif ranged >=1 and ammo < 5:
        ammo = 5
        print('''********
You stuff a few stones in your bag for later.''')
    elif ranged >= 1 and ammo > 4:
        print('''
********
        You don't find any suitable stones.''')







## Battle//////////
def battle():
    print('''****************

You have been attacked by a wild pig chicken. What do you do?
''')

    print('''1. Run
2. Fight
3. Make friends with the pig chicken''')
    command = prompt()
    if command == "1":
        forest()
    elif command == "2":
        print('''
*********''')
        print("You have just been brutally raped by a horny pig chicken!! You suck!!")
        print('''

GAME OVER!!!!!''')
    elif command == "3":
        print('''
*********''')
        print("You have just been brutally raped by a horny pig chicken!! You suck!!")
        print('''


GAME OVER!!!!!''')



##Training//////////////
def fishconvo():
    print('''********

Fisherman: "The fish aren't biting today. Want me to teach you a few boxing moves?

1. Yes
2. No''')
    global melee
    command=prompt()
    if command == "1" and melee <2:
        melee = 2
        print('''********
Fisherman: "Now don't go beating up that nerdy crippled kid."
(+1 melee)
''')
        coast()
    elif command == "1" and melee >1:
        print('''********
Fisherman "Looks like I have nothing left to teach you."
        ''')
        coast()
    elif command == "2":
        print(''' ********
Fisherman: "Well, don't come crying to me if you get brutally raped by a horny pig chicken."
''')
        coast()
    else:
        coast()


def boatconvo():
    print('''********
Boat captain: "I can sail you to the city for a nominal fee."

1. Okay
2. No thanks
''')
    command = prompt()
    if command == "1":
        print('''********
The boatman sails you to the capital city.
(-2 gold)
''')
        global gold
        gold = gold -2
        city()
    elif command =="2":
        coast()


start()
\$\endgroup\$
  • \$\begingroup\$ The best practice would be using a class ˋAdventure`: replace global variables by attributes (instance variables). And function by methods. \$\endgroup\$ – Laurent LAPORTE Feb 18 '17 at 6:54
  • \$\begingroup\$ Your game could be implemented by a Finite State Machine where states are location + player properties (gold, health, etc.) and transitions are user inputs. \$\endgroup\$ – Laurent LAPORTE Feb 18 '17 at 6:58
  • 1
    \$\begingroup\$ @LaurentLAPORTE Please do not review code in comments. All critiques belong in answers. \$\endgroup\$ – 200_success Feb 18 '17 at 15:34
4
\$\begingroup\$

As I told you, the best practice is to use a class, I called it Adventure (it sounds encouraging).

The start() function is the starting point which do initialisation. So, let's create a constructor for that:

import textwrap


class Adventure(object):
    def __init__(self):
        self.gold = 2
        self.health = 10
        self.energy = 10
        self.melee = 1
        self.ranged = 0
        self.ammo = 0
        self.full_health = 10
        self.attack_num = 0

    def start(self):
        msg = textwrap.dedent('''
        ****************************************
        *                                      *
        *  Welcome to Aaron's Text Adventure!  *
        *                                      *
        ****************************************
        ''')
        print(msg)
        self.cottage()

    def cottage(self):
        # ...
        pass


def main():
    adventure = Adventure()
    adventure.start()


if __name__ == '__main__':
    main()

The constructor:

  • Initialisation is done in the constructor (the __init__ method);
  • Usually, I prefer not doing anything in the constructor (except initialisation), so I decided to move the print in the start method.
  • I rename the following variable: fullhealth and attacknum that way my editor don't alert me about spelling. If you used to do that, your code will be easier to maintain.

The start method:

  • I use textwrap.dedent() to have good looking messages: it simply remove the indentation;
  • Like you, I trigger the cottage() method, but it will be better to use a Finite State Machine…

main()

  • It is better to put the main code in a main() function (call it as you like). Because, your Adventure class could be use as an API, for instance: one can inherit from it and add more, say, locations…
  • In Python, you use the if __name__ == '__main__' construction do call the main function when the program is executed as a script. See the answer to the question What does if __name__ == “__main__”: do?
\$\endgroup\$
3
\$\begingroup\$

Finite State Machine (FSM):

As I told you, this game can be implemented like a Finite-state machine (FSM).

Where:

  • Each state is a location (cottage, hometown...) or a situation (pick up stones in the forest, in the coast, talking to the fisherman...)
  • Each transition has a condition (the user commands "1", "2", "3", etc.) and an action (a function which display the situation).

A transition can be implemented by a namedtuple, as bellow:

import collections

Transition = collections.namedtuple("Transition",
                                    ["start_state", "end_state", "condition", "action"])

The following transition:

Transition("cottage", "hometown", "1", adventure.hometown)

Can be interpreted like this: “when the user enter the command "1" and the current state is "cottage" then we can trigger the transition to reach the state "hometown" by calling the function adventure.hometown (here, it's a method, see further).

If I want to trigger a transition without user input, the only have to consider that the condition is None, for instance:

    Transition("forest", "forest_stones", "2", adventure.find_stones),
    Transition("forest_stones", "forest", None, adventure.forest),

If the user is in the forest and choose the command "2", he can pick up stones and stay in the forest.

The following class store the game parameters (gold, health, energy...), the current state and the dictionary of transitions.

class Adventure(object):
    def __init__(self):
        self._curr_state = None
        self._transitions = {}
        self.gold = 2
        self.health = 10
        self.energy = 10
        self.melee = 1
        self.ranged = 0
        self.ammo = 0
        self.full_health = 10
        self.attack_num = 0

    def setup(self, transitions):
        for transition in transitions:
            self._transitions[(transition.start_state, transition.condition)] = transition
        self._curr_state = transitions[0].start_state

    def trigger(self, command):
        can_continue = True
        key = self._curr_state, command
        if key not in self._transitions:
            print("Unknown command '{0}'".format(command))
            return can_continue
        while key in self._transitions:
            transition = self._transitions[key]
            can_continue = transition.action()
            self._curr_state = transition.end_state
            key = self._curr_state, None
        return can_continue

    def run(self):
        can_continue = self.trigger(None)
        while can_continue:
            command = prompt("Type a command ('q' to quit): ")
            if command.lower() == 'q':
                print("Good bye!")
                return
            can_continue = self.trigger(command)

    def start(self):
        msg = """\
        ****************************************
        *                                      *
        *  Welcome to Aaron's Text Adventure!  *
        *                                      *
        ****************************************
        """
        print(textwrap.dedent(msg))
        return True

    def check_stats(self):
        msg = textwrap.dedent("""\
        ********

        Gold:   {gold}
        Health: {health}/{full_health}
        Melee:  {melee}
        Energy: {energy}
        """)
        if self.ranged:
            msg += textwrap.dedent("""\
            Ranged: {ranged}
            Ammo:   {ammo}
            """)
        msg = msg.format(gold=self.gold,
                         health=self.health,
                         full_health=self.full_health,
                         melee=self.melee,
                         energy=self.energy,
                         ranged=self.ranged,
                         ammo=self.ammo)
        print(msg)
        return True

    def cottage(self):
        msg = """\
        ****************************************

        You are in your cottage in a small village.

        Options:
        1. Go Outside
        2. Rest
        9. Stats
        """
        print(textwrap.dedent(msg))
        return True

    def back_to_full_health(self):
        self.health = self.full_health
        msg = """\
        You are back to full health. ({health}/{full_health})
        """.format(health=self.health, full_health=self.full_health)
        print(textwrap.dedent(msg))
        return True

    def hometown(self):
        msg = """\
        ****************************************

        You are in your hometown. A few villager are walking the streets, going about their business as usual.

        Options:
        1. Enter your cottage
        2. Enter the forest
        3. Walk to the coast
        9. Stats
        """
        print(textwrap.dedent(msg))
        return True

    def forest(self):
        msg = """\
        ****************************************

        You are in the forest. It's dark and eerie.

        """
        print(textwrap.dedent(msg))
        if self.attack_num == 0:
            msg = """\
            ********

            You are attacked by a wild beast! OH NO!!
            (-1 health)
            """
            print(textwrap.dedent(msg))
            self.attack_num += 1
            self.health -= 1
            if self.health < 0:
                print("Your are dead!")
                return False

        msg = """\
        Options:
        1. Go to your hometown
        2. Pick up stones
        3. Explore deeper into the forest
        9. Stats
        """
        print(textwrap.dedent(msg))
        return True

    def coast(self):
        msg = '''\
        ****************************************

        You at the coast. The wind blows your hair and the smell of salt fills your nostrils.

        Options:
        1. Go to hometown
        2. Talk to fisherman
        3. Pick up stones
        9. Stats
        '''
        print(textwrap.dedent(msg))
        return True

    def find_stones(self):
        if self.ranged == 0:
            msg = """\
            ********

            You found 5 stones you can throw at an enemy.
            (+1 ranged) (+5 ammo)
            """
            print(textwrap.dedent(msg))
            self.ranged = 1
            self.ammo = 5
            return True

        elif self.ranged >= 1 and self.ammo < 5:
            msg = """\
            ********

            You stuff a few stones in your bag for later.
            """
            print(textwrap.dedent(msg))
            self.ammo = 5
            return True

        elif self.ranged >= 1 and self.ammo > 4:
            msg = """\
            ********

            You don't find any suitable stones.
            """
            print(textwrap.dedent(msg))
            return True

    def battle(self):
        msg = """\
        ****************

        You have been attacked by a wild pig chicken. What do you do?

        1. Run
        2. Fight
        3. Make friends with the pig chicken
        """
        print(textwrap.dedent(msg))
        return True

    def battle_raped(self):
        msg = """\
        ****************

        You have just been brutally raped by a horny pig chicken!! You suck!!

        GAME OVER!!!!!
        """
        print(textwrap.dedent(msg))
        return False

    def fishconvo(self):
        msg = """\
        ********

        Fisherman: "The fish aren't biting today. Want me to teach you a few boxing moves?

        1. Yes
        2. No
        """
        print(textwrap.dedent(msg))
        return True

    def fishconvo_yes(self):
        if self.melee == 1:
            msg = """\
            ********

            Fisherman: "Now don't go beating up that nerdy crippled kid."
            (+1 melee)
            """
            self.melee += 1
        else:
            msg = """\
            ********

            Fisherman "Looks like I have nothing left to teach you."
            """
        print(textwrap.dedent(msg))
        return True

    def fishconvo_no(self):
        msg = """\
        ********

        Fisherman: "Well, don't come crying to me if you get brutally raped by a horny pig chicken."
        """
        print(textwrap.dedent(msg))
        return True

The state machine is implemented as follow:

  • The setup() method is used to initialize the transitions. You can imagine new scenarios more easily…
  • The trigger() method is used to trigger a transition given a user command. This function also trigger transitions without user input. The triggering calls the action of each matching transition; if the result of a command is False, the state machine is stopped (game over).
  • The run() method start the state machine and prompt the user to enter a command.

The remaining classes are actions: they print a user message and change the game parameters when necessary.

The definition of the transitions is done in the main() function:

def main():
    adventure = Adventure()
    adventure.setup([
        Transition("__init__", "start", None, adventure.start),

        Transition("start", "cottage", None, adventure.cottage),

        Transition("cottage", "hometown", "1", adventure.hometown),
        Transition("cottage", "cottage", "2", adventure.back_to_full_health),
        Transition("cottage", "cottage", "9", adventure.check_stats),

        Transition("hometown", "cottage", "1", adventure.cottage),
        Transition("hometown", "forest", "2", adventure.forest),
        Transition("hometown", "coast", "3", adventure.coast),
        Transition("hometown", "hometown", "9", adventure.check_stats),

        Transition("forest", "hometown", "1", adventure.hometown),
        Transition("forest", "forest_stones", "2", adventure.find_stones),
        Transition("forest", "battle", "3", adventure.battle),
        Transition("forest", "forest", "9", adventure.check_stats),
        Transition("forest_stones", "forest", None, adventure.forest),

        Transition("battle", "forest", "1", adventure.forest),
        Transition("battle", "game_over", "2", adventure.battle_raped),
        Transition("battle", "game_over", "3", adventure.battle_raped),

        Transition("coast", "hometown", "1", adventure.hometown),
        Transition("coast", "fishconvo", "2", adventure.fishconvo),
        Transition("coast", "coast_stones", "3", adventure.find_stones),
        Transition("coast", "coast", "9", adventure.check_stats),
        Transition("coast_stones", "coast", None, adventure.coast),

        Transition("fishconvo", "fishconvo_yes", "1", adventure.fishconvo_yes),
        Transition("fishconvo", "fishconvo_no", "2", adventure.fishconvo_no),
        Transition("fishconvo_yes", "coast", None, adventure.coast),
        Transition("fishconvo_no", "coast", None, adventure.coast),

    ])
    adventure.run()


if __name__ == '__main__':
    main()

Notice:

I haven't deep-tested the scenario, so I may left some transition. In you original post, I remarked that the boatconvo() function was never called.

The prompt() function can be implemented for Python2 / Python3, like this:

import sys

PY2 = sys.version_info[0] == 2

if PY2:
    def prompt(msg):
        return raw_input(msg)
else:
    def prompt(msg):
        return input(msg)

Tell us about your adventure!

\$\endgroup\$
  • 1
    \$\begingroup\$ Wow, thanks man! I can't believe you were willing to go through the effort of actually go through my whole program in so much detail. I am gonna take your advice, and spend some time studying your suggestions and implementing these changes. Thanks for the help! \$\endgroup\$ – Aaron Cox Feb 19 '17 at 23:21

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.