Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python Multiple Turtles Moving (seemingly) Simultaneously

I'm working on testing something for my teacher, he wants to see how the program below could possibly run faster if we simulated the simultaneous (i know it can't be perfectly simultaneous, this is just an experiment for the sake of learning/practicing) movement of multiple turtles. I've tried using modules like multiprocessing, threading, and even some crazy stupid attempt to time and delay (I'm in high school and I just learned about classes in python because of a previous question I asked I think last week).

So after many failed attempts I'm asking if someone has a few ideas of what else to try, or a direction to go in to simulate simultaneous movement of the turtles

import turtle
from turtle import Turtle

turtle.getscreen().delay(0)
class MyTurtle(Turtle):
    def petal(self):
        for i in range(90):
            self.fd(1)
            self.rt(1)
        self.rt(90)
        for i in range(90):
            self.fd(1)
            self.rt(1)

    def stem(self):
        self.pencolor('green')
        self.fd(250)

    def flowerhead(self):
        for i in range(9):
          self.pencolor('red')
          self.begin_fill()
          self.petal()
          self.lt(230)
          self.end_fill()

    def stempetal(self):
        self.seth(90)
        self.rt(15)
        self.fillcolor('green')
        self.begin_fill()
        self.petal()
        self.end_fill()

    

tony = MyTurtle(shape='turtle')
todd = MyTurtle(shape='turtle')
tina = MyTurtle(shape='turtle')
tiny = MyTurtle(shape='turtle')
tweeny = MyTurtle(shape='turtle')


def flower1():
    todd.speed('fastest')
    todd.fillcolor('blue')
    todd.flowerhead()
    todd.seth(270)
    todd.stem()
    todd.stempetal()
            
def flower2():
    tony.speed('fastest')
    tony.setpos(80, -15)
    tony.pencolor('green')
    tony.goto(0, -200)
    tony.fillcolor('purple')
    tony.goto(80,-15)
    tony.rt(40)
    tony.flowerhead()
    

def flower3():
    tina.speed('fastest')
    tina.setpos(-80, -15)
    tina.pencolor('green')
    tina.goto(0, -200)
    tina.fillcolor('teal')
    tina.goto(-80,-15)
    tina.lt(40)
    tina.flowerhead()
    
    
def flower4():
    tiny.speed('fastest')
    tiny.setpos(160, -25)
    tiny.pencolor('green')
    tiny.goto(0, -200)
    tiny.fillcolor('black')
    tiny.goto(160, -25)
    tiny.flowerhead()
    
    
def flower5():
    tweeny.speed('fastest')
    tweeny.setpos(-160, -25)
    tweeny.pencolor('green')
    tweeny.goto(0, -200)
    tweeny.fillcolor('pink')
    tweeny.goto(-160,-25)
    tweeny.lt(40)
    tweeny.flowerhead()
    
    
flower2()
tony.hideturtle()
flower4()
tiny.hideturtle()
flower3()
tina.hideturtle()
flower5()
tweeny.hideturtle()
flower1()
todd.hideturtle()
like image 669
TheKingOfJesters Avatar asked Aug 18 '26 05:08

TheKingOfJesters


2 Answers

The solution is to disable updating the position of each turtle, and then force the whole screen to update once the new position is computed.

import turtle

# our two turtle instances
first, second = turtle.Turtle(), turtle.Turtle()

turtle.tracer(False)  # disable updating view on screen for all turtles

# make one move - note this will not appear on screen.
first.forward(50)
second.left(20)

# when you are ready to see the whole screen update
turtle.update()
turtle.exitonclick()

To do what you want, you will have to essentially make it so that every new action is done before a turtle.update(). You cannot keep it to a serial execution as you are doing now - in other words, you can't run flower1, then flower2, in sequence.

Here's an example of a pair of turtles that will generate a random pattern on the screen at the same time:

import turtle
import random

# our two turtle instances
turtles = [turtle.Turtle(), turtle.Turtle()]
turtle.tracer(False)

