13
\$\begingroup\$

I made a small text adventure. I'm not trying to make it appealing to anyone, but rather just to practice my Python skills. I am fairly new to Python and this was my next step after making a console calculator and BMI calculator. I would like someone to tell me how this code could be better optimised, less work, etc.

import time
import random

# game function
def game():

print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print ("Welcome to the cavern of secrets!")
print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")

time.sleep(3)

print ("You enter a dark cavern out of curiosity. It is dark and you can only make out a small stick on the floor.")
ch1 = str(input("Do you take it? [y/n]: "))

# STICK TAKEN
if ch1 in ['y', 'Y', 'Yes', 'YES', 'yes']:
    print("You have taken the stick!")
    time.sleep(2)
    stick = 1

# STICK NOT TAKEN
else:
    print("You did not take the stick")
    stick = 0

print ("As you proceed further into the cave, you see a small glowing object")
ch2 = str(input("Do you approach the object? [y/n]"))

# APPROACH SPIDER
if ch2 in ['y', 'Y', 'Yes', 'YES', 'yes']:
    print ("You approach the object...")
    time.sleep(2)
    print ("As you draw closer, you begin to make out the object as an eye!")
    time.sleep(1)
    print ("The eye belongs to a giant spider!")
    ch3 = str(input("Do you try to fight it? [Y/N]"))

    # FIGHT SPIDER
    if ch3 in ['y', 'Y', 'Yes', 'YES', 'yes']:

        # WITH STICK
        if stick == 1:
            print ("You only have a stick to fight with!")
            print ("You quickly jab the spider in it's eye and gain an advantage")
            time.sleep(2)
            print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
            print ("                  Fighting...                   ")
            print ("   YOU MUST HIT ABOVE A 5 TO KILL THE SPIDER    ")
            print ("IF THE SPIDER HITS HIGHER THAN YOU, YOU WILL DIE")
            print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
            time.sleep(2)
            fdmg1 = int(random.randint(3, 10))
            edmg1 = int(random.randint(1, 5))
            print ("you hit a", fdmg1)
            print ("the spider hits a", edmg1)
            time.sleep(2)

            if edmg1 > fdmg1:
                print ("The spider has dealt more damage than you!")
                complete = 0
                return complete

            elif fdmg1 < 5:
                print ("You didn't do enough damage to kill the spider, but you manage to escape")
                complete = 1
                return complete

            else:
                print ("You killed the spider!")
                complete = 1
                return complete

        # WITHOUT STICK
        else:
            print ("You don't have anything to fight with!")
            time.sleep(2)
            print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
            print ("                  Fighting...                   ")
            print ("   YOU MUST HIT ABOVE A 5 TO KILL THE SPIDER    ")
            print ("IF THE SPIDER HITS HIGHER THAN YOU, YOU WILL DIE")
            print ("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
            time.sleep(2)
            fdmg1 = int(random.randint(1, 8))
            edmg1 = int(random.randint(1, 5))
            print ("you hit a", fdmg1)
            print ("the spider hits a", edmg1)
            time.sleep(2)

            if edmg1 > fdmg1:
                print ("The spider has dealt more damage than you!")
                complete = 0
                return complete

            elif fdmg1 < 5:
                print ("You didn't do enough damage to kill the spider, but you manage to escape")
                complete = 1
                return complete

            else:
                print ("You killed the spider!")
                complete = 1
                return complete

    #DON'T FIGHT SPIDER
    print ("You choose not to fight the spider.")
    time.sleep(1)
    print ("As you turn away, it ambushes you and impales you with it's fangs!!!")
    complete = 0
    return complete

# DON'T APPROACH SPIDER
else:
        print ("You turn away from the glowing object, and attempt to leave the cave...")
        time.sleep(1)
        print ("But something won't let you....")
        time.sleep(2)
        complete = 0
        return complete

# game loop
alive = True
while alive:

complete = game()
if complete == 1:
    alive = input('You managed to escape the cavern alive! Would you like to play again? [y/n]: ')
    if alive in ['y', 'Y', 'YES', 'yes', 'Yes',]:
        alive

    else:
        break

else:
    alive = input('You have died! Would you like to play again? [y/n]: ')
    if alive in ['y', 'Y', 'YES', 'yes', 'Yes',]:
        alive

    else:
        break
\$\endgroup\$
  • 1
    \$\begingroup\$ See this question and its answers. \$\endgroup\$ – Gareth Rees Dec 6 '13 at 12:49
  • \$\begingroup\$ The list search is quite ugly. You can make use of string functions to take all cases into account: if ch1.upper() in ['Y', 'YES'] or even if ch1[0].upper() == 'Y' \$\endgroup\$ – ejrb Dec 6 '13 at 13:44
  • \$\begingroup\$ Also, "not trying to make it appealing to anyone" is a mistake. Make the game as appealing as you possibly can, and this will motivate you to learn more. \$\endgroup\$ – Gareth Rees Dec 6 '13 at 14:05
14
\$\begingroup\$

You have a lot of repetition in your code, which is something you should focus on reducing. For example, this recurring pattern:

ch2 = str(input("Do you approach the object? [y/n]"))

# APPROACH SPIDER
if ch2 in ['y', 'Y', 'Yes', 'YES', 'yes']:

... can be encapsulated into a function and reused:

def ask(question):
    answer = input(question + " [y/n]")
    return answer in ['y', 'Y', 'Yes', 'YES', 'yes']

if ask("Do you approach the object?"):
    # ...

Another pattern is printing followed by time.sleep:

    print ("You approach the object...")
    time.sleep(2)
    print ("As you draw closer, you begin to make out the object as an eye!")
    time.sleep(1)
    print ("The eye belongs to a giant spider!")

which could be reduced to this:

def print_pause(lines):
    for line, pause in lines:
        print line
        time.sleep(pause)

print_pause([
    ("You approach the object...", 2),
    ("As you draw closer, you begin to make out the object as an eye!", 1),
    ("The eye belongs to a giant spider!", 0)
    ])

The spider fight part also contains a lot of repetition, but I will leave it as an exercise for you to turn it into a function and reuse. :)

