Python Multiple Turtles Moving (seemingly) Simultaneously - python

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()
thank you for your time

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()
first.tracer(False) # disable updating view on screen for this turtle!
second.tracer(False)
# 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()
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()]
for turtle_object in turtles:
turtle_object.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()
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.

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

Related

Is ipyturtle missing much of the full turtle-graphics module?

I'm trying Turtle for the first time and running into some trouble. I'm using ipyturtle, a widget that lets you use Turtle inline on a Jupyter notebook. It seems to be missing some commands. For example:
from ipyturtle import Turtle
t = Turtle()
t
size = 10
angle = 20
t.reset()
for a in range(10):
for i in range(100):
t.right(1)
t.forward(i/size)
t.home()
t.right(a*angle)
draws the first line, then throws the error:
AttributeError: 'Turtle' object has no attribute 'home'
It also seems to be missing goto(), speed(), among other key commands. Am I doing something wrong? If it is missing commands, how can you tell? I've used Python a fair amount in an engineering context but am unfamiliar with Github. I'd really appreciate an explanation of how someone navigating the page I linked above might sniff out a list of available commands.
I've tried running the following very similar block of code on Repl.it and it works fine:
from turtle import Turtle
t = Turtle()
size = 15
angle = 20
for a in range(1, 19):
for i in range(100):
t.right(1)
t.forward(i/size)
t.home()
t.right(a*angle)
Thanks in advance for your help!
Looking at the ipyturtle code, these are the turle methods supported:
position(self)
forward(self, length)
back(self, length)
heading(self)
goto(self, x, y=None)
setpos(self, x, y=None)
setposition(self, x, y=None)
left(self, degree=None)
right(self, degree=None)
penup(self)
pendown(self)
isdown(self)
hideturtle(self)
showturtle(self)
isvisible(self)
reset(self)
pencolor(self,r=-1,g=-1,b=-1)
So you're right about home() and speed() but goto() does appear to be there. There also appears to be only one name for each command, not the wealth of aliases available in Python turtle (e.g. forward(), fd()).
The t.home() call can be replaced by:
t.goto(0, 0)
t.setheading(0)
But in your example, you immediately do right() afterward so we can combine that into the setheading(). I believe the following should work in ipyturtle, Repl.it and standard Python:
from turtle import Turtle
size = 10
angle = 20
t = Turtle()
for a in range(1, 19):
for i in range(100):
t.right(1)
t.forward(i / size)
t.goto(0, 0)
t.setheading(-a * angle)

How to get multiple keybindings to work simultaniously in turtle graphics?

I am making a game in python called pong.
Can I get 2 different turtles in turtle graphics to respond simultaneously to keybindings?
Here is the code:
import turtle
class paddle(turtle.Turtle):
def __init__(self, x_cord, keybindings):
super().__init__("square")
self.color("white")
self.penup()
self.goto(x_cord, 0)
self.turtlesize(stretch_wid=5, stretch_len=1, outline=1)
self.screen = turtle.Screen()
self.screen.bgcolor("black")
self.screen.tracer(0)
self.screen.listen()
self.screen.update()
def up():
self.goto(self.xcor(), self.ycor() + 10)
self.screen.update()
def down():
self.goto(self.xcor(), self.ycor() - 10)
self.screen.update()
self.screen.onkey(up, keybindings[0])
self.screen.onkey(down, keybindings[1])
paddle_1 = paddle(-350, ["Up", "Down"])
paddle_2 = paddle(350, ["w", "s"])
food.screen.exitonclick()
This was once a problem I struggled with for a long time, and came to the conclusion that it's not possible (please prove me wrong, as I'm interested in the solution if there is one).
I've analyzed this great answer that explains how to bind two arrow keys for diagonal movement, but it only works one step at a time, just like how your code allows for simultaneous movement of the turtles as long as making them move one step at a time.
Anyways, that situation pushed me further into embracing the versatile Pygame python package.

How to make unfilled polies without retracing path in turtle?

