9
\$\begingroup\$

I need help simplifying this Python code. I'm new, this adventure game is one of my first projects. I have tried simplifying my code already and this was the best result I could get.

import time
import random

r = random.randrange(10)
#r is the weapon being used for gun

#pauses the story
def print_pause(lines):
    for line, pause in lines:
        print(line)
        time.sleep(pause)

def print_sep():
    print("You chose " + answer + ".")
    print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")

def game_over():
    print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
    print(""" 
  ___                                  
 / __|__ _ _ __  ___   _____ _____ _ _ 
| (_ / _` | '  \/ -_) / _ \ V / -_) '_|
 \___\__,_|_|_|_\___| \___/\_/\___|_|  
            """)
def win():
    print("""
 _     _                      _       
| |   | |                    (_)      
| |___| | ___  _   _    _ _ _ _ ____  
|_____  |/ _ \| | | |  | | | | |  _ \ 
 _____| | |_| | |_| |  | | | | | | | |
(_______|\___/|____/    \___/|_|_| |_|
""")

print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
print("Soviet Union, 1988")
print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
time.sleep(1)
name = input("Comrade, what is your name?")
print_pause([
    ("-- KGB Offices, Moscow --", 1),
    ("Glory to the party and the glorious leader, " + name + ".", 2),
    ("You were arrested after participating in a democratic protest in\nKazan yesterday. My name is Vladimir; tell me what happened.", 3),
    ("Do you: \n A: Tell the KGB officer everything \n B: Say nothing", 0) #how many seconds to rest
    ])
answer = input("A or B?")
print_sep()
if answer == "A":
    print_pause([
        ("You tell Vladimir everything; and he approaches you with a\nlucrative offer.", 3),
        ("You have a one time opportunity to join the KGB, otherwise you face prison time.", 3),
        ("Do you: \n A: Accept the offer \n B: Decline the offer", 3)
        ])  
    answer = input("A or B?")
    print_sep()
    if answer == "A":
        print_pause([
            ("Agent " + name + ", welcome to the KGB.", 1),
            ("Here is your badge and gun; your first task; help us arrest known\ndissident guards at the Inner German border.", 3),
            ("You are sent to the Inner German border; and soon you are feet away from West Germany. Do you escape?", 3),
            ("Do you: \n A: Escape \n B: Continue on your mission", 3)
            ])
        answer = input("A or B?")
        print_sep()
        if answer == "A":
            if r > 5:
                print_pause([
                    ("Success, you escaped from the Eastern Bloc.", 2),
                    ("Wait another 3 years, and all of communism collapses.", 2)
                    ])
                win()
            else:
                print_pause([
                    ("As you try to climb across the border, you step on an infamous\nSM-70 mine.", 3),
                    ("80 steel cubes rip into your body.", 2)
                    ]) 
                game_over()
        elif answer == "B":
            print_pause([
                ("You find the guard dissident, and you shout 'HALT!'", 2),
                ("He whips around, but before he can shoot you, you tackle him to the ground", 3),
                ("For the rest of your life, you continue to work for the KGB, and retire comfortably after the collapse of the USSR", 3)
                ])
            win()
    elif answer == "B":
        print_pause([
            ("Prison, like Vladimir said, is your new home.", 2),
            ("But the USSR collapses in 1991; so you are free to go after 3 years!", 3),
            ("Unfortunately the KGB wants you to keep quiet about what you went\nthrough so a splinter faction kills you to make sure you don't leak\nany info.", 3)
            ])
        game_over()
elif answer == "B":
    print_pause([
        ("You are tortured for days on by Vladimir.", 2),
        ("Just when you think you lost all hope, you find an opportunity: his pistol left on the table.", 2),
        ("Do you:\n A: Grab the pistol \n B: Leave it on the table", 2.5)
        ])
    answer = input("A or B?")
    print_sep()
    if answer == "A":
        if r > 5:
            print_pause([
                ("You pick up the pistol. It's a Makarov; standard issue for KGB. You fire!\nThe bullet whizzes through the air... and hits it's mark!", 3),
                ("Vladimir lies in a pool of blood as you run." , 2),        
                ("Do you: \n A: Run away \n B: Surrender", 2)
            ])
            answer = input("A or B?")
            print_sep()
            if answer == "A":
                print_pause([
                    ("You escape... barely.", 2),
                    ("You spend the rest of your years in hiding until your charges are dropped after the dissolution of the USSR.", 3)
                ])
                win()        
            elif answer == "B":
                print_pause([
                    ("Bad choice... the USSR carries the death penalty for murder cases.", 2)
                    ("I'll leave the rest to your imagination.", 2)
                ])
                game_over()                      
        else:
            print_pause([
                ("You shoot, and the bullet whizzes past Vladimir, hitting the wall.", 3),
                ("He easily whips around and chokes you to death with the ferocity of a bear." , 2)        
                ])
            game_over()
    elif answer == "B":
        print_pause([
            ("You tried your best, but eventually you gave up.", 2),
            ("You told Vladimir everything, and a show trial exiles you to a gulag.", 2),
            ("The rest of your days you spend working in the Siberian cold.", 2)
        ])
        game_over()
