Turtle threaded parallel drawing - python

I am currently trying to make a sierpinski triangle which uses a recursive function to create threads that draw individual triangles simultaneously using the turtle library(cant change that). The thing is it keeps telling me that RuntimeError: main thread is not in main loop.
Here is the code I use for thread creating and execution, as well as some of my attempts at fixing it
def triangle_thread(x, y, size, invert=False):
global turtles
turtles.append(turtle.Turtle("turtle"))
turtles[-1].speed("fastest")
t = threading.Thread(target=partial(triangle, x, y, size, turtles[-1], invert=invert))
t.daemon = True
t.start()
This is supposed to create and start a new thread that draws a triangle
It apparently works.
I tried multiple things, as well as some queue trickery but it wouldnt draw them simultaneously.
here is my latest attempt at fixing it :
thread_sierpinski_recursive = threading.Thread(target=partial(sierpinski, -700, -500, 3, 1000))
thread_sierpinski_recursive.start()
turtle.mainloop()
I tried to run the entire sierpinski triangle generation in a separate thread so the main thread would be running turtle.mainloop
this works with sierpinski with up to 4 generations, but as soon as you try more it returns the same error:
sierpinski(-700, -500, 3, 1000)
turtle.mainloop()
here is the whole code I use, after trying cdlane's solution :
import turtle
import math
from functools import partial
import threading
threads = []
turtle.speed("fastest")
def triangle(x, y, size, t, invert=False):
y_offset = size*(math.sqrt(3)/2)
t.hideturtle()
t.penup()
partial(t.goto, x, y)()
t.pendown()
partial(t.goto, x+size, y)()
partial(t.goto, x+size/2, y-y_offset if invert else y+y_offset)()
partial(t.goto, x, y)()
def inner_sierpinski(x, y, size, iteration, thread_in):
global threads
height = size * (math.sqrt(3) / 2)
triangle(x + (size / 2), y + height, size, thread_in.Turtle, invert=True)
if iteration > 3:
for nx, ny in ((x, y), (x+(size/2), y+height), (x+size, y)):
threads.append(threading.Thread(target=partial(sierpinski, x, y, iteration-1, size, is_first=False)))
threads[-1].Turtle = turtle.Turtle("turtle", visible=False)
threads[-1].Turtle.speed("fastest")
elif iteration > 1:
for nx, ny in ((x, y), (x+(size/2), y+height), (x+size, y)):
sierpinski(nx, ny, iteration-1, size, is_first=False)
#this exists for the sole purpose of drawing the outer triangle
#this function calls inner_sierpinski, whoch calls this thrice.
def sierpinski(x, y, iterations, total_size, is_first=True):
global threads
if is_first:
triangle(x, y, total_size, turtle)
threading.main_thread().Turtle = turtle
thread_out = threading.main_thread()
else:
thread_out = threads[-1]
inner_sierpinski(x, y, total_size/2, iterations, thread_out)
sierpinski(-700, -500, 6, 1000)
for thread in threads:
thread.start()
turtle.mainloop()

