Home > Enterprise >  How to prevent 2 python turtles in "movement" from flickering when coliding
How to prevent 2 python turtles in "movement" from flickering when coliding

Time:11-02

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()

CodePudding user response:

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 the Turtle() objects to influence which car will be visible on top of the other ones.

CodePudding user response:

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.

  • Related