6
\$\begingroup\$

The program is running fine but I was just wondering whether there was anything I could do to make it more user friendly or efficient.

from random import randint
import time
word = "a"
shield = 5
#The main function
def main():
    #This will trigger the introduction
    intro()
    while True:
        shield, word, finished = TakeTurn(word,shield)
        if finished:
            break
        if shield == 1:
            word1 = "shield"
        else:
            word1 = "shields"
        print ("You have",shield,word1)
    if shield < 1:
        print ("Sorry! You ran out of shields! You lose!")
    else:
        print ("You win")

#This function is the introduction to the program
def intro():
    time = [1,2.5, 2, 1.5, 1, 2, 1]
    text = ["You are lost in a maze, it is dark and you are lost",
         "Im afraid there are monsters here...", "BOO!",
         "You have five shields to protect you...","Use them well!",
         "Okay, let's go!"]
    print_on_a_timer(time, text)

#This function is the actual 'game' and will deterine what happens to the character    
def TakeTurn(word1,shield1):
    time.sleep(1.5)
    #This means that when the user reaches 0 shields, they lose.
    if shield1 < 1:
        return True
    #Whatever the user inputs will not actually affect the outcome
    print ("You have reached", word1 ,"junction.\nDo you want to turn left (L), turn right (R) or go straight ahead(S)?")
    turning = input().lower()
    #This is a simple instruction that means that the first time you come to a junction, it will say 'a junction' but the second time it will say 'another junction'
    word1 = "another"
    #This 'if' statement means that the program will only continue if the user has inputed the letters 'L', 'R' or 'S'
    while turning not in ["l","r","s"] :
        time.sleep (0.7)
        print ("Sorry, I didn't understand that")
        turning = input().lower()
    choice = randint (1,10)
    #This is just going to display a random message which will affect the outcome
    time.sleep (1)
    if choice == 1:
        print ("You have found the exit!")
        return shield1, word1, True
    elif choice == 2:
        print ("You have found a shield!")
        time.sleep(1)
        shield1 = shield1 +1
        return shield1, word1,False
    elif choice == 3:
        print ("You have found two shields!")
        time.sleep(1)
        shield1 = shield1 +2
        return shield1, word1,False
    elif choice == 4:
        print ("You have found three shields!")
        time.sleep(1)
        shield1 = shield1 +3
        return shield1, word1,False
    elif choice == 5:
        print ("A fairy has jumped into your pants!")
        time.sleep(2)
        print ("You lose two shields")
        time.sleep(1)
        shield1 = shield1 -2
        return shield1, word1,False
    elif choice == 6:
        treasurechest(shield1)
        return shield1, word1,False
    elif choice == 7:
        print ("You have tripped over a log!")
        time.sleep(2)
        print ("You lose a shield")
        time.sleep(1)
        shield1 = shield1 -1
        return shield1, word1,False
    elif choice == 8:
        print ("An angry teenager is staring at you in the eye.")
        time.sleep(2.5)
        print ("He uses laziness...")
        time.sleep(1.5)
        print ("It's super effective!")
        time.sleep(1)
        print ("You lose three shields")
        time.sleep(1)
        shield1 = shield1 -3
        return shield1, word1,False
    elif choice == 9:
        print ("You have encountered an ogre...")
        time.sleep(1.5)
        print ("He bashes you over the head with a steel bar")
        time.sleep(2)
        print ("You lose two shields")
        time.sleep(1)
        shield1 = shield1 -2
        return shield1, word1,False
    else:
        print ("A goblin aproaches and says the following:")
        time.sleep(2)
        goblin(shield1)
        return shield1, word1,False

    def goblin(shield):
        time1 = [1,2.5,1,1,1]
        text = ["'Do you want to play my magical roulette?\n There are three different outcomes:'",
                "You lose a shield", "You gain a shield","Nothing happens"]
        print_on_a_timer(time1,text)
        goblin = 0
        while goblin == 0:
            print ("Do you want to play? Y or N?")
            choice2 = input().lower()
            time.sleep(1)
            if choice2 not in ["y","n"]:
                print ("Sorry I didnt understand that")
            elif choice2 not in ["y"]:
                print ("Okay bye")
                TakeTurn()
            else:
                print ("Let's play!")
                time.sleep(1)
                print ("Spinning...")
                time.sleep(1)
                print ("Spinning...")
                time.sleep(1)
                print ("Spinning...")
                time.sleep(1)
                roulette = randint (1,3)
                if roulette == 1:
                    print ("Nothing happens")
                    goblin = 1
                elif roulette == 2:
                    print ("Im going to have to take one of your shields")
                    shield = shield -1
                    goblin = 1
                else:
                    print ("Here! Have a shield!")
                    shield = shield +1
                    goblin = 1

