Is there a way to make them move together simultaneously?
The best we can hope to do is make them appear to move simultaneously. Below are three increasingly complex approaches to this problem. But first, let's establish our baseline code, two turtles heading at each other and stopping when they meet at the origin:
from turtle import Screen, Turtle
screen = Screen()
a = Turtle('square', visible=False)
a.speed('slow')
a.color('red')
a.penup()
a.setx(-300)
a.setheading(0)
a.pendown()
a.showturtle()
b = Turtle('circle', visible=False)
b.speed('slow')
b.color('green')
b.penup()
b.setx(300)
b.setheading(180)
b.pendown()
b.showturtle()
### Subsequent variations start here ###
a.forward(300)
b.forward(300)
### Subsequent variations end here ###
screen.mainloop()
The above doesn't do what we want as one turtle moves and then the other. For our first variation, we simply chop up the motion into smaller units and alternate:
###
for _ in range(300):
a.forward(1)
b.forward(1)
###
Our next variation uses timer events to control the motion of the two turtles:
###
def move(turtle):
turtle.forward(1)
if turtle.distance(0, 0) > 1 :
screen.ontimer(lambda t=turtle: move(t), 50)
move(a)
move(b)
###
Our final variation uses threading to independently control the two turtles. Each turtle is a thread and there's a third, main thread that handles all the graphics operations for the turtle threads. This is needed as turtle operates atop tkinter which has issues handling graphics from secondary threads:
###
from threading import Thread, active_count
from queue import Queue
QUEUE_SIZE = 1
def process_queue():
while not actions.empty():
action, *arguments = actions.get()
action(*arguments)
if active_count() > 1:
screen.ontimer(process_queue, 100)
actions = Queue(QUEUE_SIZE) # a thread-safe data structure
def move(turtle):
while turtle.distance(0, 0) > 1:
actions.put((turtle.forward, 1))
Thread(target=move, args=[a], daemon=True).start()
Thread(target=move, args=[b], daemon=True).start()
process_queue()
###