I would need to see more of your code to understand specifically, but generally, drawing an individual triangle seems too small a step to thread as the overhead overwhelms the process. If each triangle was a complex calculation that took significant time, that would be fine. Similarly, if you break up your Sierpinski triangle into large sections that could be drawn in parallel, that would also seem reasonable.
Below is code I just pulled together from my other examples that uses threading to recursively draw the three large components of a Koch snowflake in parallel:
from queue import Queue
from functools import partial
from turtle import Screen, Turtle
from threading import Thread, active_count
STEPS = 3
LENGTH = 300
def koch_curve(turtle, steps, length):
if steps == 0:
actions.put((turtle.forward, length))
else:
for angle in [60, -120, 60, 0]:
koch_curve(turtle, steps - 1, length / 3)
actions.put((turtle.left, angle))
def process_queue():
while not actions.empty():
action, *arguments = actions.get()
action(*arguments)
if active_count() > 1:
screen.ontimer(process_queue, 1)
screen = Screen()
actions = Queue(1) # size = number of hardware threads you have - 1
turtle1 = Turtle('turtle')
turtle1.hideturtle()
turtle1.speed('fastest')
turtle1.color('red')
turtle1.penup()
turtle1.right(30)
turtle1.backward(3**0.5 * LENGTH / 3)
turtle1.left(30)
turtle1.pendown()
turtle2 = turtle1.clone()
turtle2.color('green')
turtle2.penup()
turtle2.forward(LENGTH)
turtle2.right(120)
turtle2.pendown()
turtle3 = turtle1.clone()
turtle3.speed('fastest')
turtle3.color('blue')
turtle3.penup()
turtle3.right(240)
turtle3.backward(LENGTH)
turtle3.pendown()
thread1 = Thread(target=partial(koch_curve, turtle1, STEPS, LENGTH))
thread1.daemon = True # thread dies when main thread (only non-daemon thread) exits.
thread1.start()
thread2 = Thread(target=partial(koch_curve, turtle2, STEPS, LENGTH))
thread2.daemon = True
thread2.start()
thread3 = Thread(target=partial(koch_curve, turtle3, STEPS, LENGTH))
thread3.daemon = True
thread3.start()
process_queue()
screen.exitonclick()
Since the amount of drawing is the same (and the limiting factor on the speed) I don't expect to gain any performance over simply drawing the fractal the usual way with the turtle speed turned up or tracing turned off. It's just for visual effect.
1/16 Update: Reviewing your attempt to incorporate my example into your code it's clear you don't understand what the role of partial() is, nor, for that matter, the role of the global statement in Python. And you're missing a key concept: all turtle graphics commands must happen in the main thread, not any of the ones you create. (I.e. turtle's underpinning code is not thread safe.)
Let's begin by turning your code back into a non-threaded implemenation of the Sierpinski triangle. But with a slight change in that we will make the triangles on the corners of the top level triangle three different colors, to show we know when those major segments begin:
from turtle import Screen, Turtle
def triangle(x, y, size, turtle, invert=False):
y_offset = size * 3**0.5/2
turtle.penup()
turtle.goto(x, y)
turtle.pendown()
turtle.goto(x + size, y)
turtle.goto(x + size/2, y + (-y_offset if invert else y_offset))
turtle.goto(x, y)
def inner_sierpinski(x, y, size, turtle, iterations, iteration):
height = size * 3**0.5/2
triangle(x + size/2, y + height, size, turtle, invert=True)
if iteration:
for nx, ny in ((x, y), (x + size/2, y + height), (x + size, y)):
sierpinski(nx, ny, size, turtle, iterations, iteration-1)
def sierpinski(x, y, size, turtle, iterations, iteration=-1):
if iteration < 0:
triangle(x, y, size, turtle)
iteration = iterations
elif iteration == iterations - 1:
turtle.color(colors.pop())
inner_sierpinski(x, y, size/2, turtle, iterations, iteration)
colors = ['black', 'red', 'green', 'blue']
screen = Screen()
turtle = Turtle(visible=False)
turtle.speed('fastest')
turtle.color(colors.pop(0))
sierpinski(-350, -250, 700, turtle, 6)
screen.exitonclick()
Having done that, let's now incorporate the threading from my Koch snowflake example:
from queue import Queue
from functools import partial
from turtle import Screen, Turtle
from threading import Thread, active_count
def triangle(x, y, size, turtle, invert=False):
y_offset = size * 3**0.5/2
actions.put((turtle.penup,))
actions.put((turtle.goto, x, y))
actions.put((turtle.pendown,))
actions.put((turtle.goto, x + size, y))
actions.put((turtle.goto, x + size/2, y + (-y_offset if invert else y_offset)))
actions.put((turtle.goto, x, y))
def inner_sierpinski(x, y, size, turtle, iterations, iteration):
# Surprisingly, this doesn't change from my
# previous example so just copy it over!
def sierpinski(x, y, size, turtle, iterations, iteration=-1):
if iteration < 0:
triangle(x, y, size, turtle)
iteration = iterations
if iteration == iterations - 1:
turtle = turtles.get()
thread = Thread(target=partial(inner_sierpinski, x, y, size/2, turtle, iterations, iteration))
thread.daemon = True
thread.start()
else:
inner_sierpinski(x, y, size/2, turtle, iterations, iteration)
def process_queue():
# Copy this function from my Koch snowflake example
COLORS = ['black', 'red', 'green', 'blue']
turtles = Queue(len(COLORS))
screen = Screen()
for color in COLORS:
turtle = Turtle(visible=False)
turtle.speed('fastest')
turtle.color(color)
turtles.put(turtle)
actions = Queue(1)
thread = Thread(target=partial(sierpinski, -350, -250, 700, turtles.get(), 6)) # must be a thread as it calls triangle()
thread.daemon = True # thread dies when main thread (only non-daemon thread) exits.
thread.start()
process_queue()
screen.exitonclick()
When this runs, you should see the black outer triangle created followed by independent threads working on the three color triangles:

