6
\$\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 layerimport osimport timedebug = Falsedef 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+2188portal = " "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 lineswindow_height = 9scrollingY = 0playerX = 1player = '\u237E'fellat = 0dead = Falsewhile scrollingY + window_height < len(my_list):    scrollingY += 1scrollingY -= 6def clear():    os.system('cls' if os.name == 'nt' else 'clear')s = []middle = 0playerline = 0target = "a"undertarget = "b"underundertarget = "c"fellat = 0key = "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.

toolic's user avatar
toolic
16.4k6 gold badges29 silver badges221 bronze badges
askedNov 18 at 19:27
Chip01's user avatar
\$\endgroup\$
1

3 Answers3

6
\$\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 toTrue orFalse are unnecessary.dead == False is the same asnot dead.
  • Avoid repeating yourself. Near the beginning ofdisplay_list you calculatemin(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-allexcept: as you have. Instead aim to catch specific exception types.

I strongly suggest you readPEP 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 ReadPEP 257 on docstrings.

answeredNov 18 at 19:49
Chris's user avatar
\$\endgroup\$
0
5
\$\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 includingencapsulation. I think readability would be improved if you had a class namedUni 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 variabledead that takes on the valuesTrue andFalse at various points in the code. But then you have tests such asif test == True: andif test == False:, which would be more pythonic if expressed respectively asif test: andif not test:.
  2. A test such asif undertarget == " " or undertarget == "\u235A" or undertarget == "\u06E9": can be more concisely and efficiently expressed asif undertarget in (" ", "\u235A", "\u06E9"):.
  3. You haveimport 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 theseimport statement to the top of your file.
answeredNov 18 at 21:23
Booboo's user avatar
\$\endgroup\$
0
4
\$\begingroup\$

Layout

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

Also, add blank line before and after the functions.Theblack program can be used to automaticallyreformat 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."""

ThePEP 8 style guide recommendsadding 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 valuesTrue andFalse.You can use therufftool to identify all such cases.

Naming

ThePEP 8 style guide recommendssnake_case for function and variable names.

setrightundertarget would beset_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.

answeredNov 18 at 20:01
toolic's user avatar
\$\endgroup\$
3
  • 1
    \$\begingroup\$black is pretty great, though I've also become a fan orruff for having bothruff format andruff check (with optional--fix), where the former roughly replacesblack for formatting and the latterisort andflake8. It's also impressively faster at both of those.\$\endgroup\$CommentedNov 19 at 6:02
  • 1
    \$\begingroup\$@fyrepenguin: Agreed! I've recommendedruff in 73 of my answers already, so I'm sold.\$\endgroup\$CommentedNov 19 at 12:33
  • 1
    \$\begingroup\$@fyrepenguin: I tried avoiding it as per the rules, but I think its worth saying: THANKS\$\endgroup\$CommentedNov 20 at 23:33

You mustlog 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.