4
\$\begingroup\$

I did a project in Python where I combined user-input, random, and the turtle module to make a sort of "Mandala Generator", or a program that can generate a simple design either randomly or by the specifications of users. Is there any way I can shorten my code without drastically changing it?

#custom mandala project Python
import turtle
import random
import time

#is the game in progress?
drawing = False

#define turtle
pen = turtle.Turtle()
pen.setheading(90)
pen.pensize(3)
pen.speed(12)

#colors empty list
colors = []

#define variables
LINE1, LINE2 = "~" * 36, "~" * 24

#define check input function
def get_input(response, values):
    #make sure the input is in the choices
    while True:
        value = input(response).strip().lower()
        if value in values:
            return value

#define main function
def main():
    print(LINE1 + "\nCustom Mandala Generator\n" + LINE1)
    name = input("What is your name? ")
    print("In this custom mandala generator, you get to choose the size and colors of your mandala, or have the computer generate a random one,", name, ".\n" + LINE2)
    time.sleep(1)
    ready = input("Are you ready or not? ")

    if ready[0].lower() == "y":
        answer = get_input("Would you like to use our random generator function or our custom function? {random/custom} ", {"random", "custom"})

        #universal variables
        size = random.uniform(1.5, 2.5)
        fd = 75 * size

        if answer == "random":
            colors.extend(("darkred", "red", "yellow", "darkgreen", "green", "lightgreen", "darkblue", "blue", "purple"))
            #rt = right turn
            rt = random.uniform(100, 300)

        elif answer == "custom":
            print(LINE2 + "\nYou can choose any colors from this list for your Mandala: \nDarkred, red, yellow, darkgreen, green, lightgreen, darkblue, blue, purple")
            color1 = get_input("What is the first color? ", {"darkred", "red", "yellow", "darkgreen", "green", "lightgreen", "darkblue", "blue", "purple"})
            color2 = get_input("What is the second color? ", {"darkred", "red", "yellow", "darkgreen", "green", "lightgreen", "darkblue", "blue", "purple"})
            colors.extend((color1, color2))
            rt = int(input("What angle would you like your Mandala to turn at? "))

        #pre-draw sequence
        print(LINE2 + "\nInitializing...")
        print("Determining features... \nColor... \nSize...")
        time.sleep(3)
        print(LINE1 + "\nYour final result should be drawing as of this point!\nThanks for using the generator,", name, "!")

        for counter in range(50):
            selection = random.choice(colors)
            pen.color(selection)
            pen.forward(fd)
            pen.right(rt)

    else:
        print("Come and use the custom generator later!")


main()
\$\endgroup\$
3
\$\begingroup\$

I don't know why you would want to shorten your code. It is already just 73 lines long (including the final blank line). Apart from that, "short" does not automatically mean "good". Maybe concise would be a better fit here, which I would interpret as "the code does what it should do without clutter". IMHO your code is very concise and readable at the moment and that should not be sacrificed to gain a few lines/bytes. We're not at Code Golf here.

Nevertheless, there are a few things you can improve easily.

Documentation

Python's official Style Guide has recommendations on how to comment code and functions. Function docstrings should always be defined inside the function body and be surrounded by triple quotes """...""". Applying this to your code it would look something like

def get_input(response, values):
    """make sure the input is in the choices"""
    # your code here

This will also allow Python's built-in help(...) function and most IDEs to pick up the documentation.

Avoid globals

You should always strive to avoid global variables whenever possible. E.g. in your code colors is not used outside of main() and should therefore be defined there. drawing does not seem to be used anywhere, so you might be able to drop it completely. LINE1 and LINE2 are two valid uses for globals. My only recommendation here would be to put them on two separate lines. Oh, and strictly speaking your comment on them is "wrong" since they are not used as variables but as constants, which is also supported by the ALL_CAPS_NAME (the Style Guide also says something on that).

Variable names

There is this piece of code which I find kind of amusing:

#rt = right turn
rt = random.uniform(100, 300)

