Is there any way I can avoid Turtle slowing down the more lines it has to print? - python

I made a program to generate a random 2D array of 1s and 0s that I then print as green "land" tiles above a blue background to generate a little 'map'. I've tried to optimise the code as much as I know how but when I try to print anything above 100x100, it slows down by a LOT. If I set it to print line by line, I see that it's taking quite a lot longer to print lines the more of the map turtle has already printed. I don't know why this is (I don't know much about turtle or rendering in general) but I would quite like to know how to solve this problem.
If this is not possible, could I have recommendations for other libraries/languages that I could try this in? I'd like to do more programs like this and I don't mind learning something new. :)
Here is my code:
import turtle
import random
import time
d = turtle.Turtle()
d.ht()
wn = turtle.Screen()
wn.setup(width=0.45,height=0.8,startx=800,starty=50)
wn.bgcolor("#002240")
turtle.tracer(0,0)
d.penup()
interval = 10
def square(col) :
d.pendown()
d.color(col)
d.begin_fill()
for i in range(4) :
d.fd(interval)
d.right(90)
d.end_fill()
d.penup()
size = int(input("Enter map size "))
custom_interval = input("Custom interval? ")
if custom_interval != '' :
interval = int(custom_interval)
int_by_size = interval * size
start_gen = time.time()
data = []
for i in range(size) :
gen_line = []
[gen_line.append(random.randint(0,1)) for i in range(size)]
data.append(gen_line)
end_gen = time.time()
print("GEN DONE IN",end_gen-start_gen)
d.goto(-int_by_size/2,int_by_size/2)
start_draw = time.time()
d.pendown()
d.color("blue")
d.begin_fill()
for i in range(4) :
d.fd(int_by_size)
d.right(90)
d.end_fill()
d.penup()
for y,line in enumerate(data) :
for x,tile in enumerate(line) :
if tile == 1 :
square("green")
d.fd(interval)
else :
d.fd(interval)
#Comment out to print entire map at once
turtle.update()
d.backward(int_by_size)
d.sety(d.ycor()-interval)
end_draw = time.time()
print("DRAW DONE IN",end_draw-start_draw)
wn.exitonclick()

My rework of your code below speeds up the drawing by an order of magnitude by switching from drawing to stamping and other tricks:
from turtle import Screen, Turtle
from random import randint
import time
CURSOR_SIZE = 20
interval = 10
size = int(input("Enter map size: "))
custom_interval = input("Custom interval: ")
if custom_interval != '':
interval = int(custom_interval)
int_by_size = interval * size
screen = Screen()
screen.setup(width=0.45, height=0.8, startx=800, starty=50)
screen.bgcolor('#002240')
screen.tracer(False)
x, y = interval/2 - int_by_size/2, int_by_size/2 - interval/2
start_gen = time.time()
data = [(randint(False, True) for _ in range(size)) for _ in range(size)]
print("GEN DONE IN", time.time() - start_gen)
start_draw = time.time()
turtle = Turtle()
turtle.hideturtle()
turtle.shape('square')
turtle.penup()
turtle.color('blue')
turtle.shapesize(int_by_size / CURSOR_SIZE)
turtle.stamp()
turtle.color('green')
turtle.shapesize(interval / CURSOR_SIZE)
turtle.goto(x, y)
for line in data:
dx = x
for tile in line:
if tile:
turtle.setx(dx)
turtle.stamp()
dx += interval
# Comment out to print entire map at once
screen.update()
y -= interval
turtle.goto(x, y)
print("DRAW DONE IN", time.time() - start_draw)
screen.tracer(True)
screen.exitonclick()
Some of my optimizations you may need to undo, like making the data a list of generators instead of a list of lists (speeds up data generation but is only good for a single pass.) Also, remember that a window size of width=0.45, height=0.8 is relative to the size of your screen -- other folks running on other screens will get a different size window.

Related

Improving Chaos Game efficiency

