4
\$\begingroup\$

I already asked this question, but the script I provided was not compliant to PEP-8, so I rewrote the script for better readability.

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 updated script;


"""
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).
"""

import os
import time

debug = False


def get_key():
    """Cross-platform key input. Returns a single character."""
    try:
        import sys
        import tty
        import 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 Exception:
        return input("You may press W,A,S or D to move (or nothing, lets say if you are in freefall and you want to fall straight down), then hit Enter: ")[0].lower


# Game state variables
checkpoint1 = "\u235A"
portal = " "
my_list = []
window_height = 9
scrolling_y = 0
player_x = 1
player_char = "\u237E"
fellat = 0
dead = False
target = "a"
undertarget = "b"
right_undertarget = "c"
player_line = 0
key = "uhh"


def update_map():
    """Update the map with the current portal and checkpoint."""
    # This is the layout of the level, which I would like to be automatically generated instead (while remaining possible for the player to complete)
    global my_list, portal
    my_list = [
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        f"     \u2359    \u2359      {portal}  ",
        "   \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_",
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        "                    ",
        "                    ",
    ]


update_map()

# Pre-scroll to initial visible area
while scrolling_y + window_height < len(my_list):
    scrolling_y += 1
scrolling_y -= 6


def clear_screen():
    """Clear the terminal screen."""
    os.system("cls" if os.name == "nt" else "clear")


def get_player_line(i):
    """Return the player's current line split into segments."""
    line = my_list[i]
    middle_pos = len(line) // 2
    before = line[: middle_pos + player_x]
    target_char = line[middle_pos + player_x]
    after = line[(middle_pos + player_x + 1) :]
    return before, target_char, after, middle_pos


def handle_checkpoint():
    """Check if player reached the checkpoint and activate it."""
    global checkpoint1
    if target == "\u235A" and scrolling_y == 7 and player_x == -3:
        checkpoint1 = "\u06E9"
        update_map()


def handle_player_physics():
    """Handle bouncing, falling, and spike death."""
    global scrolling_y, fellat, dead, player_char
    if (target == "\u210D" and key != "s") or (target == "\u20AA" and key == "s"):
        scrolling_y -= 1
        fellat = 0
        clear_screen()
        display_map()
        return True  # interrupts further display
    elif undertarget == "\u2359" or target == "\u2359":
        player_char = "\u237D"
        dead = True
    elif undertarget in [" ", "\u235A", "\u06E9"]:
        scrolling_y += 1
    elif right_undertarget == "\u20AA" and fellat == 2:
        fellat = 0
    return False