Why not just call the variable right_turn or right_turn_angle right away then? The same reasoning would apply to fd, but you didn't care to write the comment there and left it as an exercise to the person looking at your code to find out that it actually stands for forward. You could also rename size to mandala_scale or so and then drop that #universal variables altogether since it does not really help to understand the code better. At least at the moment you could even get rid of size completely since its only use is to be multiplied by 75 on the next line and it's never used after that.

Other aspects

There is a lot of repetition when it comes to the list of available colors. Including the print, you define them four times. If you ever decide to add, change or remove a color from that list, there would be four places that change would need to be applied to. I would recommend to define them in one place, and reuse the list wherever it is needed.

# ...

colors = []
all_colors = ("darkred", "red", "yellow", "darkgreen", "green", "lightgreen", "darkblue", "blue", "purple")
if answer == "random":
    colors.extend(all_colors)
    right_turn = random.uniform(100, 300)

elif answer == "custom":
    all_colors_string = ", ".join(all_colors)[:-2] # -2 to trim of the trailing space and ,
    print(LINE2 + "\nYou can choose any colors from this list for your Mandala: \n" + all_colors_string)
    color1 = get_input("What is the first color? ", all_colors)
    color2 = get_input("What is the second color? ", all_colors)

# ...

In the transformation the set notation ({...}) got lost, but I think your application will not really suffer from the (minuscule) performance impact because a tuple is used instead of a set.

Another handy feature I would like to point out to you is string formatting. So what's that? Let's look at an example from your code:

print("[...] or have the computer generate a random one,", name, ".\n" + LINE2)

Here you are using Python's implicit behavior to print the elements of a tuple separated by whitespace1. That leads to the following output (minus the annotation of course):

[...] or have the computer generate a random one, Alex .
~~~~~~~~~~~~~~~~~~~~~~~~
                                       does look off ~^

Note the unwanted whitespace between the name and the dot?
But we can do better! Python has very powerful string formatting capabilities (doc), and since you have tagged your question as Python 3, you can likely use the new f-string syntax (helpful blog post comparing all the different ways, specification (PEP 498)). Rewriting the code from above using an f-string would lead to:

print(f"[...] or have the computer generate a random one, {name}.\n" + LINE2)

So f"... {name}" basically generates a string containing the value of name right at the spot we the curly braces and the name of the variable where.2 For the sake of completeness that generates the following output:

[...] or have the computer generate a random one, Alex.
~~~~~~~~~~~~~~~~~~~~~~~~

As a final note, you could/should also add

if __name__ == "__main__":
    main()

(explanation) to highlight what part of the code is actually run if this file is used as Python script.


Bonus: by removing unnecessary globals and comments your script is actually even shorter more concise now, although that was not the main goal of what I was trying to tell you.


1 Depending on your Python experience you might wonder where there is a tuple in that line or you will nod silently. In the first case I would recommend reading this SO post on how Python deals with functions that have a variable number of arguments (often varargs for short), and then to have a look at how the print function is defined in the documentation.
2 In fact, f-strings are way more powerful than this, but you should refer to the resources linked earlier if you want to know more.

\$\endgroup\$
-1
\$\begingroup\$

I prefer to have at least one print statement per line of output. Instead of:

print('line1\nline2\line3')

I would rather write:

print('line1')
print('line2')
print('line3')

That way, when there is a large block of output on the screen and you want to find it in the file, it's obvious where in the code it might be.


Going to sleep before doing the actual work is dishonest. There is no benefit of having sleep(3) in the code, it's just wasting the user's time.


From a technical viewpoint, asking for the name of the user is unnecessary.

The next question is unnecessary as well since the user, if they choose not to produce a mandala, can always quit the program at any time.

The question about the angle should suggest some useful numbers or at least a range, to help people who don't like math.

The following line is a bold lie:

print("Determining features... \nColor... \nSize...")

By using the word mandala, you made me expect a colorful image with curved lines, the lines painted in black and the areas between filled with colors. Just drawing some straight lines doesn't qualify as a mandala for me. That part is something you should invest some work, to create beautiful images.

\$\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.