I've written this script to use the turtle module to replicate the Chaos Game I saw on Numberphile's channel. There are a large amount of dots being drawn to actually make it work well in a larger scale. I assume the large amount of dots is what causes the program to begin running slower after a bit and I was wondering if anyone could help me come up with a workaround for this.
I'm open for any kind of solution, as long as the controls remain the same and the number of vertexes can be any number above 3.
If someone doesn't know what the Chaos Game is, it's a game where you have polygon with any amount of vertexes. At first you place a dot inside the polygon, randomly choose one of the vertexes and draw a new dot halfway in-between the dot you just placed earlier and the randomly chosen vertex. You keep repeating this process and each time you'll use the newly drawn dot.
In this script I've also included a rule to make sure it doesn't choose the same vertex two times in a row to form nice fractals with more than 3 vertexes. 3 vertexes actually forms the Sierpinski triangle.
Here's a link to Numberphile's video: https://www.youtube.com/watch?v=kbKtFN71Lfs
As you can probably tell, I'm somewhat new to Python and coding in general.
Full code:
import turtle as t
import tkinter as tk
from tkinter import ttk
from random import randint
wn = t.Screen()
wn.colormode(255)
t.pu();t.ht();t.speed(0)
plist = []
l = 0
val = 0
pb=ttk.Progressbar(orient="horizontal",length=wn.window_width(),mode="determinate")
pb.pack(side=tk.BOTTOM)
pb["value"]=0
def Clear():
t.clear()
plist = []
def Dot(x, y):
t.goto(x, y)
t.dot(5, (0, 0, 255))
plist.append(t.pos())
def Run(x, y):
wn.onscreenclick(None)
wn.tracer(0, 0)
l = len(plist)
pb["maximum"]=l*1000
xyc = randint(0, l-1)
xyc_old = 0
for _ in range(l*10):
xyc = randint(0, l-1)
for i in range(100):
xyc = randint(0, l-1);
if l >= 4:
while xyc == xyc_old:
xyc = randint(0, l-1);
xyc_old = xyc;
t.goto((t.pos()[0]+plist[xyc][0])/2, (t.pos()[1]+plist[xyc][1])/2);
t.dot(2, (255, 0, 0));
pb["value"]+=1;
pb.update()
wn.update()
plist.clear()
wn.onscreenclick(Dot, btn=1)
wn.onscreenclick(Run, btn=3)
wn.onscreenclick(Dot, btn=1)
wn.onscreenclick(Run, btn=3)
wn.onkey(Clear, "c")
wn.listen()
wn.mainloop()
I feel like my coding style is very different from a lot of the people on here, but I hope that isn't an issue.
Thank you!
I assume the large amount of dots is what causes the program to begin
running slower after a bit and I was wondering if anyone could help me
come up with a workaround for this.
Surprisingly, no. It's your own instrumentation (progress bar) slowing you down. Comment out:
pb.update()
and watch what happens.
I feel like my coding style is very different from a lot of the people
on here, but I hope that isn't an issue.
It's an issue where it overlaps with bad coding style. E.g. lack of white space, use of semicolons, effective no-ops in the code, etc. My rework of your code:
from turtle import Screen, Turtle
import tkinter as tk
from tkinter import ttk
from random import randrange
def clear():
turtle.clear()
plist.clear()
def dot(x, y):
turtle.goto(x, y)
turtle.dot(5, 'blue')
plist.append(turtle.position())
def run(x, y):
screen.onscreenclick(None, btn=1)
screen.onscreenclick(None, btn=3)
screen.onkey(None, 'c')
length = len(plist)
flag = length >= 4
pb['maximum'] = length * 1000
xyc_old = 0
for _ in range(length * 100):
for _ in range(10):
xyc = randrange(length)
if flag:
while xyc == xyc_old:
xyc = randrange(length)
xyc_old = xyc
x, y = turtle.position()
dx, dy = plist[xyc]
turtle.goto((x + dx) / 2, (y + dy) / 2)
turtle.dot(2)
pb['value'] += 10
pb.update()
plist.clear()
pb['value'] = 0
screen.onscreenclick(dot, btn=1)
screen.onscreenclick(run, btn=3)
screen.onkey(clear, 'c')
plist = []
screen = Screen()
screen.tracer(False)
turtle = Turtle()
turtle.hideturtle()
turtle.setundobuffer(None)
turtle.color('red')
turtle.penup()
pb = ttk.Progressbar(orient='horizontal', length=screen.window_width(), mode='determinate')
pb.pack(side=tk.BOTTOM)
pb['value'] = 0
screen.onscreenclick(dot, btn=1)
screen.onscreenclick(run, btn=3)
screen.onkey(clear, 'c')
screen.listen()
screen.mainloop()
Other changes include:
The plist = [] in Clear() won't work without a global plist. Use plist.clear() instead.
You also need to disable (and reenable) wn.onkey(Clear, "c") during Run otherwise your user can break the program. You also need to disable the two mouse buttons independently.
You really want randrange(), not randint().

