So I decided to model a differential equation in python turtle, but if the turtle is moving fast it starts to "cut", with the circle repeatedly being cutoff at points
I tried to make it smoother by using tracer(0, 0) and updating it only when I need to but that still doesn't work
from turtle import *
import time
from threading import Thread
import math
setup(500, 500)
bgcolor("black")
b = Turtle()
s = b.getscreen()
tracer(0, 0)
b.color("sky blue")
b.shape("circle")
b.up()
b.goto(0, -100)
s.update()
globals()['lastt'] = 0
globals()['auto'] = 0
globals()['angle'] = 0
globals()['speed'] = 0
globals()['accel'] = 0
globals()['air'] = .5
#Starting speed
def measurespeed():
while True:
time.sleep(.01)
while globals()['auto'] == 0:
past = globals()['angle']
time.sleep(.01)
globals()['speed'] = (angle - past)/.01
dspeed = Thread(target=measurespeed)
dspeed.start()
#Auto
def equation():
while True:
time.sleep(.01)
while globals()['auto'] == 1:
globals()['accel'] = -5 * math.sin(angle) - (air * speed)
globals()['speed'] += accel * .01
globals()['angle'] += speed * .01
time.sleep(.01)
b.goto(100 * math.cos(angle - (math.pi / 2)), 100 * math.sin(angle - (math.pi / 2)))
s.update()
move = Thread(target=equation)
move.start()
#Dragging
def drag(x, y):
if x == 0:
if y >= 0:
b.goto(0, 100)
else:
b.goto(0, -100)
elif x > 0:
globals()['angle'] = math.atan(y/x) + (math.pi / 2)
else:
globals()['angle'] = math.atan(y/x) + (3 * math.pi / 2)
b.goto(100 * math.cos(angle - (math.pi / 2)), 100 * math.sin(angle - (math.pi / 2)))
s.update()
def static(x, y):
globals()['auto'] = 0
globals()['speed'] = 0
def resume(x, y):
globals()['auto'] = 1
b.ondrag(drag)
b.onclick(static)
b.onrelease(resume)
s.mainloop()
Sorry for the lack of comments, this was supposed to be a quick experiment
I took a crack at this: I turned off the drag handler while inside the drag handler as this can cause problems including faux recursion stack overflow.
Other changes not directly affecting function include using standard Python global notation and other style stuff like parentheses reduction:
from turtle import Screen, Turtle
from threading import Thread
from time import sleep
import math
AIR = 0.5
auto = False
angle = 0
speed = 0
accel = 0
# Starting speed
def measurespeed():
global speed
while True:
sleep(0.01)
while not auto:
past = angle
sleep(0.01)
speed = (angle - past) / 0.01
# Auto
def equation():
global accel, angle, speed
while True:
sleep(0.01)
while auto:
accel = -5 * math.sin(angle) - AIR * speed
speed += accel * 0.01
angle += speed * 0.01
sleep(0.01)
turtle.goto(100 * math.cos(angle - math.pi/2), 100 * math.sin(angle - math.pi/2))
screen.update()
# Dragging
def drag(x, y):
global angle
turtle.ondrag(None) # disable handler inside handler
if x == 0:
if y >= 0:
turtle.goto(0, 100)
else:
turtle.goto(0, -100)
elif x > 0:
angle = math.atan(y / x) + math.pi/2
else:
angle = math.atan(y / x) + 3 * math.pi/2
turtle.goto(100 * math.cos(angle - math.pi/2), 100 * math.sin(angle - math.pi/2))
screen.update()
turtle.ondrag(drag) # reenable handler
def static(x, y):
global auto, speed
auto = False
speed = 0
def resume(x, y):
global auto
auto = True
screen = Screen()
screen.setup(500, 500)
screen.bgcolor('black')
screen.tracer(False)
turtle = Turtle()
turtle.shape('circle')
turtle.color("sky blue")
turtle.penup()
turtle.sety(-100)
screen.update()
dspeed = Thread(target=measurespeed)
dspeed.start()
move = Thread(target=equation)
move.start()
turtle.ondrag(drag)
turtle.onclick(static)
turtle.onrelease(resume)
screen.mainloop()
Another issue to consider is that traditionally you could only do turtle graphic operations like goto() from the main thread. However, this seems to work better in the latest Python and latest tkinter. If you're still having issues, make sure to upgrade to current software. If that doesn't help, search for some of the turtle and threading questions on SO.
In the context of a drag, the click and release events appear to work fine, but on their own they don't work the way you'd expect -- both are invoked on each down and each up of the button.
Related
I am writing a program for an exercise that simulates a projectile launch while tracking down every x and y position per second.
While the program technically works, and it does track down every x and y position, the text that tracks down the x and y position flickers for each step.
I understand the problem is that I am using the .clear() function (which, as its name implies, clears the text generated by the turtle). However, I believe that the program only functions properly with it.
Here is my code:
"""Program to track specific location of turtle (for every step)"""
from turtle import *
from math import *
def cball_graphics():
leonardo = Turtle()
leonardo.color("dark blue")
leonardo.shape("turtle")
leonardo.speed(1)
return leonardo
def show_position():
pos = Turtle()
pos.color("white")
pos.goto(30, -50)
pos.color("red")
return pos
class cannon_ball:
def __init__(self, angle, vel, height, time):
self.x_pos = 0
self.y_pos = height
theta = pi * angle / 180
self.x_vel = vel * cos(theta)
self.y_vel = vel * sin(theta)
self.time = time
def update_time(self):
self.x_pos += self.time * self.x_vel
y_vel1 = self.y_vel - 9.8 * self.time
self.y_pos += self.time * (self.y_vel + y_vel1) / 2
self.y_vel = y_vel1
def get_x(self):
return self.x_pos
def get_y(self):
return self.y_pos
def variables():
angle = 55
vel = 10
height = 100
time = .01
return cannon_ball(angle, vel, height, time)
def main():
leonardo = cball_graphics()
"""pos is a variable that writes the position on the screen using x and y pos"""
pos = show_position()
pos.hideturtle()
projectile = variables()
while projectile.y_pos >= 0:
pos.write(f"{'%0.0f' % projectile.x_pos}, {'%0.0f' % projectile.y_pos}")
projectile.update_time()
leonardo.goto(projectile.x_pos, projectile.y_pos)
pos.clear()
main()
I believe that the tracer() and update() methods of the turtle screen will solve your problem. They allow you to write to the backing store and then copy it to the screen once it's the way you want. Particularly useful for eliminating flicker:
from turtle import Screen, Turtle
from math import pi, sin, cos
FONT = ('Arial', 18, 'normal')
def cball_graphics():
leonardo = Turtle()
leonardo.color('dark blue')
leonardo.shape('turtle')
return leonardo
def show_position():
pos = Turtle()
pos.hideturtle()
pos.goto(30, -50)
pos.color('red')
return pos
class cannon_ball:
def __init__(self, angle, velocity, height, time):
self.x_pos = 0
self.y_pos = height
theta = pi * angle / 180
self.x_vel = velocity * cos(theta)
self.y_vel = velocity * sin(theta)
self.time = time
def update_time(self):
self.x_pos += self.time * self.x_vel
y_vel1 = self.y_vel - 9.8 * self.time
self.y_pos += self.time * (self.y_vel + y_vel1) / 2
self.y_vel = y_vel1
def freefall():
if projectile.y_pos >= 0:
pos.clear()
pos.write("({:0.0f}, {:0.0f})".format(projectile.x_pos, projectile.y_pos), font=FONT)
projectile.update_time()
leonardo.goto(projectile.x_pos, projectile.y_pos)
screen.update()
screen.ontimer(freefall)
variables = {
'angle': 55,
'velocity': 10,
'height': 100,
'time': 0.01,
}
screen = Screen()
screen.tracer(False)
leonardo = cball_graphics()
# pos is a turtle that writes the position on the screen using x and y pos
pos = show_position()
projectile = cannon_ball(**variables)
freefall()
screen.mainloop()
I also made several style changes to the code while I was at it...
I am creating a program in Python using Zelle graphics package. There is a moving circle that the user clicks on in order to make it return to the center of the screen. I cannot figure out how to identify when the user clicks inside of the circle. Here is the code I have written:
from graphics import *
from time import sleep
import random
Screen = GraphWin("BallFalling", 400 , 400);
Screen.setBackground('green')
ball = Circle(Point(200,200),25);
ball.draw(Screen);
ball.setFill('white')
ballRadius = ball.getRadius()
ballCenter = 0
directionX = (random.random()*40)-20;
directionY = (random.random()*40)-20;
clickx = Screen.getMouse().getX();
clicky = 0
while ball.getCenter().getX() + ball.getRadius() <= 400 and ball.getCenter().getY() + ball.getRadius() <= 400 and ball.getCenter().getX() >= 0 and ball.getCenter().getY() >= 0:
ball.move(ball.getRadius()//directionX,ball.getRadius()//directionY)
ballLocation = ball.getCenter().getX();
ballLocationy = ball.getCenter().getY();
sleep(1/15);
The main problem I am having is identifying the coordinates of the mouse click. I cannot find anything in the Zelle graphics package that says anything about this.
The major issues I see are: you are calling getMouse() before the loop when you should be calling checkMouse() inside the loop; you have no code to compare the distance of the click from the ball; you have no code to return the ball to the center of the screen.
Below is my complete rework of your code addressing the above issues:
from time import sleep
from random import randrange
from graphics import *
WIDTH, HEIGHT = 400, 400
RADIUS = 25
def distance(graphic, point):
x1, y1 = graphic.getCenter().getX(), graphic.getCenter().getY()
x2, y2 = point.getX(), point.getY()
return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** 0.5
window = GraphWin("Ball Falling", WIDTH, HEIGHT)
window.setBackground('green')
ball = Circle(Point(WIDTH/2, HEIGHT/2), RADIUS)
ball.setFill('white')
ball.draw(window)
directionX = randrange(-4, 4)
directionY = randrange(-4, 4)
while True:
center = ball.getCenter()
x, y = center.getX(), center.getY()
if not (RADIUS < x < WIDTH - RADIUS and RADIUS < y < HEIGHT - RADIUS):
break
point = window.checkMouse()
if point and distance(ball, point) <= RADIUS:
ball.move(WIDTH/2 - x, HEIGHT/2 - y) # recenter
directionX = randrange(-4, 4)
directionY = randrange(-4, 4)
else:
ball.move(directionX, directionY)
sleep(1/30)
There may still be subtle bugs and contants tweaking for you to sort out.
So I've been attempting to make some dots not only come towards a circle but also to make them orbit it. To do this I am using cosine and sine, however I'm running into issues with getting the dots to move forward as well as setting their distance. With the code below the dots are able to form a circle around the bigger dot, as well as follow it, but they don't approach the dot nor do they, when having the coordinates scaled by their distance from t1, come to that location, but instead do funky stuff. This is referring specifically to the line
t2.goto(2 * (t1.xcor() + math.degrees(math.cos(math.radians(t1.towards(t2)))) // 1), 2 * (t1.ycor() + math.degrees(math.sin(math.radians(t1.towards(t2)))) // 1))
which I had replaced with:
t2.goto(dist * (t1.xcor() + math.degrees(math.cos(math.radians(t1.towards(t2)))) // 1), dist * (t1.ycor() + math.degrees(math.sin(math.radians(t1.towards(t2)))) // 1))
and that gave me the sporadic view of the dots attempting to follow the bigger dot.
This line is found in the follow() function. Create() makes the smaller dots, move() moves the bigger dot and grow() grows the bigger dot on collision with the smaller dots. Produce() and redraw() are supposed to be a stage 2 of the program, but those functions are irrelevant to the question. Finally, quit() just exits the Screen() and quits the program.
Thanks to cdlane for help with organizing data and updating the screen more efficiently.
Code as of now:
from turtle import Turtle, Screen
import sys
import math
CURSOR_SIZE = 20
def move(x, y):
""" has it follow cursor """
t1.ondrag(None)
t1.goto(x, y)
screen.update()
t1.ondrag(move)
def grow():
""" grows t1 shape """
global t1_size, g
t1_size += 0.1
t1.shapesize(t1_size / CURSOR_SIZE)
g -= .1
t1.color((r/255, g/255, b/255))
screen.update()
def follow():
""" has create()'d dots follow t1 """
global circles, dist
new_circles = []
for (x, y), stamp in circles:
t2.clearstamp(stamp)
t2.goto(x, y)
dist = t2.distance(t1) / 57.29577951308232 // 1
t2.goto(2 * (t1.xcor() + math.degrees(math.cos(math.radians(t1.towards(t2)))) // 1), 2 * (t1.ycor() + math.degrees(math.sin(math.radians(t1.towards(t2)))) // 1))
t2.setheading(t2.towards(t1))
if t2.distance(t1) < t1_size // 1:
if t2.distance(t1) > t1_size * 1.2:
t2.forward(500/t2.distance(t1)//1)
else:
t2.forward(3)
if t2.distance(t1) > t1_size // 2:
new_circles.append((t2.position(), t2.stamp()))
else:
grow() # we ate one, make t1 fatter
screen.update()
circles = new_circles
if circles:
screen.ontimer(follow, 10)
else:
phase = 1
produce()
def create():
""" create()'s dots with t2 """
count = 0
nux, nuy = -400, 300
while nuy > -400:
t2.goto(nux, nuy)
if t2.distance(t1) > t1_size // 2:
circles.append((t2.position(), t2.stamp()))
nux += 20
count += 1
if count == 40:
nuy -= 50
nux = -400
count = 0
screen.update()
def quit():
screen.bye()
sys.exit(0)
def redraw():
t2.color("black")
t2.shapesize((t2_size + 4) / CURSOR_SIZE)
t2.stamp()
t2.shapesize((t2_size + 2) / CURSOR_SIZE)
t2.color("white")
t2.stamp()
def produce():
#create boundary of star
global t2_size, ironmax
t1.ondrag(None)
t1.ht()
t2.goto(t1.xcor(), t1.ycor())
t2.color("black")
t2.shapesize((t1_size + 4) / CURSOR_SIZE)
t2.stamp()
t2.shapesize((t1_size + 2) / CURSOR_SIZE)
t2.color("white")
t2.stamp()
#start producing helium
while t2_size < t1_size:
t2.color("#ffff00")
t2.shapesize(t2_size / 20)
t2.stamp()
t2_size += .1
redraw()
screen.update()
ironmax = t2_size
t2_size = 4
while t2_size < ironmax:
t2.shapesize(t2_size / 20)
t2.color("grey")
t2.stamp()
t2_size += .1
screen.update()
# variables
t1_size = 6
circles = []
phase = 0
screen = Screen()
screen.screensize(900, 900)
#screen.mode("standard")
t2 = Turtle('circle', visible=False)
t2.shapesize(4 / CURSOR_SIZE)
t2.speed('fastest')
t2.color('purple')
t2.penup()
t2_size = 4
t1 = Turtle('circle')
t1.shapesize(t1_size / CURSOR_SIZE)
t1.speed('fastest')
r = 190
g = 100
b = 190
t1.color((r/255, g/255, b/255))
t1.penup()
t1.ondrag(move)
screen.tracer(False)
screen.listen()
screen.onkeypress(quit, "Escape")
create()
follow()
#print(phase)
screen.mainloop()
I took another crack at this, just looking at the problem of meteors swarming around a planet. Or in this case, moon as I chose Deimos as my model. I attempted to work at scale making the coordinate system 1 pixel = 1 kilometer. At the start, Deimos sits in a field of meteors each of which has a random heading but they all have the same size and velocity:
from turtle import Turtle, Screen
from random import random
METEOR_VELOCITY = 0.011 # kilometers per second
METEOR_RADIUS = 0.5 # kilometers
SECONDS_PER_FRAME = 1000 # each updates represents this many seconds passed
UPDATES_PER_SECOND = 100
DEIMOS_RADIUS = 6.2 # kilometers
G = 0.000003 # Deimos gravitational constant in kilometers per second squared
CURSOR_SIZE = 20
def follow():
global meteors
new_meteors = []
t = SECONDS_PER_FRAME
for (x, y), velocity, heading, stamp in meteors:
meteor.clearstamp(stamp)
meteor.goto(x, y)
meteor.setheading(heading)
meteor.forward(velocity * t)
meteor.setheading(meteor.towards(deimos))
meteor.forward(G * t * t)
meteor.setheading(180 + meteor.towards(x, y))
if meteor.distance(deimos) > DEIMOS_RADIUS * 2:
new_meteors.append((meteor.position(), velocity, meteor.heading(), meteor.stamp()))
screen.update()
meteors = new_meteors
if meteors:
screen.ontimer(follow, 1000 // UPDATES_PER_SECOND)
def create():
""" create()'s dots with meteor """
count = 0
nux, nuy = -400, 300
while nuy > -400:
meteor.goto(nux, nuy)
if meteor.distance(deimos) > DEIMOS_RADIUS * 2:
heading = random() * 360
meteor.setheading(heading) # all meteors have random heading but fixed velocity
meteors.append((meteor.position(), METEOR_VELOCITY, meteor.heading(), meteor.stamp()))
nux += 20
count += 1
if count % 40 == 0:
nuy -= 50
nux = -400
screen.update()
meteors = []
screen = Screen()
screen.screensize(1000, 1000)
screen.setworldcoordinates(-500, -500, 499, 499) # 1 pixel = 1 kilometer
meteor = Turtle('circle', visible=False)
meteor.shapesize(2 * METEOR_RADIUS / CURSOR_SIZE)
meteor.speed('fastest')
meteor.color('purple')
meteor.penup()
deimos = Turtle('circle')
deimos.shapesize(2 * DEIMOS_RADIUS / CURSOR_SIZE)
deimos.color("orange")
deimos.penup()
screen.tracer(False)
create()
follow()
screen.mainloop()
The first variable to investigate is METEOR_VELOCITY. At the setting provided, most meteors will crash into the moon but a few obtain orbital velocity. If you halve its value, all meteors will crash into the moon. If you double its value, a few meteors obtain escape velocity, leaving the window; a few may crash into the moon; most will form an orbiting cloud that gets smaller and tighter.
I tossed the trigonometric stuff and reverted back to degrees instead of radians. I use vector addition logic to work out the motion.
In the end, it's just a crude model.
By changing 180 to some other offsets, for example 195, in the def follow() in cdlane's code,
meteor.setheading(195 + meteor.towards(x, y))
then the metors would not go straight (180 degree) towards the Deimos, but instead would show some spiral movement towards the center.
Great example provided!
I'm trying to make a game where you click a bunch of random circles and get a score, however I also want it to deduct from your score when you miss the circle. I've been trying to use screen.onclick() but instead of deducting the score on a misclick it seems to deduct the score every second for no reason. What am I doing wrong?
import turtle
from random import random, randint
import time
CURSOR_SIZE = 20
score=0
def addscore():
global score
score += 1
def deletescore():
global score
score -= 1
def my_circle(color):
radius = (15)
circle = turtle.Turtle('circle', visible=False)
circle.shapesize(radius / CURSOR_SIZE)
circle.color(color)
circle.penup()
while True:
nx = randint(2 * radius - width // 2, width // 2 - radius * 2)
ny = randint(2 * radius - height // 2, height // 2 - radius * 2)
circle.goto(nx, ny)
for other_radius, other_circle in circles:
if circle.distance(other_circle) < 2 * max(radius, other_radius):
break
else:
break
circle.showturtle()
circle.onclick(lambda x,y,t=circle: (circle.hideturtle(), addscore()))
screen.onclick(deletescore())
return radius, circle
username=str(input("Set your username: "))
screen = turtle.Screen()
screen.bgcolor("lightgreen")
screen.title("Speed Clicker")
width, height = screen.window_width(), screen.window_height()
circles = []
gameLength = 30
difficulty = 20
startTime = time.time()
while True:
time.sleep(1/difficulty)
rgb = (random(), random(), random())
timeTaken = time.time() - startTime
circles.append(my_circle(rgb))
screen.title('SCORE: {}, TIME LEFT: {}'.format(score,int(round(gameLength - timeTaken,0))))
if time.time() - startTime > gameLength:
break
screen.title('GG! FINAL SCORE: {}'.format(score))
screen.mainloop()
The problem is this line:
screen.onclick(deletescore())
It's in the wrong place (only needs to be called once, not in a loop) and the argument is incorrect, it should be passing the function not calling it:
screen.onclick(deletescore)
The fix is multifold: first, move the modified statement to just before your while True: statement. Then fix the definition of deletescore() to take x and y arguments that we won't use but are necessary to be a click handler. (Or wrap it in a lambda like the call to addscore())
However, a click on a turtle can also be passed through as a click on the screen. To counter this, we can add 2 in addscore() instead of 1 since deletescore() will get called as well. That should be enough to get things working.
However, we really should eliminate the while True: loop and sleep() call which have no place in an event-driven program. Instead we use ontimer() to keep things moving and not potentially block other events. The reformulated code would be more like:
from turtle import Turtle, Screen
from random import random, randint
from time import time
CURSOR_SIZE = 20
def addscore():
global score
score += 2 # add 2 as this will also count as a -1 screen click!
def deletescore():
global score
score -= 1
def my_circle(color):
circle = Turtle('circle', visible=False)
circle.shapesize(radius / CURSOR_SIZE)
circle.color(color)
circle.penup()
while True:
nx = randint(2 * radius - width // 2, width // 2 - radius * 2)
ny = randint(2 * radius - height // 2, height // 2 - radius * 2)
circle.goto(nx, ny)
for other_radius, other_circle in circles:
if circle.distance(other_circle) < 2 * max(radius, other_radius):
break
else:
break
circle.onclick(lambda x, y: (circle.hideturtle(), addscore()))
circle.showturtle()
return radius, circle
def play():
rgb = (random(), random(), random())
timeTaken = time() - startTime
circles.append(my_circle(rgb))
screen.title('SCORE: {}, TIME LEFT: {}'.format(score, int(round(gameLength - timeTaken, 0))))
if time() - startTime > gameLength:
screen.title('FINAL SCORE: {}'.format(score))
screen.onclick(None)
screen.clear()
else:
screen.ontimer(play, 1000 // difficulty)
screen = Screen()
screen.bgcolor("lightgreen")
screen.title("Speed Clicker")
width, height = screen.window_width(), screen.window_height()
score = 0
circles = []
radius = 15
difficulty = 20
gameLength = 30
screen.onclick(lambda x, y: deletescore())
startTime = time()
play()
screen.mainloop()
So this is a program I made for some dots to be attracted to a bigger dot and for that bigger dot to grow. The issue I'm facing right now is that the dots don't follow the bigger dot but rather seem to move away from it. The way I'm getting it to get closer is by translating the points, one to (0,0), the other to [t2.xcor() - t1.xcor() , t2.ycor()- t1.ycor()] , and then finding C with the Pythagorean theorem, and then using arc cosine to find the angle it needs to face in order to move towards the bigger dot.
from turtle import *
import sys
from math import *
#grows t1 shape + has it follow cursor
def grow(x, y):
t1.ondrag(None)
t1.goto(x,y)
global big, nig
t1.shapesize(big,nig)
big += .004
nig += .004
t1.ondrag(grow)
follow()
#has create()'d dots follow t1
def follow():
global count
#t1.ondrag(None)
screen.tracer(0,0)
for p in lx:
#print(lx[0:5])
t2.goto(p, ly[count])
t2.dot(4, "white")
if ly[count] != 0:
yb = abs(t2.ycor() - t1.ycor())
xb = abs((t2.xcor() - t1.xcor()))
c = sqrt((xb**2 + yb**2))
#print(y,x,c)
#print(lx)
t2.seth(360 - degrees(acos(yb/c)))
else:
t2.seth(0)
t2.forward(20)
t2.dot(4, "purple")
lx.pop(count)
ly.pop(count)
lx.insert(count, t2.xcor())
ly.insert(count, t2.ycor())
count += 1
#print(lx[0:5])
#screen.update()
screen.tracer(1,10)
count = 0
#t1.ondrag(follow)
#quits program
def quit():
screen.bye()
sys.exit(0)
#create()'s dots with t2
def create():
screen.tracer(0,0)
global nux, nuy, count3
while nuy > -400:
t2.goto(nux, nuy)
if t2.pos() != t1.pos():
t2.dot(4, "purple")
lx.append(t2.xcor())
ly.append(t2.ycor())
nux += 50
count3 += 1
if count3 == 17:
nuy = nuy - 50
nux = -400
count3 = 0
screen.tracer(1, 10)
#variables
count3 = count = 0
big = nig = .02
lx = []
ly = []
nux = -400
nuy = 300
screen = Screen()
screen.screensize(4000,4000)
t2 = Turtle()
t2.ht()
t2.pu()
t2.speed(0)
t2.shape("turtle")
t1 = Turtle()
t1.shape("circle")
t1.penup()
t1.speed(0)
t1.color("purple")
t1.shapesize(.2, .2)
create()
screen.listen()
screen.onkeypress(quit, "Escape")
t1.ondrag(grow)
#t1.ondrag(follow)
#screen.update()
screen.mainloop()
I see two (similar) issues with your code. First, you can toss the fancy math as you're reinventing turtle's .towards() method which gives you the angle you seek. Second, you're reinventing stamps which, unlike most turtle elements, can be cleared cleanly off the screen via clearstamp(). Also, you're using parallel arrays of coordinates which indicates lack of a proper data structure. I've replaced this with a single array containing tuples of positions and stamps.
I've adjusted the dynamics of your program, making the dots act independently (on a timer) and not rely on the movement of the cursor. I.e. they move towards the cursor whether it's moving or not. Also, I've made the cursor only grow when a dot reaches it and disappears:
from turtle import Turtle, Screen
CURSOR_SIZE = 20
def move(x, y):
""" has it follow cursor """
t1.ondrag(None)
t1.goto(x, y)
screen.update()
t1.ondrag(move)
def grow():
""" grows t1 shape """
global t1_size
t1_size += 0.4
t1.shapesize(t1_size / CURSOR_SIZE)
screen.update()
def follow():
""" has create()'d dots follow t1 """
global circles
new_circles = []
for (x, y), stamp in circles:
t2.clearstamp(stamp)
t2.goto(x, y)
t2.setheading(t2.towards(t1))
t2.forward(2)
if t2.distance(t1) > t1_size // 2:
new_circles.append((t2.position(), t2.stamp()))
else:
grow() # we ate one, make t1 fatter
screen.update()
circles = new_circles
if circles:
screen.ontimer(follow, 50)
def create():
""" create()'s dots with t2 """
count = 0
nux, nuy = -400, 300
while nuy > -400:
t2.goto(nux, nuy)
if t2.distance(t1) > t1_size // 2:
circles.append((t2.position(), t2.stamp()))
nux += 50
count += 1
if count == 17:
nuy -= 50
nux = -400
count = 0
screen.update()
# variables
t1_size = 4
circles = []
screen = Screen()
screen.screensize(900, 900)
t2 = Turtle('circle', visible=False)
t2.shapesize(4 / CURSOR_SIZE)
t2.speed('fastest')
t2.color('purple')
t2.penup()
t1 = Turtle('circle')
t1.shapesize(t1_size / CURSOR_SIZE)
t1.speed('fastest')
t1.color('orange')
t1.penup()
t1.ondrag(move)
screen.tracer(False)
create()
follow()
screen.mainloop()
You should be able to rework this code to do whatever it is you want. I strongly recommend you spend some time reading the Turtle documentation so you don't need to reinvent its many features.