\$\endgroup\$
  • 2
    \$\begingroup\$ Welcome to Code Review! I hope you get some great answers. \$\endgroup\$ – Phrancis Apr 10 at 1:55
  • \$\begingroup\$ I rolled back your last edit. After getting an answer you are not allowed to change your code anymore (including removing the code...). This is to ensure that answers do not get invalidated and have to hit a moving target. If you have changed your code you can either post it as an answer (if it would constitute a code review) or ask a new question with your changed code (linking back to this one as reference). Refer to this post for more information \$\endgroup\$ – Sᴀᴍ Onᴇᴌᴀ Apr 12 at 16:49
  • \$\begingroup\$ Thanks that was an accident of mine. \$\endgroup\$ – Mattthecommie Apr 12 at 22:17
6
\$\begingroup\$

Decouple the game engine from the data

An adventure game is a good example of a state machine: at any given point in the game, a small number of parameters determine the user's position, and a small number of actions are available that cause transitions to possible new states.

For this game, we can give a number to each state:

  1. KGB Offices, Moscow
  2. Vladimir makes an offer
  3. Welcome to the KGB
  4. Escape
  5. Land-mine
  6. Kill the guard
  7. Prison
  8. Torture
  9. ...

We can represent each state as an object. For example, state 1 might look something roughly like:

id = 1
description = "You were arrested after participating in " + more_description
actions = [ ("Tell the KGB officer everything", 2), ("Say nothing", 8) ]

Now, we're able to turn the game into a loop that just prints the description from the current state and presents the available actions. When the user inputs a valid action, then we set the current state from the action's next state.

With the data separated like this, it's easier to re-use the game engine for an entirely different game, just by changing the data.

If the game data are kept in a separate file to the code, the story could be written by a professional author and the text could be translated into many languages by professional translators even if none of them are programmers - this is common in the software industry, and allows each person to contribute what they're best at without needing additional skills.


A small problem that needs fixing is that we always assume the user will enter a valid action. If the user enters something other than the options presented, we should re-ask for valid input. With the game engine transformed into a simple loop, there's only one input() that we need to change - another benefit of separating the code from the data.

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

Standard docstrings

Rather than

#pauses the story
def print_pause(lines):

the standard is to do:

def print_pause(lines):
    """
    pauses the story
    """

It'd be a good idea to add similar documentation to your other functions.

f-strings

This:

print("You chose " + answer + ".")

can be

print(f'You chose {answer}.')

similar instances elsewhere.

Hard wraps?

This:

"Unfortunately the KGB wants you to keep quiet about what you went\nthrough so a splinter faction kills you to make sure you don't leak\nany info."

shouldn't really include newlines. A proper terminal will auto-wrap on character based on the width of the terminal. If you want to be more careful and wrap on the word, do a little reading about the textwrap library:

https://docs.python.org/3.7/library/textwrap.html

General

Your game logic is very simple. As such, consider re-representing your game as a very small game engine, and a set of data - perhaps represented in a .json file - that represents outputs and choices.

\$\endgroup\$
3
\$\begingroup\$

Your story is basically a directed graph. From each question you can take multiple routes. So I would represent each question as such, with different types of nodes for when the user chooses, the choice is random or the game is over (either winning or loosing).

import random

class Node:
    def __init__(self, message, choices):
        self.message = message
        self.choices = choices

    def __str__(self):
        return self.message

    def choose(self):
        pass


class UserChoice(Node):
    def choose(self):
        while True:
            answer = input("A or B?" )
            if answer in self.choices:
                return self.choices[answer]


class RandomChoice(Node):
    def choose(self):
        return random.choice(list(self.choices.values()))


class Win(Node):
    def __str__(self):
        return super().__str__() + r"""
 _     _                      _
| |   | |                    (_)
| |___| | ___  _   _    _ _ _ _ ____
|_____  |/ _ \| | | |  | | | | |  _ \
 _____| | |_| | |_| |  | | | | | | | |
(_______|\___/|____/    \___/|_|_| |_|
"""