How can I transfer the data that my program generates to a new turtle program to use?

Seen below is the code for my iteration program. I want to be able to use turtle graphics to take each parameter (k) and have the equations output plotted against its corresponding k value. This should create a feigenbaum diagram if im not mistaken? My problem is, how can I get a turtle to plot these points for each k value, then connect them to the points from the neighbouring k values and so on?
def iteration(xstore):
global x0
x0=xstore
print (x0)
x0=float(input("x0:"))
n=float(input("max parameter value:"))
divison=float(input("divisons between parameters:"))
xv=x0
x1=0
k=0
while k<(n+divison):
print("K VALUE:"+str(k))
for i in range (0,20):
x1=x0+x0*k*(1-x0)
iteration(x1)
print ("________________________")
x0=xv
k=k+divison
Here is a feigenbaum diagram generated using tkinter. It is from the "open book project", visualizing chaos.
The program source is here; I converted it to python 3 and posted it hereunder. There is a lot for you to learn reading and understanding this code.
#
# chaos-3.py
#
# Build Feigenbaum Logistic map. Input start and end K
#
# python chaos-3.py 3.4 3.9
#
canWidth=500
canHeight=500
def setupWindow () :
global win, canvas
from tkinter import Tk, Canvas, Frame
win = Tk()
canvas = Canvas(win, height=canHeight, width=canWidth)
f = Frame (win)
canvas.pack()
f.pack()
def startApp () :
global win, canvas
import sys
# k1 = float(sys.argv[1]) # starting value of K
# k2 = float(sys.argv[2]) # ending value of K
x = .2 # is somewhat arbitrary
vrng = range(200) # We'll do 200 horz steps
for t in range(canWidth) :
win.update()
k = k1 + (k2-k1)*t/canWidth
# print("K = %.04f" % k)
for i in vrng :
p = x*canHeight
canvas.create_line(t,p,t,p+1) # just makes a pixel dot
x = x * (1-x) * k # next x value
if x <=0 or x >= 1.0 :
# print("overflow at k", k)
return
def main () :
setupWindow() # Create Canvas with Frame
startApp() # Start up the display
win.mainloop() # Just wait for user to close graph
k1 = 2.9
k2 = 3.8
main()
how can I get a turtle to plot these points for each k value
Here's a simple, crude, slow example I worked out using Python turtle:
from turtle import Screen, Turtle
WIDTH, HEIGHT = 800, 400
Kmin = 2.5
Kmax = 3.8
x = 0.6
screen = Screen()
screen.setup(WIDTH, HEIGHT)
screen.setworldcoordinates(Kmin, 0.0, Kmax, 1.0)
screen.tracer(False)
turtle = Turtle()
turtle.hideturtle()
turtle.penup()
k = Kmin
while k < Kmax:
for _ in range(HEIGHT//4):
x *= (1.0 - x) * k
turtle.goto(k, x)
turtle.dot(2)
x *= 1 + 1/(HEIGHT//4)
k *= 1 + 1/WIDTH
screen.tracer(True)
screen.exitonclick()
I hope it gives you some ideas about plotting functions using a turtle. (Of course, using matplotlib with numpy usually works out better in the end.)

Random Walk - Any Direction

I'm new to this, but I'm trying to create a program that goes on a Random Walk in turtle and I have no idea what I'm doing.This is the program that I need to create Requirements, and this is what I have so far Code. I'm starting to get a little lost, and am wondering if I 'm headed towards the right direction or if i need to scrap it and start all over again
Thanks,
import turtle
import random
import math
def start():
myS = turtle.Screen()
myS.title("Random Walk")
border = 50
myWin.setworldcoordinates(-border, -border, border, border)
def go(heading, step_size):
turtle.setheading(heading)
turtle.forward(step_size)
def random_walk(step_size, steps):
angle = random.random() * 2 * math.pi
x = 0
y = 0
x = x + math.cos(angle)
y = y + math.sin(angle)
coord = (x, y)
for _ in range(steps):
go(random.choice(coord), step_size)
if __name__ == '__main__':
turtle.hideturtle()
turtle.speed('fastest')
random_walk(15, 1000)
Your random_walk function does way to many things; I think it grew over your head a little. What it should do for every step:
Calculate a random angle between 0 and 2*math.pi
Calculate x and y with the given formulas
Call turtle.goto(x,y) to go to these coordinates
Also there was a little typo in start.
import turtle
import random
import math
def start():
myS = turtle.Screen()
myS.title("Random Walk")
border = 50
myS.setworldcoordinates(-border, -border, border, border)
def go(heading, step_size):
turtle.setheading(heading)
turtle.forward(step_size)
def random_walk(steps):
x = 0
y = 0
for i in range(steps):
angle = random.random()*2*math.pi
x = x + math.cos(angle)
y = y + math.sin(angle)
turtle.goto(x,y)
if __name__=="__main__":
start()
random_walk(100)
Because this looks like a homework assignment I don't want to just fix your errors but instead give you pointers to the right solution. I will also hide hints behind spoilers...
if you run random_walk() with e.g. parameters random_walk(1, 1) your will notice that the direction of the walk will not appear random. Use some form of debugging to find out why that is
your go() functions expects an angle, look at what random.choice() produces
if you fix the issue number 1. you will see that the turtle will not draw a random line at all again
looks like the angle does not change every step. find a solution that updates the angle before each step!
units of angles: find out what unit setheading() expects: hint
your requirements mention the output of straight line distance and distance travelled. This somehow sounds like you need some form of tally variable (one or several). hint: if you read the turtle documentation carefully you might find a funtion that maybe makes this task easier
read the documentation for turtle.position().

Can't draw parabolic curve correctly with turtle graphics

I'm making a game like 'angry bird'.
There are two input:power and angle.
I apply those inputs to parabolic curve.
My turtle flies, making a parabolic curve. and my turtle have to hit the target,
but my turtle draws strange curve when angle is greater than 46, angle is 30, 40 etc...
I don't know where is problem....here is my code:
import turtle
import random
import math
g=9.80665
origin_x=-480
origin_y=-180
flag=False
def create_target():
x=random.randint(0,500)
y=random.randint(-200,0)
target=turtle.Turtle()
target.hideturtle()
target.penup()
target.goto(x,y)
target.shape('square')
target.color('red')
target.showturtle()
return target
def create_turtle():
homework=turtle.Turtle()
homework.hideturtle()
homework.penup()
homework.speed(0)
homework.goto(origin_x,origin_y)
homework.pendown()
homework.shape('turtle')
homework.color('blue')
homework.left(45)
homework.showturtle()
return homework
def setting():
'''drawing back ground lines'''
setting=turtle.Turtle()
setting.hideturtle()
setting.speed(0)
turtle.colormode(255)
setting.pencolor(214,214,214)
for y in range(100,-101,-100):
setting.penup()
setting.goto(-500,y)
setting.pendown()
setting.goto(500,y)
for x in range(-375,500,125):
setting.penup()
setting.goto(x,200)
setting.pendown()
setting.goto(x,-200)
def throw_turtle(turtle,target):
angle=int(input("Enter Angle:"))
power=int(input("Enter Power:"))
'''
parabola fomula:
x coordinate: speed(in here, that is power) * cos(anlge)*time
y coordinate: speed*sin(angle)*time - (gravity speed*time**2)/2
'''
for time in range(1,20):
# the origin fomula is for the situation that starts from (0,0). so I think
# I should compensate it, but is it right?
x=power*math.cos(angle)*time + origin_x
y=power*math.sin(angle)*time - (((time**2)*g)/2) + origin_y
if x<origin_x: # I think it has problem...
x-=origin_x
turtle.goto(x,y)
turtle.stamp() #this is for testing
if (x==target.xcor()) and (y==target.ycor()):
print("******Target is HIT!!! ******")
print("End of Game")
flag=True
break
else:
print("You missed...")
turtle.setup(1000,400)
windo=turtle.Screen()
windo.title('Angry Turtle')
setting()
#__main
my_turtle=create_turtle()
while flag==False:
target=create_target()
my_turtle=create_turtle()
my_turtle.speed(6)
throw_turtle(my_turtle,target)
my_turtle.hideturtle()
target.hideturtle()
I think create_target() and create_turtle(), and setting() don't have problem...
Below, I reduce your code to a MVCE (minimal, complete, and verifiable example) to examine the parabolic curve drawing code. The problem I found with it is the usual one of the difference between degrees and radians. The Python math library thinks in radians but provides a conversion function for degrees. The Python turtle library thinks in degress, by default, but can switch to radians using turtle.radians(). Either way is fine but the usage has to be consistent:
from turtle import Turtle, Screen
import math
import random
G = 9.80665
origin_x = -480
origin_y = -180
def create_turtle():
homework = Turtle(shape='turtle')
homework.hideturtle()
homework.penup()
homework.goto(origin_x, origin_y)
homework.pendown()
homework.speed(0)
homework.left(45)
homework.showturtle()
return homework
def throw_turtle(turtle):
angle = int(input("Enter Angle (in degrees): "))
power = int(input("Enter Power: "))
# parabola formula:
# x coordinate: speed(in here, that is power) * cos(angle)*time
# y coordinate: speed*sin(angle)*time - (gravity speed * time**2)/2
for time in range(1, 20):
x = power * math.cos(math.radians(angle)) * time + origin_x
y = power * math.sin(math.radians(angle)) * time - (((time ** 2) * G) / 2) + origin_y
turtle.goto(x, y)
turtle.stamp() # this is for testing
window = Screen()
window.setup(1000, 400)
for _ in range(3):
my_turtle = create_turtle()
my_turtle.color(random.choice(['red', 'green', 'blue', 'purple', 'black']))
throw_turtle(my_turtle)
window.exitonclick()
EXAMPLE
> python3 test.py
Enter Angle (in degrees): 30
Enter Power: 120
Enter Angle (in degrees): 45
Enter Power: 90
Enter Angle (in degrees): 60
Enter Power: 90
>
Now, what more do you want it to do parabolic curve-wise?
Oh Thanks thanks really thanks.......!!!!
but, i have one more problem. that is, the 'if' sentence in throw_turtle function.
my intention for using that 'if' sentence is for check and end the game. but in fact, the user can not exactly correct coordinate of target. so it is impossible that end the game. so the game is endless....
to avoid that, i re-write like this.
def throw_turtle(turtle,target):
angle=int(input("Enter Angle:"))
power=int(input("Enter Power:"))
'''
parabola fomula: x coordinate: speed(in here, that is power) * cos(anlge)*time
y coordinate: speed*sin(angle)*time - (gravity speed*time**2)/2'''
for time in range(1,20):
x=power*math.cos(math.radians(angle))*time + origin_x #the origin fomula is for the situation that starts from (0,0). so i think i should compensate it. but.. is it right?
y=power*math.sin(math.radians(angle))*time - (((time**2)*g)/2) + origin_y
turtle.goto(x,y)
turtle.stamp() #this is for testing min_target_x=target.xcor()-1
max_target_x=target.xcor()+1 #the '1' means target's size
min_target_y=target.ycor()-1
max_target_y=target.ycor()+1
min_target_y=target.ycor()-1
if ((turtle.xcor()>=min_target_x) or (turtle.xcor()<=max_target_x)) and ((turtle.ycor()>=min_target_y) or (turtle.ycor()<=max_target_y)):
print("******Target is HIT!!! ******")
print("End of Game")
flag=True
break
else:
print("You missed...")

Randomly orientated lines drawn off a random point in python

I'm trying to create python program that has several vertical lines which act as boundaries where randomly generated points or "dots" (as referred to in the code) which draw a straight line at a random degree. If the straight line intersects with one of the vertical "boundaries" I want to make it change colour. I have a picture of what I am trying to achieve which will probably explain my situation a bit clearer. The code I post below has drawn the "vertical boundaries" and has the points randomly generated within the region, however that is where I am stuck.
What I am aiming to achieve:
Example of program
My current Code:
setup(750,750)
screen_size = 750
max_coord = (screen_size - 30) / 2
### change the number of dots you have via that variable
num_dots = 500
bgcolor('yellow')
dot_size=5
reset() # Create an empty window
pi = Turtle()
hideturtle()
def parallel_lines(number):
pi.pensize(2)
pi.pencolor('black')
width = pi.window_width()
height = pi.window_height()
pi.setheading(90)
pi.penup()
pi.setposition(width/-2, height/-2)
for i in range(1, number +2):
pi.pendown()
pi.forward(height)
pi.penup()
pi.setposition(width/-2+i*(width/(number+1)),height/-2)
parallel_lines(7)
## centre turtle back in the middle of the page
goto(0,0)
### list to hold the dots
x_coords = []
y_coords = []
### Draw the dots via randomint
penup()
color("blue")
for dot_num in range(num_dots):
dot_pos_x = randint (-max_coord, max_coord)
dot_pos_y = randint (-max_coord, max_coord)
goto(dot_pos_x, dot_pos_y)
dot(dot_size)
x_coords.append(dot_pos_x)
y_coords.append(dot_pos_y)
done()
Thank you in advance for anyone that can help.
Here's an implementation of the program the OP describes. If there is a line intersection, it uses Python 3 turtle's undo feature to remove the line and redraw it in the alternate color:
from turtle import Turtle, Screen
from random import randint, randrange
SCREEN_SIZE = 750
PLANK_COUNT = 8
PINHEAD_SIZE = 5
FLOOR_COLOR = "yellow"
DEFAULT_COLOR = "blue"
CROSSING_COLOR = "red"
screen = Screen()
screen.setup(SCREEN_SIZE, SCREEN_SIZE)
screen.bgcolor(FLOOR_COLOR)
# configure numbers to replicate Lazzarini's setup
NUMBER_PINS = 3408
PIN_LENGTH = 78.125
PLANK_WIDTH = screen.window_width() / PLANK_COUNT
def parallel_lines(turtle, width, height):
turtle.penup()
turtle.setheading(90)
turtle.sety(height / -2)
x_coordinates = []
for i in range(PLANK_COUNT + 1):
x = i * PLANK_WIDTH - width / 2
turtle.setx(x)
turtle.pendown()
turtle.forward(height)
turtle.penup()
turtle.left(180)
x_coordinates.append(x)
return x_coordinates
pi = Turtle(visible=False)
pi.speed("fastest")
x_coordinates = parallel_lines(pi, screen.window_width(), screen.window_height())
def crosses(x0, x1, coordinates):
for coordinate in coordinates:
if x0 <= coordinate <= x1 or x1 <= coordinate <= x0:
return True
return False
previous_crossings = crossings = 0
max_coord = screen.window_width() / 2
for pin in range(NUMBER_PINS):
x0, y0 = randint(-max_coord, max_coord), randint(-max_coord, max_coord)
pi.color(DEFAULT_COLOR)
pi.goto(x0, y0)
pi.dot(PINHEAD_SIZE)
pi.setheading(randrange(360))
pi.pendown()
pi.forward(PIN_LENGTH)
if crosses(x0, pi.xcor(), x_coordinates):
pi.undo()
pi.color(CROSSING_COLOR)
pi.dot(PINHEAD_SIZE)
pi.forward(PIN_LENGTH)
crossings += 1
pi.penup()
if previous_crossings != crossings:
estimate = (2 * PIN_LENGTH * pin) / (PLANK_WIDTH * crossings)
print(estimate)
previous_crossings = crossings
screen.exitonclick()
Now for the rest of the story. What the OP didn't mention is that this is a drawing of planks in a floor, and we're dropping pins onto it, keeping track of how many cross lines in the flooring, as a means of estimating the value of PI (π)!
Read about Buffon's needle for details. The gist is the probability of a pin crossing a line is a function of PI so we can turn the equation around, drop actual (or virtual) pins, to estimate PI.
The program outputs the running estimate for PI (π), based on pins dropped so far, to the console:
...
3.121212121212121
3.1215970961887476
3.1370772946859904
3.134418324291742
3.131768953068592
3.1381381381381384
3.1384892086330933
3.1358467983243568
3.1451612903225805
3.1454979129397733
3.1458333333333335
3.1491384432560903
3.1465005931198102
3.1438721136767316
3.144208037825059
3.144542772861357
3.1419316843345113

Categories