def treasurechest(shield):
    treasure = 1
    while treasure == 1:
        print ("You have found a treasure chest! Do you want to open it? Y or N?")
        chest = input().lower()
        if chest not in ["y","n"]:
            print ("Sorry, I didn't understand that")
        elif chest not in ["y"]:
            print ("Okay Bye")
            treasure = 0
        else:
            time.sleep(1)
            print ("Opening...")
            time.sleep(1)
            print ("Opening...")
            time.sleep(1)
            print ("Opening...")
            time.sleep(1)
            chest = randint (1,6)
            if chest == 1:
                print ("You have found a shield!")
                shield = shield +1
                treasure = 0
            elif chest == 2:
                print ("You have found two shields!")
                shield = shield +2
                treasure = 0
            elif chest == 3:
                print ("A dwarf jumps out and steals one of your shields!")
                shield = shield -1
                treasure = 0
            elif chest == 4:
                print ("An evil fairy steals two of your shields!")
                shield = shield -2
                treasure = 0
            elif chest == 5:
                print ("Sorry, the chest is empty")
                treausre = 0
            else:
                print ("A goblin is in the chest and says the following...")
                time.sleep(2)
                goblin()

def print_on_a_timer(times, lines):
    for times, lines in zip(times, lines):
        time.sleep(times)
        print(lines)


main(shield,word)
\$\endgroup\$
3
  • 4
    \$\begingroup\$ You could make the improvement I already suggested. \$\endgroup\$
    – jonrsharpe
    Commented Jun 1, 2014 at 19:20
  • \$\begingroup\$ Note that you misspelled 'treasure' after 'Sorry, the chest is empty'. \$\endgroup\$
    – Lstor
    Commented Jun 1, 2014 at 21:45
  • \$\begingroup\$ Just a slight point that as far as I can see, please correct me if I am wrong, you only ever use sleep from the time module which would make it more efficient to do from time import sleep and then just call sleep each time \$\endgroup\$
    – 13ros27
    Commented Dec 16, 2017 at 18:39

3 Answers 3

5
\$\begingroup\$

Time to learn about objects. Below is a rewrite that should move you down the learning path. I did not change everything about the code I just shuffled the logic into events.

Note the use of game_sleep. This allows sleep to be disabled during testing. Moving all of the bits of logic and choices out of the core function enables tests to be written for each specific choice, event, etc.

I needed game_input because I am using 2.7 and so my input is not the same as the one in 3.x.

Enjoy and feel free to ask any questions.

import random
import time


def game_input():
    return raw_input()


def game_sleep(value):
    # time.sleep(value)
    pass


def pluralize(word, number):
    # you can find libraries containing a function like this
    known_words = {
        'shield': lambda x: "shield" if x == 1 else "shields",
    }

    if word in known_words:
        return known_words[word](number)
    else:
        return word


def print_on_a_timer(times, lines):
    for times, lines in zip(times, lines):
        game_sleep(times)
        print(lines)


#This function is the introduction to the program
def intro():
    time = [1,2.5, 2, 1.5, 1, 2, 1]
    text = ["You are lost in a maze, it is dark and you are lost",
            "I'm afraid there are monsters here...", "BOO!",
            "You have five shields to protect you...","Use them well!",
            "Okay, let's go!"]
    print_on_a_timer(time, text)


def get_valid_input(prompt, choices):
    choice = None

    # This means that the program will only continue if the user's choice is in valid_choices
    while True:
        print (prompt)
        choice = game_input().lower()

        if choice in choices:
            break

        print ("Sorry, I didn't understand that")
        game_sleep(0.7)

    return choice


class Event(object):
    _result = 0

    def __init__(self):
        pass

    def endGame(self):
        return False

    def interact(self):
        return None

    def message(self):
        yield self._message

    def result(self):
        return self._result