def display_map():
    """Display the visible portion of the map and handle player character."""
    global target, undertarget, right_undertarget, middle, player_char, player_line

    right_undertarget_set = False
    clear_screen()

    for i in range(scrolling_y, min(scrolling_y + window_height, len(my_list))):
        if i == min(scrolling_y + window_height, len(my_list)) - 3:
            # Player line
            player_line = i
            before, target, after, middle = get_player_line(i)
            undertarget = my_list[i + 1][(len(my_list[i + 1]) // 2) + player_x]

            if not right_undertarget_set:
                right_undertarget = undertarget
                right_undertarget_set = True

            handle_checkpoint()
            if handle_player_physics():
                break

            # Display the line with the player
            print(before + target + "\b" + player_char + after)
        else:
            print(my_list[i])

    if dead:
        print("\n     GAME  OVER     ")


# Main game loop
while not dead or checkpoint1 == "\u06E9":
    clear_screen()
    scrolling_y -= fellat
    display_map()

    if debug:
        print(
            f"PlayerX:{player_x} ScrollingY:{scrolling_y} "
            f"Target:{target} Undertarget:{undertarget} Fellat:{fellat}"
        )

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

    # Respawn at checkpoint
    if checkpoint1 == "\u06E9" and dead:
        checkpoint1 = " "
        update_map()
        dead = False
        fellat = 0
        player_x = -3
        scrolling_y = 7
        clear_screen()
        display_map()
        player_char = "\u237E"

    # Movement controls
    if key == "q":
        break
    elif key == "w":
        fellat = 3
    elif key == "s" and scrolling_y + window_height < len(my_list):
        scrolling_y += 1
        fellat = 0
    elif (
        key == "d"
        and player_x < 7
        and my_list[player_line - (1 if fellat != 0 else 0)][(middle + player_x) + 1]
        != "\u20AA"
    ):
        player_x += 1
    elif (
        key == "a"
        and player_x > -10
        and my_list[player_line - (1 if fellat != 0 else 0)][(middle + player_x) - 1]
        != "\u20AA"
    ):
        player_x -= 1

    if fellat > 0:
        fellat -= 1

    # Portal animation sequence
    if scrolling_y == -2 and portal == " ":
        for p in ["\u2223", "\u2336", "\u2180", "\U00002182"]:
            portal = p
            update_map()
            clear_screen()
            display_map()
            time.sleep(0.15)

    # Ending sequence
    if player_x == 6 and scrolling_y == -2:
        player_look_before = player_char
        player_char = " "
        update_map()
        clear_screen()
        display_map()
        time.sleep(0.2)

        for p in ["\u2180", "\u2336", "\u2223", " "]:
            portal = p
            update_map()
            clear_screen()
            display_map()
            time.sleep(0.2)

        time.sleep(1.8)
        clear_screen()
        time.sleep(6)

        for p in [" ", "\u2223", "\u2336", "\u2180", "\U00002182", player_look_before]:
            clear_screen()
            player_char = p
            r, c = os.get_terminal_size()
            print("\n" * r + " " * (c // 2) + f"{player_char}")
            time.sleep(0.2)

        time.sleep(2.8)
        clear_screen()
        print("[The Creator] - Congradulations.")
        time.sleep(2)
        input("<<Continue>>")
        clear_screen()
        print("[The Creator] - You have proven yourself worthy of becoming citizen of unicode.")
        time.sleep(2)
        input("<<Continue>>")

        for p in [player_char, "\U00002182", "\u2180", "\u2336", "\u2223", " "]:
            clear_screen()
            player_char = p
            r, c = os.get_terminal_size()
            print("\n" * r + " " * (c // 2) + f"{player_char}")
            time.sleep(0.9)

        time.sleep(3)
        clear_screen()
        time.sleep(4)
        print("YOU WON")
        time.sleep(0.5)

        for msg in ["\n", "Coded by:", "Emmanuel Ghattas", "\n", f"Thanks for pl{player_look_before}ying!"]:
            print(msg)
            time.sleep(0.2)

        time.sleep(7)
        break

As you may notice, update_map() defines the layout of this level. I would like for the game layout (my_list[]) to be automatically generated through some logic. If anyone knows how to make this logic so that each time the player runs the script the layout is set through some rules and randomness 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\$
0

2 Answers 2

4
\$\begingroup\$

Layout

There is never a reason to have a long comment line:

# This is the layout of the level, which I would like to be automatically generated instead (while remaining possible for the player to complete)

This can easily be split up into multple lines for better readability:

# This is the layout of the level, which I would like to be automatically generated instead
# (while remaining possible for the player to complete)

Naming

The variable name my_list is too generic. Array variables are typically pluralized. Maybe something like lines would be more descriptive.

UX

When I run the code, I see this:

   ℍ                
       ℍ₪₪⍙⍙ℍ       
     ℍ              
   ₪      ₪ℍ₪       
    ⍙ℍ    ⍙         
   ₪    ⍙ ₪₪⍙       
     ℍ ⍚  ⍙⍾        
      ₪₪₪₪₪₪₪₪       
_⍙⍙⍙⍙⍙⍙⍙⍙⍙⍙⍙⍙⍙⍙⍙⍙⍙⍙_
>> 

I seem to be prompted to do something, but I don't know what I am expected to do. I think this line in # Main game loop could be improved by adding instructions in this input line:

key = input(">> ").lower()
\$\endgroup\$
1
  • 2
    \$\begingroup\$ Thanks for pointing that out, I thought I had already modified the input to be more clear, but I just noticed that I’ve only done so for the exception in the get_key() function. \$\endgroup\$ Commented 17 hours ago
4
\$\begingroup\$

I'm going to look at some of the points I made in my previous review and let's see how you've done.

  • 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.

This has not been addressed.

  • 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 docstring, it's doing too much.

You are using more functions. You do still have that absurdly long while loop. It's too long, but it should also be wrapped in a function and that function call guarded by a check that __name__ is "__main__" so that your program can be imported into another Python program without actually running the main loop.

  • Many of your lines are much too long. Aim for an 80 character width limit.

This is a little better.

  • Comparisons to True or False are unnecessary. dead == False is the same as not dead.

This has not been addressed.

  • 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.

This has not been addressed.

  • Avoid "magic numbers" in your code, favoring named constants instead.

This has not been addressed.

  • Do not write a catch-all except: as you have. Instead aim to catch specific exception types.

Catching except Exception: isn't really much better than except:. Think about what exceptions the block you're trying can raise, and handle those. Or let them propagate out.

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

I very much doubt you've had a chance to thoroughly read PEP 8.

Where to go from here

Take a step back and think about the things in your program. Can you model those as objects, with all of the state they need and operations on that state. Your game "board," for instance, might be an object with dimensions and current location on it.

\$\endgroup\$
2
  • \$\begingroup\$ Sorry if I made you feel that way. Thank you for your patience (both of you), I'm still learning. \$\endgroup\$ Commented 19 hours ago
  • \$\begingroup\$ Enthusiasm is fantastic. \$\endgroup\$ Commented 19 hours 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.