Related

Trying to make two turtles move randomly in a square and saying when they're close to each other

So my assignement is to:
make a blue rectangle
write a function that makes the turle move in a random direction within an interval of 90 degrees and move forward in a random interval of 0-25
create a blue square
Move the turle to a random point in the square
Code so the turtle moves back inside the square if it leaves it
Create an additonal turle (both should have different colors)
Use the same statement to move both turtles (with the move_random function) 500 times
if the turtles are closer than 50 units - print a string that counts the number of times they are 50 units close.
This is what it should look like:
enter image description here
I've added some comments to explain my thought process
Any and all help is appreciated
The code:
EDIT: fixed the indentations, now i get the error message on the last line that the name "meet" is not defined. Also if i run the code without the last line which is supposed to print the amount of close encounters, nothing happens, no errors, but no turtles either.
import turtle
import random
#makes the jump function
def jump(t, x, y):
t.penup()
t.goto(x, y)
t.pendown()
#creares a turtle at a defined place
def make_turtle(x, y):
t = turtle.Turtle()
jump(t, x, y) # Use of the function defined above
return t
#function to create a rectangle and fill it with a color
def rectangle(x, y, width, height, color):
t = make_turtle(x, y)
t.speed(0)
t.hideturtle()
t.fillcolor(color)
t.begin_fill()
for dist in [width, height, width, height]:
t.forward(dist)
t.left(90)
t.end_fill()
#function to move turtle in a random heading (90 degree interval) between 0--25 units forward
#While also making it turn around if it is outside of the square
def move_random(t):
if abs(t.pos()[0]) >= 250 or abs(t.pos()[1]) >= 250:
target = (0, 0)
d = (0,0)
t.setheading(d)
else:
ini = t.heading()
new = rd.randint(ini - 45, ini + 45)
t.setheading(new)
t.forward(rd.randint(0, 25))
#creates the square and both turtles
t = make_turtle(0 , 0)
t.color("green")
t2 = make_turtle(0 , 0)
t2.color("black")
rectangle(-250, -250, 500, 500, "lightblue")
jump(t, rd.randint(-250, 250), rd.randint(-250, 250))
jump(t2, rd.randint(-250, 250), rd.randint(-250, 250)) #jumps the turles randomly in the square
meet = 0
for i in range(1, 501): #makes the turtles move randomly as specified above
move_random(t)
move_random(t2)
if t.distance(t2) < 50:
t.write("close")
meet += 1
print(str(meet), "close encounter") #prints the amount of times they are close to each other
if abs(t.pos()[0]) >= 250 or abs(t.pos()[1]) >= 250:
target = (0, 0)
d = (0,0)
t.setheading(d)
else:
See that function before the "else:"? You missed a tab there.

How can i increase the speed of this pixel game engine's drawing speed?

I was wondering what I could do to increase the overall speed of changing individual values in a NumPy array?
class Draw:
def __init__(self, game, pygame):
self.pygame = pygame
self.game = game
self.display = np.array([[random_color() for x in range(self.game.settings.PIX_ARRAY_HEIGHT)] for y in range(self.game.settings.PIX_ARRAY_WIDTH)])
def draw_screen(self):
self.pygame.surfarray.make_surface(self.display)
surface = self.pygame.surfarray.make_surface(self.display)
surface = self.pygame.transform.scale(surface, (self.game.settings.SCREEN_WIDTH, self.game.settings.SCREEN_HEIGHT))
self.game.screen.blit(surface, (0, 0))
self.pygame.display.update()
def pixel(self, x, y, color, alpha=1):
if x >= 0 and x < self.game.settings.PIX_ARRAY_WIDTH and y >= 0 and y < self.game.settings.PIX_ARRAY_HEIGHT:
self.display[int(x), int(y)] = color if alpha == 1 else self.mix_pixels(color, self.display[int(x), int(y)], alpha)
def rect(self, x, y, w, h, color):
threads = []
for i in range(x, x + w):
for j in range(y, y + h):
x = threading.Thread(target=self.pixel, args=(i,j,color,))
threads.append(x)
x.start()
I tried threading to increase the speed of overwriting pixels in the NumPy array but that seemed to decrease the overall speed.
I have tried to use numbas's njit and jit tool to pre-compile the code, but it refused to do so.
when a 100 x 100 rect is drawn the fps drops from 160+ to just over 3 without any threading
with threading it goes to about 0.13 fps.
after changing the drawing technique I am using this code:
#njit(parallel=True, fastmath=True)
def blit(image1, image2, x, y):
for i in prange(len(image2)):
for j in prange(len(image2[0])):
try:
image1[int(i + x), int(j + y)] = image2[i, j]
except:
print(i, j)
the code is pre-compiled and runs on the GPU so is much much faster than it would be otherwise.
any suggestions to make it faster are highly sought after but I am happy with the results of over a constant 120 fps.