for _ in range(10000):  # make ten thousand moves.

    for t in turtles:
        # list the possible moves available
        possible_moves = [t.forward, t.back, t.right, t.left]
        # give it a random value
        random_value = random.randint(0, 100)
        # make a random move 
        random.choice(possible_moves)(random_value)

    # update the whole screen now that the new positions have been calculated
    turtle.update()
    
turtle.exitonclick()

The trick here is to note that every new position for each turtle is calculated, then the screen as a whole is told to update, and only then do you move on to the next move. Every move must be as granular as possible.

like image 173
Akshat Mahajan Avatar answered Aug 20 '26 18:08

Akshat Mahajan


You've asked for two different things, 'run faster' and 'simulate simultaneous movement'. I believe we can do both (separately) but I don't believe that tracer() and update() are the answer in this situation as they'd just be a band-aid to cover over the real issue.

wants to see how the program below could possibly run faster

If you want it to run faster, fix the bottleneck which is the petal() function. Replace it with something that uses turtle's built-in circle() function which is faster. For example:

def petal(self):
    self.circle(-60, 90)
    self.rt(90)
    self.circle(-60, 90)

This speeds up your code by a factor of 25X with no other changes.

simulate simultaneous movement of the turtles

This can be done with turtle's own ontimer() event hander and some careful programming. Surprisingly, we use your original petal() logic as it breaks up the graphics into minute steps between which we can switch off processing to another timed event:

from random import randint
from turtle import Turtle, Screen

class MyTurtle(Turtle):

    def petals(self, size=30, count=8, speed=100):
        if size == 30:
            self.begin_fill()

        if size > 0:  # drawing leading edge of petal
            self.fd(3)
            self.rt(3)

            screen.ontimer(lambda: self.petals(size - 1, count, speed), speed)
            return

        if size == 0:  # switch to other edge of petal
            self.rt(90)

        if size > -30:  # drawing trailing edge of petal
            self.fd(3)
            self.rt(3)

            screen.ontimer(lambda: self.petals(size - 1, count, speed), speed)
            return

        self.end_fill()  # finish this petal
        self.lt(230) # prepare for the next petal

        if count > 0:  # drawing the next petal
            screen.ontimer(lambda: self.petals(count=count - 1, speed=speed), speed)
            return

        self.hideturtle()  # finished drawing

    def stem(self):
        self.pencolor('green')
        self.fd(250)

    def flowerhead(self):
        self.pencolor('red')

        self.petals(speed=randint(50, 250))

def flower2():
    tony.color('green', 'purple')
    tony.penup()
    tony.goto(0, -200)
    tony.pendown()
    tony.showturtle()
    tony.goto(80, -15)
    tony.rt(40)
    tony.flowerhead()

def flower3():
    tina.color('green', 'turquoise')
    tina.penup()
    tina.goto(0, -200)
    tina.pendown()
    tina.showturtle()
    tina.goto(-80, -15)
    tina.lt(40)
    tina.flowerhead()

def flower5():
    tweeny.color('green', 'pink')
    tweeny.penup()
    tweeny.goto(0, -200)
    tweeny.pendown()
    tweeny.showturtle()
    tweeny.goto(-160, -25)
    tweeny.lt(40)
    tweeny.flowerhead()

tony = MyTurtle(shape='turtle', visible=False)
tina = MyTurtle(shape='turtle', visible=False)
tweeny = MyTurtle(shape='turtle', visible=False)

screen = Screen()

screen.ontimer(flower2, 100)
screen.ontimer(flower3, 120)
screen.ontimer(flower5, 100)

screen.mainloop()

RUNNING IMAGE

enter image description here

It won't be any faster, as it's just a simulation. (Well, it does go a little faster as I made petal drawing slightly cruder in return for speed.) If you look closely, you can see the turtles are (intentionally) moving at their own individual speed.

like image 39
cdlane Avatar answered Aug 20 '26 20:08

cdlane



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!