class GameOver(Node):
    def __str__(self):
        return super().__str__() + r"""
  ___
 / __|__ _ _ __  ___   _____ _____ _ _
| (_ / _` | '  \/ -_) / _ \ V / -_) '_|
 \___\__,_|_|_|_\___| \___/\_/\___|_|
"""


nodes = {
    "start": UserChoice("""-- KGB Offices, Moscow --
Glory to the party and the glorious leader,
You were arrested after participating in a democratic protest in
Kazan yesterday. My name is Vladimir; tell me what happened.
Do you:
 A: Tell the KGB officer everything
 B: Say nothing""", {"A": "offer", "B": "torture"}),
    "offer": UserChoice("""You tell Vladimir everything; and he approaches you with a
lucrative offer.
You have a one time opportunity to join the KGB, otherwise you face prison time.
Do you:
 A: Accept the offer
 B: Decline the offer""", {"A": "kgb", "B": "game_over_kgb"}),
    "kgb": UserChoice("""Welcome to the KGB.
Here is your badge and gun; your first task; help us arrest known
dissident guards at the Inner German border.
You are sent to the Inner German border; and soon you are feet away from West Germany. Do you escape?
Do you:
 A: Escape
 B: Continue on your mission""", {"A": "escape", "B": "win_kgb"}),
    "escape": RandomChoice(None, {"A": "win_escape1", "B": "game_over_mine"}),
    "torture": UserChoice("""You are tortured for days on by Vladimir.
Just when you think you lost all hope, you find an opportunity: his pistol left on the table.
Do you:
 A: Grab the pistol
 B: Leave it on the table""", {"A": "pistol_random", "B": "game_over_gulag"}),
    "pistol_random": RandomChoice(None, {"A": "pistol_lucky", "B": "game_over_choke"}),
    "pistol_lucky": UserChoice("""You pick up the pistol. It's a Makarov;
standard issue for KGB. You fire!
The bullet whizzes through the air... and hits it's mark!
Vladimir lies in a pool of blood as you run.
Do you:
 A: Run away
 B: Surrender""", {"A": "win_escape2", "B": "game_over_surrender"}),
    "win_kgb": Win("""You find the guard dissident, and you shout 'HALT!'
He whips around, but before he can shoot you, you tackle him to the ground
For the rest of your life, you continue to work for the KGB, and retire comfortably after the collapse of the USSR""", None),
    "win_escape1": Win("""Success, you escaped from the Eastern Bloc.
Wait another 3 years, and all of communism collapses.""", None),
    "win_escape2": Win("""You escape... barely.
You spend the rest of your years in hiding until your charges are dropped after the dissolution of the USSR.""", None),
    "game_over_mine": GameOver("""As you try to climb across the border, you step on an infamous
SM-70 mine.
80 steel cubes rip into your body.""", None),
    "game_over_kgb": GameOver("""Prison, like Vladimir said, is your new home.
But the USSR collapses in 1991; so you are free to go after 3 years!
Unfortunately the KGB wants you to keep quiet about what you went
through so a splinter faction kills you to make sure you don't leak
any info.""", None),
    "game_over_surrender": GameOver("""Bad choice... the USSR carries the death penalty for murder cases.
I'll leave the rest to your imagination.""", None),
    "game_over_choke": GameOver("""You shoot, and the bullet whizzes past Vladimir, hitting the wall.
He easily whips around and chokes you to death with the ferocity of a bear.""", None),
    "game_over_gulag": GameOver("""You tried your best, but eventually you gave up.
You told Vladimir everything, and a show trial exiles you to a gulag.
The rest of your days you spend working in the Siberian cold.""", None),
}

With this the actual calling code becomes very easy:

if __name__ == "__main__":
    print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
    print("Soviet Union, 1988")
    current = nodes["start"]
    while current.choices is not None:
        if current.message is not None:
            print("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~")
            print(current)
        current = nodes[current.choose()]
    print(current)

Note that I used a if __name__ == "__main__": guard to allow importing from this script from another script and made your "You win" and "Game over" strings raw strings (by prepending an r) so that the backslashes don't potentially escape the character after them.

This structure has the advantage that it is very easy to visualize the story as well. Add the following code:

import sys

...

if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "--graph":
        print("digraph G {")
        for name, node in nodes.items():
            if node.choices is not None:
                for label, choice in node.choices.items():
                    print(f"{name} -> {choice} [ label={label} ];")
        print("}")
    else:
        ...

And just run this with python3 story_graph.py --graph | dot | display to get this output:

enter image description here

\$\endgroup\$

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.