Scaling a turtle drawing python

I am trying to to write a python script that take n and draw a Hilbert curve based on that order. my algorithm work fine it draws the the curve and rescale the size when changing the window size. however, my drawing is not centred and can go out of bound. I would like to scale the curve with the screen without having much empty space or have it going out of bound
here is my code:
import sys
import turtle
from turtle import Turtle, Screen
#Drawing the hilbert curve using recursion.
#Var: turtle if for the Turtle, A is the length of the lines, parity is for inverting the direction, and n is for the order
def hilbert_curve(turtle, A, parity, n):
if n < 1:
return
turtle.left(parity * 90)
hilbert_curve(turtle, A, - parity, n - 1)
turtle.forward(A)
turtle.right(parity * 90)
hilbert_curve(turtle, A, parity, n - 1)
turtle.forward(A)
hilbert_curve(turtle, A, parity, n - 1)
turtle.right(parity * 90)
turtle.forward(A)
hilbert_curve(turtle, A, - parity, n - 1)
turtle.left(parity * 90)
def main():
#Rescale the drawing when changing the window size
def onResize(x=0, y=0):
width = my_win.window_width()
hight = my_win.window_height()
my_win.setworldcoordinates(-width-1, -hight-1, width-1, hight-1)
my_win.ontimer(onResize,100)
#initilize the turtle.
turtle = Turtle()
#initilize the screen.
my_win = Screen()
w = my_win.window_width()
h = my_win.window_height()
A = 20
onResize()
rule = 1
my_win.tracer(False)
if len(sys.argv) < 2:
print("Please declare the order after calling the program name")
return
n = int(sys.argv[1])
hilbert_curve(turtle,A,rule,n)
my_win.update()
my_win.mainloop()
main()
**I would appreciate it if someone can fix my problem thank you **
my algorithm work fine it draws the the curve and rescale the size
when changing the window size.
No it doesn't. Your drawing is never scaled, it remains the same size. And your main() function which sets up the scaling code is never called as it follows the mainloop() call which turns control over to the tkinter event handler:
my_win.mainloop()
main()
Using an event timer is the wrong way to go about this problem. However, since turtle doesn't expose the underlying tkinter window resize event, let's play along with this model, rather than dropping down to the tkinter layer. I would do it this way instead:
from turtle import Turtle, Screen
def hilbert_curve(turtle, A, parity, n):
'''
Draw the hilbert curve using recursion.
Arguments:
turtle is for the Turtle,
A is the length of the lines,
parity is for inverting the direction,
and n is for the order
'''
if n < 1:
return
turtle.left(parity * 90)
hilbert_curve(turtle, A, - parity, n - 1)
turtle.forward(A)
turtle.right(parity * 90)
hilbert_curve(turtle, A, parity, n - 1)
turtle.forward(A)
hilbert_curve(turtle, A, parity, n - 1)
turtle.right(parity * 90)
turtle.forward(A)
hilbert_curve(turtle, A, - parity, n - 1)
turtle.left(parity * 90)
def main():
order = 4
parity = 1
length = 100 / (4 * order - 1)
def onResize():
# Rescale drawing when changing window size (the hard way)
turtle.reset()
screen.setworldcoordinates(0, 0, 100, 100)
hilbert_curve(turtle, length, parity, order)
screen.update()
screen.ontimer(onResize, 1000)
screen = Screen()
screen.tracer(False)
turtle = Turtle()
onResize()
screen.mainloop()
main()
I.e. maintain constant virtual coordinates regardless of window size and redraw the curve to fit the current window size. BTW, didn't I write this Hilbert curve code? Make sure to upvote where you got it from!

How to do animation using Zelle graphics module?