class NegativeEvent(Event):
    def __init__(self, msg, value):
        super(NegativeEvent, self).__init__()

        self._message = msg
        self._result = value


class FoundExit(Event):
    _message = "You have found the exit!"

    def endGame(self):
        return True


class FoundShield(Event):
    _message = "You have found {} {}!"

    def __init__(self, count):
        super(FoundShield, self).__init__()

        self._count = count

    def message(self):
        yield self._message.format(self._count, pluralize("shield", self._count))

    def result(self):
        return self._count


class FoundFairy(Event):
    _message = "A fairy has jumped into your pants!"
    _result = -2


class FoundTreasureChest(Event):
    _message = "You have found a treasure chest!"

    def __init__(self):
        super(FoundTreasureChest, self).__init__()

    def interact(self):
        choice = get_valid_input(prompt="Do you want to open it? Y or N?",
                                 choices=["y", "n"])
        if choice is not "y":
            print ("Okay Bye")
            return None

        print_on_a_timer([1, 1, 1], ["Opening...", "Opening...", "Opening..."])

        random_events = [(FoundShield, [1,]),
                         (FoundShield, [2, ]),
                         (NegativeEvent, ["A dwarf jumps out and steals one of your shields!", -1]),
                         (NegativeEvent, ["An evil fairy steals two of your shields!", -2]),
                         (JustMessage, ["Sorry, the chest is empty"]),
                         (Encounter, ["A goblin is in the chest.", Goblin])
                         ]
        event, options = random.choice(random_events)
        return event(*options)


class JustMessage(Event):
    def __init__(self, msg):
        super(JustMessage, self).__init__()
        self._message = msg


class TrippedOverALog(Event):
    _message = "You have tripped over a log!"
    _result = -1


class Encounter(Event):
    def __init__(self, msg, event, *options):
        self._message = msg
        self._next_event = event
        self._options = options

    def interact(self):
        return self._next_event(*self._options)


class AngryTeenager(Event):
    _result = -3

    def message(self):
        game_sleep(2.5)
        yield "He uses laziness..."
        game_sleep(1.5)
        yield "It's super effective!"
        game_sleep(1)
        return

class Ogre(Event):
    _result = -2

    def message(self):
        game_sleep(1.5)
        yield ("He bashes you over the head with a steel bar")
        game_sleep(2)
        return


class Goblin(Event):
    _message = "He says the following:"

    def interact(self):
        timings = [1, 2.5, 1, 1, 1]
        text = ["""'Do you want to play my magical roulette?
There are three different outcomes:'""",
                "You lose a shield",
                "You gain a shield",
                "Nothing happens"]
        print_on_a_timer(timings, text)

        choice = get_valid_input(prompt="Do you want to play? Y or N?", choices=["y", "n"])
        if choice is not "y":
            print ("Okay bye")
            return

        print ("Let's play!")
        print_on_a_timer([1, 1, 1], ["Spinning...", "Spinning...", "Spinning..."])
        roulette = random.randint (1,3)
        if roulette == 1:
            print ("Nothing happens")
        elif roulette == 2:
            self._result = -1
            print ("Black!! I'm going to have to take one of your shields")
        else:
            self._result = 1
            print ("Grr, Red! Here! Have a shield!")

    def updateMessage(self, msg):
        self._message = msg


def get_random_event():
    game_sleep (1)

    random_encounters = [("An angry teenager is staring you in the eye.", AngryTeenager),
                         ("A goblin approaches.", Goblin),
                         ("You have encountered an ogre.", Ogre),
                         ]

    random_events = [(FoundExit, None),
                     (FoundShield, lambda: (random.randint(1,3), )),  # could be weighted to prefer one
                     (FoundFairy, None),
                     (FoundTreasureChest, None),
                     (TrippedOverALog, None),
                     (Encounter, lambda: random.choice(random_encounters))
                     ]

    event, mod = random.choice(random_events)
    if mod is not None:
        return event(*mod())
    else:
        return event()


