0

For a school projet that wants us to use python turtle but no external libraries (that you have to install from command line), I firstly made a car that can move following a few tutorials. The car worked perfectly. I then tried to add multiple cars. Because I couldn't use multi threading libraries, I succesfully used the exec function to create autmatically a given number of turtles acting as cars. They work fine (although having to many of them is laggy, I don't need a lot of them)

My problem is when the cars are colliding, one frame it shows a car on top and the next frame it's the other car on top, and each frame it switches making them "flicker", while I want the bottom one (with the smallest Y value) to be the one shown when 2 or more cars are colliding (for perspective reasons).

Here is my code that I simplified the best I could :

from turtle import *
from random import *
from tkinter import *

#Setup of Turtles and Screen
root = Tk()
screen = Screen()
screen.setup(width=root.winfo_screenwidth(), height=root.winfo_screenheight())
screen.tracer(False)
root.destroy()

for loop in range(1, 3):
    exec("car" + str(loop) + " = Turtle()")
for loop in range(1, 3):
    exec("car" + str(loop) + ".hideturtle()")
    exec("car" + str(loop) + ".speed(0)")

#Car itself (simplified to a rectangle)
def car_form(car_color: tuple, car):
    size = 1.7
    car.pendown()
    colormode(255)
    car.fillcolor(car_color)
    car.begin_fill()
    car.setx(car.xcor() + 75 * size)
    car.sety(car.ycor() + 20 * size)
    car.setx(car.xcor() - 75 * size)
    car.sety(car.ycor() - 20 * size)
    car.end_fill()

#Movement of the cars
def infinite_car(car, car_color, way):
    screen.update()
    car.clear()
    car_form(car_color, car)
    car.setheading(0)
    if way == 1:
        car.setx(car.xcor() + 0.2)
    else:
        car.setx(car.xcor() - 0.2)

#Main function that position the cars, give some values and start the movement loop
def main():
    for loop in range(1, 3):
        exec('car_color' + str(loop) +' = (randint(0, 255), randint(0, 255), randint(0, 255))')
        if loop == 1:
            exec('car' + str(loop) + '.setx(-100)')
        else:
            exec('car' + str(loop) + '.setx(100)')
    while True:
        for loop in range(1, 3):
            if loop == 1:
                exec('infinite_car(car' + str(loop) + ', car_color' + str(loop) + ', 1' + ')')
            else:
                exec('infinite_car(car' + str(loop) + ', car_color' + str(loop) + ', 2' + ')')
main()
KingCraft
  • 3
  • 1
  • 1
    avoid wildcard imports: https://stackoverflow.com/questions/73698351/is-anyone-know-how-to-connect-tkinter-webcam-to-yolov5/73712541#73712541 – D.L Nov 01 '22 at 20:58
  • Avoid using `exec`--just call the function. Use `ontimer` to run a proper event loop; `while True` just slams the CPU as hard as possible. – ggorlen Nov 01 '22 at 22:06
  • You say, "I want the bottom one (with the smallest Y value) to be the one shown" but they both have the same Y value, don't they? – cdlane Nov 01 '22 at 22:42
  • It's only for example purposes my bad, in my program they can't so don't worry – KingCraft Nov 01 '22 at 22:47
  • Now you have two good answers, but your question is still not marked as answered ... but it should. – Claudio Nov 02 '22 at 00:50
  • I will mark answered when I finish using them because it was late for me, sorry – KingCraft Nov 02 '22 at 08:06

2 Answers2

0

Usage of exec() in Python should be avoided and as replacement of several numbered variables you use in Python a list of Python objects which can be then indexed by an integer number from a given range().

The turtle modules Turtle() is capable of showing the turtle in a form of a square, so using this instead of drawing a square seems to fix the problem with flickering.

You can define own turtle shapes (for example looking like a car) and if you don't need to rotate the shape you can even use an image of a car as a turtle shape moving around on the screen. Check out Python documentation for details how to define own turtle shape ( using turtle.register_shape() function) if you like to create a drawing of a car as turtle shape.

Below the code from your question simplified and modified to get rid of the exec() command and wildcard imports. It runs on my machine without flickering and uses time.sleep() to limit the CPU-load and control the speed of car movement:

from turtle  import Turtle, Screen, colormode
from random  import randint
from tkinter import Tk
from time    import sleep

#Setup of Turtles and Screen
root = Tk()
screen = Screen()
screen.setup(width=root.winfo_screenwidth(), height=root.winfo_screenheight())
screen.tracer(False)
root.destroy()