\$\endgroup\$
12
\$\begingroup\$

This is very similar in spirit to other text adventure questions on the site, notably Better way to code this game?

The basic goal you should pursue here is to reduce the amount of completely repetitive code: Since the basic game loop is so simple, the 'code' part should be equally simple. In a game like this that's actually very easy: almost everything breaks down to presenting the user with a list of choices, getting their input, and using it to move to a new list of choices.

The 'right' way to do it, long term, is to follow @mrMishhall's advice and learn object-oriented techniques. However if you're still new to Ptthon and terms like OOP are intimidating, you can also do it using basic python data types - particularly dictionaries. That's what the example linked above does: every node in the game is a dictionary containing a description, a list of options, and a link to the nodes -- that is, other dictionaries -- you reach by folllowing a given option.

Rather than duplicate the example in the link above, i'll give you an example of how the same idea can simplify the combat portion. Here's an attempt to do the same kind of combat system with less reliance on if/then.

# instead of using an if/then tree, first we store the damage range 
# of all the weapons in the game
WEAPONS = {'stick': (3, 10)), None:(1,8), 'knife':(4,16)}

#and instead of tracking inventory with one variable, have a 'player' who
# has stuff in a dictionary:

player = {'weapon':None, 'health': None}

#to give the player stuff, you add it to his dictionary:

player['weapon'] = 'stick'

# while we're at it, we can treat the monsters the same way:

SPIDER = {name:'spider', 'health':5, 'attack':(1, 5) } 

# so for any given fight there's a player and an enemy:
# and two possible outcomes for each combatant. You'll want to report 
# the damage (which varies) and the result:

def combat (player, enemy):
    player_damage = random.range(*WEAPONS[player['weapon'])
    enemy_damage = random.range(*enemy['attack'])

    player_win = player_damage > enemy['health']
    enemy_win= enemy_damage  > player['health']

    return player_damage, player_win , enemy_damage, enemy_win

# of course, you also want flavor text. So you can set that up as another dictionary.
# you might want to make different dictionaries for different parts of the game
# to give a different flavor to the fights as you did above:

SIMPLE_FIGHT = {
player_damage: 'You hit the %s for %i damage',
enemy_damage: '%s hits you for %i damage',
player_win: 'The %s dies!',
enemy_win: 'You die!'
}

def describe_combat(player, enemy, fight_description):
   player_damage, player_win , enemy_damage, enemy_win = combat(player, enemy)

   print fight_description['player_damage'] % (enemy['name'], player_damage)
   print fight_description['enemy_damage'] % (enemy['name'], enemy_damage)

   if player_win:
      print fight_description['player_win'] % enemy['name'])
      return True

   if enemy_win:
      print fight_description['player_win'] % enemy['name'])
      return False

    return None # this means a draw :)


 # with that in place, you can play out a fight like so:


 fight_result = describe_combat(player, SPIDER, SIMPLE_FIGHT)
 if fight_result is None:
     # what do you do for draws?
 elif fight_result: 
     # you've won.
 else:
     # game over

This may seem like a lot of indirection at first, but you can create a whole different fight with just two new bits of data:

ELEPHANT = {'name':'Huge bull elephant', 'attack': (10,30)}

ELEPHANT_FIGHT = {
player_damage: 'You desperately try to stop the %s for %i damage',
enemy_damage: '%s gores you for %i damage',
player_win: 'The %s collapses with a thunderous boom',
enemy_win: 'You are squished'
}

and so on. I'm sure you can see how the same strategy will help with things like different weapons and armor, different rooms and so on. The key principle is to keep the LOGIC as simple and streamlined as possible and make the DATA do the descriptive work.

\$\endgroup\$
6
\$\begingroup\$

For inputs, instead of saying ['y','Y','Yes','YES','yes'] etc, you could just do

    if ch2.lower() in ['y','yes']:

The lower() makes it so that the string you type in is converted to all lower case. Also, this would work but would be pointless:

    if ch2.upper() in ['Y','YES']:
\$\endgroup\$
5
\$\begingroup\$

Once you have reduced the complexity by moving the repetitive code into functions and you start to grow your application it may end up being lengthy and difficult to follow.

You could consider applying an object-oriented style by introducing classes. Classes for chapters, the player, locations, items in the story. Your program will make objects with these classes. Your user will interact with these objects and these objects interact with each other.

For the current state of your game, that may be over-engineering but not if you continue it to, say a short story or a novel. Nevertheless, it seems a good opportunity to study OOP.

\$\endgroup\$

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