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:
You may jump two characters over where you were initially at.
To go over a block, you must jump higher than it and from below.
To go over a ladder, you must jump at least as high as where it is from below.
When in the air, you can change direction (A-D) or you can stay where you are at.
When falling, you catch up speed.
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.
If you jump over a ladder exactly 3 characters above you, its as if you jumped over a block 2 characters above you.
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.
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) breakIf 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.
- \$\begingroup\$Sorry for the messy script, I rewrote it and re-asked the same question, please go to:codereview.stackexchange.com/questions/299588/…\$\endgroup\$Chip01– Chip012025-11-18 21:27:31 +00:00CommentedNov 18 at 21:27
3 Answers3
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
TrueorFalseare unnecessary.dead == Falseis the same asnot dead. - Avoid repeating yourself. Near the beginning of
display_listyou 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-all
except: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.
Initial Impression
- 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.
- If I enter a command that is not valid, I would like to see some sort of error message.
- 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
- You have a variable
deadthat takes on the valuesTrueandFalseat 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:. - A test such as
if undertarget == " " or undertarget == "\u235A" or undertarget == "\u06E9":can be more concisely and efficiently expressed asif undertarget in (" ", "\u235A", "\u06E9"):. - You have
importstatements 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 theseimportstatement to the top of your file.
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.
- 1\$\begingroup\$
blackis pretty great, though I've also become a fan orrufffor having bothruff formatandruff check(with optional--fix), where the former roughly replacesblackfor formatting and the latterisortandflake8. It's also impressively faster at both of those.\$\endgroup\$fyrepenguin– fyrepenguin2025-11-19 06:02:27 +00:00CommentedNov 19 at 6:02 - 1\$\begingroup\$@fyrepenguin: Agreed! I've recommended
ruffin 73 of my answers already, so I'm sold.\$\endgroup\$toolic– toolic2025-11-19 12:33:17 +00:00CommentedNov 19 at 12:33 - 1\$\begingroup\$@fyrepenguin: I tried avoiding it as per the rules, but I think its worth saying: THANKS\$\endgroup\$Chip01– Chip012025-11-20 23:33:11 +00:00CommentedNov 20 at 23:33
You mustlog in to answer this question.
Explore related questions
See similar questions with these tags.