lst_of_cars       = []
lst_of_car_colors = []
colormode(255)
frames = 100
sleep_time = 1/frames

for car_no in range(2):
    car_color  = (randint(0, 255), randint(0, 255), randint(0, 255))
    car_turtle = Turtle()
    car_turtle.color(car_color)
    car_turtle.shape("square")
    car_turtle.shapesize(stretch_wid=3, stretch_len=7)
    car_turtle.speed(0)
    car_turtle.penup() 
    if car_no == 0:
       car_turtle.setx(-100)
    else:
       car_turtle.setx(100)
    lst_of_cars.append(car_turtle)
    lst_of_car_colors.append(car_color)

#Movement of the cars
def infinite_car(car, way):
    screen.update()
    car.setheading(0)
    if way == 1:
        car.setx(car.xcor() + 1)
    else:
        car.setx(car.xcor() - 1)

#Main function that position the cars, give some values and start the movement loop
def main():
    while True:
        for car_no in range(2):
            if car_no == 0:
                infinite_car(lst_of_cars[car_no], 1)
            else:
                infinite_car(lst_of_cars[car_no], 2)
        sleep(sleep_time)
main()

Change the order of creating/moving the Turtle() objects to influence which car will be visible on top of the other ones.

Check also the turtle.ontimer() and turtle.mainloop() methods in the Python documentation if you want to change the code flow to an event driven one (as suggested in the other answer).

Claudio
  • 7,474
  • 3
  • 18
  • 48
  • Hi Thanks this is very useful. But one thing, do you have a link for a tutorial for a turtle that is more than just a rectangle? Because the car is much more complex – KingCraft Nov 01 '22 at 22:29
  • Oh yeah I didn't see it when I read, sorry Thank you again – KingCraft Nov 01 '22 at 22:48
0

I would go further than @Claudio and get rid of while True: and sleep() which have no place in an event-driven environment like turtle. I would also make the car's direction a property of the car rather than a series of special cases:

from turtle import Screen, Turtle
from random import randrange

SIZE = 1.7
CURSOR_SIZE = 20

# Main function that position the cars, give some values and start the movement loop
def move():
    for car in cars:
        car.setx(car.xcor() + 0.2 * car.direction)

    screen.update()
    screen.ontimer(move)

# Setup of Turtles and Screen
screen = Screen()
screen.setup(width=1.0, height=1.0)
screen.colormode(255)
screen.tracer(False)

cars = []

for direction in (1, -1):
    car = Turtle()
    car.penup()
    car.shape('square')
    car.shapesize(stretch_wid=20*SIZE/CURSOR_SIZE, stretch_len=75*SIZE/CURSOR_SIZE)
    car_color = randrange(256), randrange(256), randrange(256)
    car.fillcolor(car_color)

    car.setx(-100 * direction)
    car.direction = direction  # user property

    cars.append(car)

move()

screen.mainloop()

As far as the car's shape, you can still draw a car with turtle commands, and save that as a turtle shape you can assign. Or you can use GIF images to represent the cars. Both will work with this design. @Claudio's recommendation to read the documentation for turtle.register_shape() is right on the mark.

The above code, and @Claudio's, leaves the "front" car in the collision to how/when the cars are added in the program, i.e. not explicit. For that purpose you could add a property (e.g. a Z coordinate) to define which car is in front and remember that the last car drawn/moved is on top.

cdlane
  • 40,441
  • 5
  • 32
  • 81
  • What is the purpose of `0.2` in `car.setx(car.xcor() + 0.2 * car.direction)` ? – Claudio Nov 01 '22 at 23:35
  • Are you aware that use of `ontimer()` without setting a value for its `t` parameter puts unnecessary full load on the CPU? – Claudio Nov 01 '22 at 23:41
  • 0.2 is just the speed for testing purposes – KingCraft Nov 02 '22 at 08:05
  • Using 0.2 to influence animation speed is waste of CPU resources and electrical power. Specify the t-parameter in `ontimer()` instead if you are using the event driven approach. – Claudio Nov 03 '22 at 11:18
  • By the way: an event-driven approach has the disadvantage of having sometimes unexpected behavior because the mechanism of processing event ques is 'hidden' from insight and as good as impossible to track what results sometimes in severe problems with debugging. This is one of the reasons why turtle supports also not event driven approach. – Claudio Nov 03 '22 at 14:19