I need help to design my graphics, without turtle nor tkinter, but with Zelle graphics.py. The problem is that I need to run 4 circles moving at the same time. Here's the code I have so far:
from graphics import *
import time #import time module
from random import randrange
def rand_color():#generates a random color and returns that color
return(color_rgb(randrange(256),randrange(256),randrange(256)))
def main():
win = GraphWin("My Circle",500,500)
c = Circle(Point(20,20),20)
c.setFill(rand_color())
c.draw(win)
for i in range(1,461):
c.move(1,1)
time.sleep(.005)
c = Circle(Point(20,20),20)
c.setFill(rand_color())
c.draw(win)
for i in range(1,461):
c.move(-1,1)
time.sleep(.005)
c = Circle(Point(20,20),20)
c.setFill(rand_color())
c.draw(win)
for i in range(1,461):
c.move(1,-1)
time.sleep(.005)
c = Circle(Point(20,20),20)
c.setFill(rand_color())
c.draw(win)
for i in range(1,461):
c.move(1,1)
time.sleep(.005)
main()
I don't know how to move multiple objects at once. How would one go about this?
Rather move each circle completely in turn, chop up the movements and alternate them so each circle moves a little at a time in round robin. I'm guessing this is close to what you're trying to do:
from random import randrange
from graphics import *
def rand_color():
""" Generate a random color and return it. """
return color_rgb(randrange(256), randrange(256), randrange(256))
win = GraphWin("My Circle", 500, 500)
circles = []
for x in [-1, 1]:
for y in [-1, 1]:
circle = Circle(Point(250, 250), 20)
circle.setFill(rand_color())
circle.draw(win)
circles.append((circle, (x, y)))
for _ in range(250):
for circle, (x, y) in circles:
circle.move(x, y)
win.getMouse() # Pause to view result
win.close() # Close window when done

How do I make a dot disappear after being eaten?

I am attempting to program a python game using turtle-graphics, and I've run into some obstacles. When I run my code it allows me to direct the turtle around, then a whole bunch of dots start appearing, and then it has recursion depth error.
The section of code that I am having issues with is this:
def move():
colormode(255)
global turtle
global moving
x = randomColor()
if moving:
for i in range(1):
turtle.penup()
turtle.shape('turtle')
turtle.shapesize(.5, .5, .5)
turtle.color(x)
turtle.forward(5)
ontimer(move, 10 // FRAMES_PER_SECOND)
x = randrange(-250, 250)
y = randrange(-250, 250)
pen1 = Pen()
pen1.hideturtle()
pen1.penup()
pen1.goto(x, y)
pen1.dot(10, "red")
if turtle.pos() == pen1.pos():
pen1.clear()
pen1.goto(x, y)
How can I fix this? I want the dot to disappear when the turtle goes over it, and then a new random dot to generate, only one dot at a time.
you should:
Draw red point once on start and save the coordination.
After each move check the turtle position with your last_point (x,y).
If same, draw a dot in the same position with color of background(white I think).
3.1. Then create new random x,y and redraw the red dot.
The way I'd go about this is to define a second turtle with the shape and color of the food. Then stamp that turtle to generate food, keeping the result of the stamp() call so you can call clearstamp(stamp) on it later. I've set such up below with lots of food which the turtle chases down until they're all gone:
from turtle import Turtle, Screen
from random import randrange
WIDTH, HEIGHT = 500, 500
FRAMES_PER_SECOND = 24
TARGET_SIZE = 10
POSITION, STAMP = 0, 1
def move():
global meal
x, y = meal[POSITION]
tx, ty = turtle.position()
if x - TARGET_SIZE // 2 < tx < x + TARGET_SIZE // 2 and y - TARGET_SIZE // 2 < ty < y + TARGET_SIZE // 2:
food.clearstamp(meal[STAMP])
if meals:
meal = meals.pop()
else:
meal = None
if meal:
turtle.setheading(turtle.towards(meal[POSITION]))
turtle.forward(turtle.speed())
screen.ontimer(move, 1000 // FRAMES_PER_SECOND)
screen = Screen()
screen.setup(int(WIDTH * 1.1), int(HEIGHT * 1.1)) # size window but leave a border
turtle = Turtle(shape='turtle')
turtle.speed("fast")
turtle.penup()
food = Turtle(shape="circle", visible=False)
food.shapesize(0.5, 0.5)
food.color("red")
food.penup()
meals = []
for _ in range(10):
x = randrange(-WIDTH // 2, WIDTH // 2)
y = randrange(-HEIGHT // 2, HEIGHT // 2)
food.goto(x, y)
meals.append(((x, y), food.stamp()))
meal = meals.pop()
screen.ontimer(move, 1000 // FRAMES_PER_SECOND)
screen.exitonclick()
This turtle just chase a random bit of food, you can modify the code to make him go after the nearest food (as you have the list of meals with their positions) or take control of the turtle yourself.
Do:
(name).up()
(name).forward(10000)

Categories

Resources