4
\$\begingroup\$

I'm making a platformer game called Uni where this small character has to go as high as possible, in which the player constantly has to choose between jumping (W) going left or right (A-D) climbing down (S) or waiting (Just enter). A friend wanted a very difficult game that requires not timing but thinking to progress. Hence there are many rules that players learn throughout the game:

  1. You may jump two characters over where you were initially at.

  2. To go over a block, you must jump higher than it and from below.

  3. To go over a ladder, you must jump at least as high as where it is from below.

  4. When in the air, you can change direction (A-D) or you can stay where you are at.

  5. When falling, you catch up speed.

  6. Because it is a game based on Frames per Input, the number of "you"s you see represents the speed at which you just moved to reach your current position.

  7. If you jump over a ladder exactly 3 characters above you, its as if you jumped over a block 2 characters above you.

  8. If you jump over a ladder less than 3 characters above you, the next time you press enter you will "bounce" one character higher, because of the exess of velocity.

  9. When you enter contact with a checkpoint, it activates it. When you die, you get teleported to the last checkpoint you activated (if any) and that same one dissappears. If there are no more activated checkpoints left, you simply die.

So the game works, and is very difficult but is possible. When I made it though, I wished for the layout to be randomly generated each time the script is runned. Now I realise that with so many rules, I have no idea how to get the layout to not just be randomly generated but also possible. The current game has a static layout.

Here is the script (although kinda sloppy):

#Mechanics:
#Make a list for each printed line. When the player goes higher, shift the list down 1 and add a new layer
import os
import time
debug = False
def get_key():
    try:
        import sys, tty, termios
        fd = sys.stdin.fileno()
        old = termios.tcgetattr(fd)
        try:
            tty.setraw(fd)
            return sys.stdin.read(1)
        finally:
            termios.tcsetattr(fd, termios.TCSADRAIN, old)
    except:
        return input("Press a key and hit Enter: ")[0]

checkpoint1 = "\u235A"
my_list = []
# 18 spaces between sides
# U+2223, U+2336, U+2180, U+2182, U+2188
portal = " "
def updater():
    global my_list
    global portal
    my_list = [
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        f"     \u2359    \u2359      {portal}  ",
        #"   \u2359     \u210D \u210D\u20AA       ",
        "   \u2359     \u210D \u210D\u20AA\u20AA\u20AA\u20AA\u20AA\u20AA\u20AA\u20AA\u20AA",
        "    \u210D\u20AA \u2359 \u210D\u20AA         ",
        f"   \u210D                ",
        "       \u210D\u20AA\u20AA\u2359\u2359\u210D       ",
        "     \u210D              ",
        "   \u20AA      \u20AA\u210D\u20AA       ",
        "    \u2359\u210D    \u2359         ",
        "   \u20AA    \u2359 \u20AA\u20AA\u2359       ",
        f"     \u210D {checkpoint1}  \u2359         ",
        "      \u20AA\u20AA\u20AA\u20AA\u20AA\u20AA\u20AA\u20AA       ",
        "_\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359\u2359_",
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        "                    "
    ]
updater()

# Number of visible lines
window_height = 9
scrollingY = 0
playerX = 1
player = '\u237E'
fellat = 0
dead = False

while scrollingY + window_height < len(my_list):
    scrollingY += 1
scrollingY -= 6
def clear():
    os.system('cls' if os.name == 'nt' else 'clear')