I am working on a turtle project where the user can draw their own avatar.
The thing is, I want the user to be able to decide whether the turtle they drew will be filled, or not.
The filled part is simple, as, by default, the polies will get filled,
but the only way I know how to avoid filling is to retrace the lines so that the end of the line meets the start of the line.
Is there a built-in method or a more efficient way to keep the polies empty?
The problem with my current method is that the more lines (parameter) the lines there are for each turtle, the more stucky the program runs (the area doesn't influence the smoothness of the program).
import turtle
wn = turtle.Screen()
pen = turtle.Turtle('circle')
pen.shapesize(0.1, 0.1)
cor = []
# function to draw with the pen
def drag(x, y):
wn.tracer(0)
pen.goto(x, y)
cor.append(pen.pos())
# function to break out of while loop
def fill():
global done
done = True
# function to break out of while loop and set fill to False
def nofill():
global done, fill
done = True
fill = False
wn.listen()
wn.onkeypress(fill, 'f')
wn.onkeypress(nofill, 'n')
pen.ondrag(drag)
done = False
fill = True
while not done:
wn.update()
pen.begin_poly()
if fill:
for c in cor:
pen.goto(c)
else:
for c in cor[::-1]: # first go backards, then forward to avoid fill
pen.goto(c)
for c in cor:
pen.goto(c)
pen.end_poly()
wn.register_shape("mypen", pen.get_poly())
wn.clear()
example = turtle.Turtle('mypen')
Example with fill:
Run the above code.
Draw a shape.
Press f
Example without fill:
Run the above code.
Draw a shape.
Press n
Is there a built-in method or a more efficient way to keep the
polies empty?
I'm not aware of any functionality to avoid closed polygons on turtle cursors -- your solution is clever! Below is my rework of your code to simplify this approach:
from turtle import Screen, Turtle
def drag(x, y):
''' function to draw with the pen '''
turtle.goto(x, y)
done = False
filled = True
def fill():
''' function to break out of while loop '''
global done
done = True
def nofill():
''' function to break out of while loop and set fill to False '''
global done, filled
done = True
filled = False
screen = Screen()
screen.tracer(False)
screen.onkeypress(fill, 'f')
screen.onkeypress(nofill, 'n')
screen.listen()
turtle = Turtle('circle')
turtle.shapesize(0.1)
turtle.ondrag(drag)
turtle.begin_poly()
while not done:
screen.update()
turtle.end_poly()
screen.clear()
polygon = turtle.get_poly()
if not filled:
polygon = (*polygon, *polygon[::-1])
screen.register_shape("mypen", polygon)
example = Turtle('mypen')
screen.tracer(True)
screen.mainloop()
The problem with my current method is that the more lines
(parameter) the lines there are for each turtle, the more stucky
the program runs
Unfortunately, the above code doesn't reduce the extra/excessive lines problem nor the general stuckiness of it (whatever stucky means, #Nick.)

how to upside down a text in python turtle.write() method?

nowadays I'm writing a program to fetch 4 poker cards from 52 poker cards randomly and I have to draw these pokers by python turtle module. Now here's my question: cause there's an upside-down number in pokers, just like this(the bottom right corner number)
at first I want to use this code to generate the numbers:
import turtle as do
def generate_digital(number, x, y, start_angle, size):
'''
this function generate '2-10'
parameters:
number: this is number you want to write
x and y: this is the pen's initial location
start_angle: the pen's initial direction
size: the number's size
'''
do.penup()
do.goto(x, y)
do.pensize(30)
do.setheading(start_angle)
do.write(number, font=("Arial", size, "normal"))
I want to use
do.settheading() to set the angle of the number, but I found that it didn't work! I can get a 5 but I can't get a upside-down 5 using the do.write() method......
Now, the only way myself can think of is to use this
def generate_photo_2(x, y, start_angle, size):
'''
this function generate a '2'
parameters:
just like last function
'''
do.penup()
do.goto(x, y)
do.pensize(3)
do.setheading(start_angle)
do.pendown()
do.circle(-size, 200)
do.fd(2 * size)
do.left(45)
do.fd(0.6 * size)
do.left(90)
do.fd(2 * size)
code to 'draw' a number, and by setting the start angle, I can 'draw' a upside-side number 2, but it causes a lot of trouble, isn't it?
Could anybody tells me how to write() a upside-down number?
Thank you very much!!!
turtle doesn't have function to display text upside down.
But turtle is built on top of tkinter module and Canvas widget which has method
create_text(x, y, text=.., angle=..., ...)
Working example
import turtle
c = turtle.getcanvas()
item_id = c.create_text(0, 0, text='5', angle=180, font=("Arial", 30, "normal"))
turtle.mainloop() # run tkinter event loop
Later you can change angle using item_id
c.itemconfig(item_id, angle=45)
Effbot.org: Canvas in tkinter.
BTW: I found information that only the newest tkinter with Tk 8.6 has angle=.
You can check version
import tkinter
print(tkinter.TkVersion)

Using turtle in Python to draw six-pointed stars with different side lengths

Hopefully I'll be able to explain this well. I'm currently using helper functions to draw a six-pointed star in the turtle graphics window of python. First, we had to create a function to draw a triangle. Here is my code:
import turtle
wn = turtle.Screen()
tess = turtle.Turtle()
tess.speed(30)
def triangle(sz):
for i in range(3):
tess.fd(sz)
tess.lt(120)
Then, we had to use the triangle function to draw a six-pointed star. Here is my code:
def sixPtdStar(sz):
triangle(sz)
tess.lt(90)
tess.pu()
tess.fd(80)
tess.rt(90)
tess.fd(120)
tess.pd()
tess.rt(180)
triangle(sz)
Now, for me, this all runs smoothly. But the parameters for our test run of those two functions was that sz = 120 (so in the shell we'd type sixPtdStar(120) and it would run. But then we had to draw a row of stars with a new function, and then a BOX outline by those rows of stars, in another function. Here is my code:
def rowOfStars(numInRow,sz):
for i in range(numInRow):
sixPtdStar(sz)
tess.pu()
tess.lt(90)
tess.fd(80)
tess.lt(90)
def sqrOfRows(numInRow, sz):
for i in range(4):
rowOfStars(numInRow, sz)
tess.rt(90)
While this accomplishes the task, it only does so if the sz = 120. And for our test run on the rowOfStars function, the parameters are supposed to be (6, 72) and for the test run on the sqrOfRows function, our parameters are supposed to be (6, 36).
So my issue is this. How can I make this work no matter what sz equals? When I run it as is (with (6, 72) for rowOfStars or (6, 36) for sqrOfRows), the pen moves too far because the triangles aren't as big anymore.
Please let me know if more info is needed! Thanks! (I'm using Python 3.5.2)
Anywhere you use a unit that has a dimension:
tess.fd(80)
tess.fd(120) # probably should be tess.fd(sz)
tess.fd(80)
you need to scale it by what ever logic you used to get from 120 (sz) to 80. However, as #wptreanor mentioned, that logic is slightly flawed as the points on your star are uneven:
Also, your rowOfStars() routine doesn't really draw a row of stars (math is off and the pen is in the wrong state at times.) Simply fixing the scaling won't fix this. Finally, your sqrOfRows() routine won't work until rowOfStars() is fixed, and to make it useful, you need to adjust the starting position on the screen to make room for the drawing.
Below is my rework of your code to address some of these issues. It uses a slightly different calculation of how to position from finishing the lower to starting the upper triangle so the numbers are slightly different:
from turtle import Turtle, Screen
WIDTH_RATIO = 2 * 3**0.5 / 3 # ratio of widest point in star to edge of triangle
def triangle(size):
for i in range(3):
tess.fd(size)
tess.lt(120)
def sixPtdStar(size):
triangle(size)
tess.lt(30)
tess.pu()
tess.fd(size * WIDTH_RATIO)
tess.lt(150)
tess.pd()
triangle(size)
def rowOfStars(numInRow, size):
for i in range(numInRow):
sixPtdStar(size)
tess.pu()
tess.lt(90)
tess.fd(size * WIDTH_RATIO / 2)
tess.lt(90)
tess.pd()
def sqrOfRows(numInRow, size):
tess.pu()
halfSize = numInRow * size / 2
tess.goto(-halfSize, halfSize) # center on screen
tess.pd()
for i in range(4):
rowOfStars(numInRow, size)
tess.rt(90)
screen = Screen()
tess = Turtle()
tess.speed("fastest") # numbers > 10 are all equivalent, safer to use symbols
sqrOfRows(6, 36)
screen.exitonclick()
The problem is in your sixPtdStar() function.
def sixPtdStar(sz):
triangle(sz)
tess.lt(90)
tess.pu()
tess.fd(80) # here
tess.rt(90)
tess.fd(120) # and here
tess.pd()
tess.rt(180)
triangle(sz)
If your function takes a size as a parameter, all functions involving movement (such as forward() or goto()) need to be scaled by the size as well. The following code should work:
def sixPtdStar(sz):
triangle(sz)
tess.lt(90)
tess.pu()
tess.fd((2.0/3.0)*sz) #formerly 80
tess.rt(90)
tess.fd(sz) #formerly 120
tess.pd()
tess.rt(180)
triangle(sz)
This will ensure that all forward movements are proportional to the size of the object you create. You will need to make similar tweaks to your rowOfStars() function. I've also noticed that your six pointed star isn't fully symmetrical. You could resolve that by replacing tess.fd((2.0/3.0)*sz) with tess.fd((7.0/12.0)*sz).

Categories

Resources