#This function is the actual 'game' and will deterine what happens to the character
def takeTurn(word, shields):
    # This means that when the user reaches 0 shields, they lose.
    if shields < 1:
        return (word, shields, True)

    # Whatever the user inputs will not actually affect the outcome
    print ("You have reached {} junction.".format(word))

    # This is a simple instruction that means that the first time you
    # come to a junction, it will say 'a junction' but the second time
    # it will say 'another junction'
    word = "another"

    turning = get_valid_input(prompt="Do you want to turn left (L), turn right (R) or go straight ahead(S)?",
                              choices=["l", "r", "s"])

    event = get_random_event()

    while event:
        for text in event.message():
            print (text)

        next_event = event.interact()
        mod = event.result()
        if mod > 0:
            print ("You gain {} {}".format(mod, pluralize("shield", mod)))
        elif mod < 0:
            print ("You lose {} {}".format(mod, pluralize("shield", mod)))

        shields += mod

        if shields < 1:
            break
        elif event.endGame():
            return (word, shields, True)

        event = next_event

    return (word, shields, False)


#The main function
def runGame(shields):
    #This will trigger the introduction
    intro()

    word = "a"

    while True:
        word, shields, finished = takeTurn(word, shields)
        if finished:
            break

        if shields > 0:
            print ("You have {} {}".format(shields, pluralize("shield", shields)))
            game_sleep(1.5)  # delay before we start the next turn

    if shields < 1:
        print ("Sorry! You ran out of shields! You lose!")
    else:
        print ("You win")


if __name__ == "__main__":
    shields = 5

    runGame(shields)
\$\endgroup\$
2
  • \$\begingroup\$ This is overengineered. \$\endgroup\$
    – Caridorc
    Commented Feb 19, 2016 at 14:11
  • \$\begingroup\$ Of course it is. I did not want to rewrite and design their code. The point is to guide. The OP was clearly getting started. I could have shown every possible flaw. But that would not have helped. They will keep trying and learning. In time they will look back and laugh. I know I did at my early code. \$\endgroup\$
    – Sean Perry
    Commented Feb 19, 2016 at 16:31
4
\$\begingroup\$

You have a huge number of if-else constructions. This is fine, but in many cases can be clarified using a dictionary or other mapping from the conditions to the things you do in the branch.

For example:

        if chest == 1:
            print ("You have found a shield!")
            shield = shield +1
            treasure = 0
        elif chest == 2:
            print ("You have found two shields!")
            shield = shield +2
            treasure = 0
        elif chest == 3:
            print ("A dwarf jumps out and steals one of your shields!")
            shield = shield -1
            treasure = 0
        elif chest == 4:
            print ("An evil fairy steals two of your shields!")
            shield = shield -2
            treasure = 0
        elif chest == 5:
            print ("Sorry, the chest is empty")
            treausre = 0

All those check for a value of the chest variable, then print a string, modifying two other varibles. They can be combined to a list or dict:

treasures = {
    1:('You have found a shield!', +1)
    2:('You have found two shields!', +2)
    3:#...
}
if chest in treasures:
    msg, shield_change = treasures[chest]
    print(msg)
    shield += shield_change
    treasure = 0

Similarly, all your repeated lines like:

            print ("Spinning...")
            time.sleep(1)
            print ("Spinning...")
            time.sleep(1)
            print ("Spinning...")
            time.sleep(1)

Can be combined to

            print_delay("Spinning...", repeat=3)

with print_delay defined as:

def print_delay(message, delay=1, repeat=1):
    for i in range(repeat):
        print(message)
        time.sleep(delay)
\$\endgroup\$
1
  • \$\begingroup\$ I think I disagree with "this is fine"; an if-else ladder rarely if ever has a place in well-designed code imo. \$\endgroup\$
    – RJFalconer
    Commented May 22, 2015 at 12:12
2
\$\begingroup\$

It's important to follow general code formatting conventions (see PEP 8). This makes code easier to share and mix with other people.

In particular, do not put a space between a function name and the opening bracket, but use spaces between arguments. You can also use string formatting (Python 2,7) and if you add to that a ternary operator for the plural you get this:

print ("You have {} shield{}".format(shield, 's' if shield > 1 else ''))

You can also use a boolean instead of 1 and 0, although I think you already know this but it looked a bit confusing in your code:

treasure = True
while treasure:
    # do stuff...
    if something:
        treasure = False

If you really wanted to have a number of treasures, then you could decrement it in the list...

\$\endgroup\$

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