s = []
middle = 0
playerline = 0
target = "a"
undertarget = "b"
underundertarget = "c"
fellat = 0
key = "uhh"
def display_list():
    global target
    global undertarget
    global underundertarget
    global s
    global playerX
    global middle
    global fellat
    clear()
    global scrollingY
    global player
    global dead
    global playerline
    global checkpoint1
    global key
    global portal
    setrightundertarget = False
    for i in range(scrollingY, min(scrollingY + window_height, len(my_list))):
        if i == min(scrollingY+window_height,len(my_list))-3:
            #print the list with the player at the center
            playerline = i
            s = my_list[i]
            middle = len(s) // 2
             
            # Split around the middle
            before = s[:middle+playerX]
            target = s[middle+playerX]
            after = s[(middle+playerX+1):]
            
            undertarget = my_list[i+1][((len(my_list[i+1])//2)+playerX)]
            if setrightundertarget == False:
                rightundertarget = undertarget
                setrightundertarget = True


            #Check if player reached checkpoint1 and checkpoint1 is deactivated
            if target == "\u235A" and scrollingY == 7 and playerX == -3:
                checkpoint1 = "\u06E9"
                updater()

            #Check if player is inside ladder but not going down(bring back on top),
            #else if player is trying to go down a floor(bring back on top)
            #else if player is over or in a spike (death),
            #else if player is over nothing or a checkpoint (fall),
            #else since player is not falling if player is about to bounce (prevent)

#Bouncing occurs after going over a regular block without having fellat at 0
            if (target == "\u210D" and key != "s") or (target == "\u20AA" and key == "s"):
                scrollingY -= 1
                fellat = 0
                clear()
                display_list()
                break
            elif undertarget == "\u2359" or target == "\u2359":
                player = "\u237D"
                dead = True
                #Player is dead from spike
            elif undertarget == " " or undertarget == "\u235A" or undertarget == "\u06E9":
                scrollingY += 1
            elif rightundertarget == "\u20AA" and fellat == 2:
                #Bouncing occurs after going over a regular block without having fellat at 0
                fellat = 0


            # Print the original middle character, backspace, then overwrite it with the player.
            print(before + target + '\b' + player + after)
        else:
            print(my_list[i])
    if dead == True:
        print("\n     GAME  OVER     ")
while dead == False or checkpoint1 == "\u06E9":
    clear()
    scrollingY -= fellat
    display_list()
    if debug:
        print(f"PlayerX:{playerX} and ScrollingY:{scrollingY}\ntarget:{target}\nundertarget:{undertarget}\nfellat:{fellat}")
    key = input(">> ").lower()
    if checkpoint1 == "\u06E9" and dead == True:
        checkpoint1 = " "
        updater()
        dead = False
        fellat = 0
        playerX = -3
        scrollingY = 7
        clear()
        display_list()
        player = "\u237E"
    if key == 'q':
        break
    elif key == 'w': # and scrollingY > 0:
        fellat = 3
    elif key == 's' and scrollingY + window_height < len(my_list):
        scrollingY += 1
        fellat = 0
    elif key == 'd' and playerX < 7 and my_list[playerline-(1 if fellat != 0 else 0)][(middle+playerX)+1] != "\u20AA":
        playerX += 1
    elif key == 'a' and playerX > (0-10) and my_list[playerline-(1 if fellat != 0 else 0)][(middle+playerX)-1] != "\u20AA":
        playerX -= 1
    if fellat > 0:
        fellat -= 1
    if scrollingY == -2 and portal == " ":
        # U+2223, U+2336, U+2180, U+2182, U+2188
        for i in ["\u2223","\u2336","\u2180","\U00002182"]:
            portal = i
            updater()
            clear()
            display_list()
            time.sleep(0.15)
    if playerX == 6 and scrollingY == -2:
        playerlooksbefore = player
        player = " "
        updater()
        clear()
        display_list()
        time.sleep(0.2)
        for i in ["\u2180", "\u2336", "\u2223", " "]:
            portal = i
            updater()
            clear()
            display_list()
            time.sleep(0.2)
        time.sleep(1.8)
        clear()
        time.sleep(6)
        for i in [" ", "\u2223","\u2336","\u2180", "\U00002182", playerlooksbefore]:
            clear()
            player = i
            r,c=os.get_terminal_size()
            print("\n"*r + " "*(c//2) + f"{player}")
            time.sleep(0.2)
        time.sleep(2.8)
        clear()
        print("[The Creator] - Congradulations.")
        time.sleep(2)
        blah = input("<<Continue>>")
        clear()
        print("[The Creator] - You have proven yourself worthy of becoming citizen of unicode.")
        time.sleep(2)
        blah = input("<<Continue>>")
        for i in [player, "\U00002182", "\u2180", "\u2336","\u2223", " "]:
            clear()
            player = i
            r, c = os.get_terminal_size()
            print("\n"*r + " "*(c//2) + f"{player}")
            time.sleep(0.9)
        time.sleep(3)
        clear()
        time.sleep(4)
        print("YOU WON")
        time.sleep(0.5)
        for i in ["\n","Coded by:","Emmanuel Ghattas","\n",f"Thanks for pl{playerlooksbefore}ying!"]:
            print(i)
            time.sleep(0.2)
        time.sleep(7)
        break


If anyone knows how to get a layout that changes each time the player runs the script while keeping it playable, or simply has ideas for the game, please let me know.

New contributor
Chip01 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$
1

3 Answers 3

4
\$\begingroup\$

Notes

  • Avoid global variables. You use them extensively, but it makes your logic much harder to follow. Information should be passed via function arguments, back out via return values, or can be encapsulated in objects which are handled this way.
  • Aim for small functions that do specific things, do them well, and can be composed together to accomplish larger goals. If you can't view the entire function on a single page, it's too long. If you can't sum up what the function does in a concise docstring1, it's doing too much.
  • Many of your lines are much too long. Aim for an 80 character width limit.
  • Comparisons to True or False are unnecessary. dead == False is the same as not dead.
  • Avoid repeating yourself. Near the beginning of display_list you calculate min(scrollingY + window_height, len(my_list)) twice when this could have been calculated once and stored in a variable.
  • Avoid "magic numbers" in your code, favoring named constants instead.
  • Do not write a catch-all except: as you have. Instead aim to catch specific exception types.

I strongly suggest you read PEP 8. This is the style guide for Python code.

I'm not going to comment on the functionality of your code because the style issues need to be addressed to make it understandable.


1 Read PEP 257 on docstrings.

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

Initial Impression

  1. The first thing I did was to try running your program to get a feel for what it is doing. But I was frustrated because, although you posted instructions of a sort in your post, I was given no prompts in your actual code as to what I can enter. Moreover, although you say in your instructions, "You may jump two characters over where you were initially at", I have no idea who "I" am or where I am initially or what the characters on the screen I am looking at (H's and ?'s) even mean.
  2. If I enter a command that is not valid, I would like to see some sort of error message.
  3. Looking at the code offers no insight as to what I as a player should be doing for lack of comments.

So Many Globals!

Python supports object-oriented programming including encapsulation. I think readability would be improved if you had a class named Uni that had an __init__ method that initialized a new game, i.e. initialized attributes that are currently global variables and included private methods (e.g. _get_key, _updater, etc.) and public methods (e.g. run_game). This would also help make the code more importable without having any unwanted side-effects. There may be some duplications of what others have already said:

Docstrings Please!

Please document what the module does by including a docstring at the top or, if you go the object-oriented route, at the class level. Having docstrings for at least the public functions (or methods) would be useful.

Be Pythonic and Efficient

  1. You have a variable dead that takes on the values True and False at various points in the code. But then you have tests such as if test == True: and if test == False:, which would be more pythonic if expressed respectively as if test: and if not test:.
  2. A test such as if undertarget == " " or undertarget == "\u235A" or undertarget == "\u06E9": can be more concisely and efficiently expressed as if undertarget in (" ", "\u235A", "\u06E9"):.
  3. You have import statements within functions that are called multiple times. True, the actual import will only occur once, but the test to see whether the modules have already been imported will be performed needlessly multiple times. Move these import statement to the top of your file.
\$\endgroup\$
0
3
\$\begingroup\$

Layout

Move the functions to the top after the import lines. Having them in the middle of the code interrupts the natural flow of the code (from a human readability standpoint).

Also, add blank line before and after the functions. The black program can be used to automatically reformat the code. This will also add consistency to you other whitespace usage.

Documentation

We don't know what the code does because you did not tell us. You shoud summarize the purpose of the code at the top using a docstring such as:

"""
A platformer game called Uni where this small character has to go as high as possible,
in which the player constantly has to choose between jumping (W)
going left or right (A-D) climbing down (S) or waiting (Just enter). 

Add details here.
"""

The PEP 8 style guide recommends adding docstrings for functions as well.

Simpler

Lines like this:

if dead == True:

are simpler as:

if dead:

There is no need to explicitly compare against boolean values True and False. You can use the ruff tool to identify all such cases.

Naming

The PEP 8 style guide recommends snake_case for function and variable names.

setrightundertarget would be set_right_under_target.

DRY

This code has 3 comparisons to the same variable:

elif undertarget == " " or undertarget == "\u235A" or undertarget == "\u06E9":

This is simpler and removes the repetition:

elif undertarget in (" ", "\u235A", "\u06E9"):

UX

When I run the code, I seem to be prompted to do something, but I don't know what I am expected to do. Instead of just showing me this bare prompt:

key = input(">> ").lower()

perhaps you could print some simple instructions.

\$\endgroup\$
1
  • \$\begingroup\$ black is pretty great, though I've also become a fan or ruff for having both ruff format and ruff check (with optional --fix), where the former roughly replaces black for formatting and the latter isort and flake8. It's also impressively faster at both of those. \$\endgroup\$ Commented 1 hour ago

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.