Using recursion to draw a pattern of squares - python

So far this is the code I've written with the great help of my tutor.
def square(t, x, y, side):
t.up()
t.goto(x-side/2, y+side/2)
t.down()
for i in range(4):
t.forward(side)
t.right(90)
def squares(t, x, y, size, n):
if n == 0:
return
if n >= 1:
square(t, x, y, size)
half = size / 2
squares(t, x - half, y + half, size / 2.2, n - 1)
squares(t, x + half, y + half, size / 2.2, n - 1)
squares(t, x + half, y - half, size / 2.2, n - 1)
squares(t, x - half, y - half, size / 2.2, n - 1)
s = Screen()
t = Turtle()
t.pensize(2)
squares(t, 0, 0, 200, 3)
s.exitonclick()
So this program will draw a square with 4 squares on each point and 4 squares on each of their points. This was done within 1.5 hours of tutoring and my guy had to leave for family reasons and I couldn't finish this alone. Here is what the assignment wants.
So what I want is a square with a square with a square with a square

I can tell you got alot from your tutoring session...
just change
squares(t, 0, 0, 200, 3)
to
squares(t, 0, 0, 200, 5)

Related

Having trouble making the Sierpinski Carpet using recursion only and python turtle

import turtle
window = turtle.Screen()
window.screensize(1920,1080)
window.setup(width=1.0, height=1.0, startx=None, starty=None)
# T is a global variable
T = turtle.Turtle()
T.shape('turtle')
T.speed(2)
def forward_and_rotate(distance, degree, t):
t.forward(distance)
t.left(degree)
def draw_square_recursion(distance, t, sides_left=4):
t.pendown()
if sides_left == 0:
return
else:
forward_and_rotate(distance, 90, t)
draw_square_recursion(distance, t, sides_left-1)
t.penup()
def draw_filled_square(t, side_len, color='black'):
current_color = T.pencolor()
t.pendown()
t.color(color)
t.begin_fill()
draw_square_recursion(side_len, t, 4)
t.end_fill()
t.penup()
T.color(current_color)
# Starter code
def sier_carp(t, l, n):
if n == 0:
home = t.pos()
t.goto(home + (l/3, l/3))
draw_filled_square(t, l/3)
t.goto(home)
else:
sier_carp(t, l/3, n - 1)
t.fd(l/3)
sier_carp(t, l/3, n -1)
t.fd(l/3)
sier_carp(t, l/3, n -1)
t.fd(l/3)
t.left(90)
t.fd(l/3)
sier_carp(t, l/3, n - 1)
t.fd(l/3)
sier_carp(t, l/3, n -1)
t.fd(l/3)
t.left(90)
t.fd(l/3)
sier_carp(t, l/3, n - 1)
t.fd(l/3)
sier_carp(t, l/3, n -1)
t.fd(l/3)
t.left(90)
t.fd(l/3)
sier_carp(t, l/3, n - 1)
t.fd(l/3)
sier_carp(t, l/3, n -1)
t.fd(l/3)
t.left(90)
t.fd(l/3)
# add more cases!
T.penup()
sl = 200
draw_square_recursion(sl, T, 4)
sier_carp(T, sl, 1)
I'm having a hard time drawing the Sierpinski carpet using recursion only and no loops. However after the first three squares are drawn and I try to rotate to the left and do the next 2 squares on the nest site, the squares go out of line. If you want you can try out this code and see how it looks like. Btw the color are inversed to white around and black inside and not the usual carpet version of black around and white square.
I'm going to do a simplied version of your code -- although repetitive, your starter code does achieve your goal of recursion only. However, I'm going to use stamping instead of drawing so we can focus on the recursive image and not the mechanics of drawing squares with turtles:
from turtle import Screen, Turtle
SIDE_LENGTH = 200
CURSOR_SIZE = 20
def draw_filled_square(t, side_len):
t.shapesize(side_len / CURSOR_SIZE)
t.stamp()
def sier_carp(t, l, n):
draw_filled_square(t, l)
if n < 1:
return
x, y = t.position()
t.setx(x + l)
sier_carp(t, l/3, n - 1)
t.sety(y + l)
sier_carp(t, l/3, n - 1)
t.setx(x)
sier_carp(t, l/3, n - 1)
t.setx(x - l)
sier_carp(t, l/3, n - 1)
t.sety(y)
sier_carp(t, l/3, n - 1)
t.sety(y - l)
sier_carp(t, l/3, n - 1)
t.setx(x)
sier_carp(t, l/3, n - 1)
t.setx(x + l)
sier_carp(t, l/3, n - 1)
t.goto(x, y)
screen = Screen()
screen.setup(width=1.0, height=1.0)
turtle = Turtle()
turtle.hideturtle()
turtle.shape('square')
turtle.penup()
sier_carp(turtle, SIDE_LENGTH, 4)
screen.exitonclick()
I intentionally avoided speeding things up with screen.tracer(False) as you should see how it's drawn in a counterclockwise pattern.
I believe the problem is simpler than you're making it. It's important in this sort of drawing that your recursive function return the turtle to where it was when the function started. This allows the caller to make valid assumptions about its position.
Here are the concepts I've noted before starting:
Define a function that takes in a coordinate and a square size, and return the 8 surrounding coordinates.
Define a function that takes in a coordinate and a square size, and draw a square with the given size as its length, and the given coordinate as its center point.
Define the main function that will take in a coordinate, a size and the number of recursions. The coordinate will be the center of the Sierpinski Carpet and the size will be the size of the finished Sierpinski Carpet.
With the above notes in mind, let's begin to construct our program:
Import the turtle module, turn off tracer (optional) for efficiency and hide the turtle cursor:
import turtle
turtle.tracer(0)
turtle.hideturtle()
Define the function that takes in coordinate and a unit, and return a list of all 8 coordinates surrounding the given coordinate:
def surrounding(cor, size):
x, y = cor
return [(x - size, y + size),
(x + size, y + size),
(x - size, y - size),
(x + size, y - size),
(x - size, y),
(x + size, y),
(x, y + size),
(x, y - size)]
Define a recursive function that will take in a coordinate, size and a count. It will draw a square with its center at the given coordinate and its size one third of the given size, so that the sides of the finished carpet will end up the given size. The count is there to tell the function when to stop recursing; when the count reaches 0:
def draw_square(cor, size, count):
size /= 3
count -= 1
turtle.penup()
turtle.goto(cor[0] - size / 2, cor[1] + size / 2)
turtle.pendown()
turtle.begin_fill()
turtle.forward(size)
turtle.right(90)
turtle.forward(size)
turtle.right(90)
turtle.forward(size)
turtle.right(90)
turtle.forward(size)
turtle.right(90)
turtle.end_fill()
if count:
func(surrounding(cor, size), size, count)
Define a recursive function that takes in a coordinate and a size. It will use the surroundings function defined earlier to convert that coordinate into a list of surround coordinates. Then, it will use the draw_square function defined earlier to draw a square at each of the surrounding coordinates:
def func(cors, size, count):
if len(cors) == 2:
draw_square(cors, size, count)
else:
draw_square(cors[0], size, count)
draw_square(cors[1], size, count)
draw_square(cors[2], size, count)
draw_square(cors[3], size, count)
draw_square(cors[4], size, count)
draw_square(cors[5], size, count)
draw_square(cors[6], size, count)
draw_square(cors[7], size, count)
Finally, call the main function, func, and update the turtle screen (if tracer was set to 0).
func((0, 0), 600, 5)
turtle.update()
Altogether:
import turtle
turtle.tracer(0)
turtle.hideturtle()
def surrounding(cor, size):
x, y = cor
return [(x - size, y + size),
(x + size, y + size),
(x - size, y - size),
(x + size, y - size),
(x - size, y),
(x + size, y),
(x, y + size),
(x, y - size)]
def draw_square(cor, size, count):
size /= 3
count -= 1
turtle.penup()
turtle.goto(cor[0] - size / 2, cor[1] + size / 2)
turtle.pendown()
turtle.begin_fill()
turtle.forward(size)
turtle.right(90)
turtle.forward(size)
turtle.right(90)
turtle.forward(size)
turtle.right(90)
turtle.forward(size)
turtle.right(90)
turtle.end_fill()
if count:
func(surrounding(cor, size), size, count)
def func(cors, size, count):
if len(cors) == 2:
draw_square(cors, size, count)
else:
draw_square(cors[0], size, count)
draw_square(cors[1], size, count)
draw_square(cors[2], size, count)
draw_square(cors[3], size, count)
draw_square(cors[4], size, count)
draw_square(cors[5], size, count)
draw_square(cors[6], size, count)
draw_square(cors[7], size, count)
func((0, 0), 600, 5)
turtle.update()
Output:

Drawing radiating circular trapezoid pattern with Turtle

I was just wondering how to draw some trapezoids in my turtle code.
I want my output to be like this:
What my output is right now:
Here's the code I've written so far:
import turtle as trtl
num_sides = 6
side_length = 15
circumradius = side_length
trtl.pencolor((245, 176, 66))
trtl.pensize(8)
for move_turtle in range(1):
trtl.penup()
trtl.sety(-circumradius)
trtl.pendown()
trtl.circle(circumradius, steps = num_sides)
circumradius *= 2
side_length = side_length + 8
trtl.pensize(12)
for move_turtle in range(1):
trtl.pencolor((255, 83, 71))
trtl.penup()
trtl.sety(-circumradius)
trtl.pendown()
trtl.circle(circumradius, steps = num_sides)
circumradius *= 2
for move_turtle in range(1):
trtl.pencolor((247, 220, 67))
trtl.penup()
trtl.sety(-circumradius)
trtl.pendown()
trtl.circle(circumradius, steps = num_sides)
circumradius *= 2
trtl.hideturtle()
What techniques can I use to get the desired output?
The approach I often take when given patterns like this is to think about a slight-of-hand trick that can produce the result with less trouble than figuring out how to draw something potentially fussy like a trapezoid.
In this case, if you remove the white spaces, we have a "pie"-shaped circle of different solid colors radiating from the middle. If we can manage to solve that simpler problem, then we can draw white lines on top and wind up with the desired result.
The only trig we need is the angle-to-coordinates formula:
x = cos(radians(angle)) * radius
y = sin(radians(angle)) * radius
Using this, we can iterate over n points (with some rotational offset) along the circumference of a circle with a loop and fill in the colors to make a pie:
import math
import turtle
def draw_pie(t, r, n, colors, rot_offset=0.5):
for i in range(n + 1):
a = 360 / n * (i + rot_offset)
t.color(colors[i%len(colors)])
t.begin_fill()
t.goto(0, 0)
x = math.cos(math.radians(a)) * r
y = math.sin(math.radians(a)) * r
t.goto(x, y)
t.end_fill()
if __name__ == "__main__":
t = turtle.Turtle()
t.screen.setup(540, 540)
t.penup()
t.speed("fastest")
sides = 6
draw_pie(t, 450, sides, ["#dd2", "orange", "#d02"])
t.ht()
turtle.exitonclick()
Next, we need to draw a bunch of thick white lines with square corners. Unfortunately, turtle draws rounded edges by default which aren't suitable for our needs. There are many tricks for making square edges (stamping is a reasonable method). The approach I used here was writing a custom rectangle drawing function that takes advantage of turtle.distance and turtle.setheading(turtle.towards(x, y)) which lets me point to the next point along the circumference of the circle. The rest is just loops to compute angles and picking the right values.
Here's the code:
import math
import turtle
def draw_rect(t, x, y, xx, yy, width):
t.goto(x, y)
t.setheading(t.towards(xx, yy))
t.begin_fill()
for d in [t.distance(xx, yy), width] * 2:
t.forward(d)
t.left(90)
t.end_fill()
def draw_pie(t, r, n, colors, rot_offset=0.5):
for i in range(n + 1):
a = 360 / n * (i + rot_offset)
t.color(colors[i%len(colors)])
t.begin_fill()
t.goto(0, 0)
x = math.cos(math.radians(a)) * r
y = math.sin(math.radians(a)) * r
t.goto(x, y)
t.end_fill()
def draw_reg_polygon(t, r, n, thickness, rot_offset=0.5):
for i in range(n):
a = 360 / n * (i + rot_offset)
x = math.cos(math.radians(a)) * r
y = math.sin(math.radians(a)) * r
a = 360 / n * (1 + i + rot_offset)
xx = math.cos(math.radians(a)) * r
yy = math.sin(math.radians(a)) * r
draw_rect(t, x, y, xx, yy, thickness)
if __name__ == "__main__":
t = turtle.Turtle()
t.screen.setup(540, 540)
t.penup()
t.speed("fastest")
sides = 6
draw_pie(t, 450, sides, ["#dd2", "orange", "#d02"])
t.color("white")
for r in range(50, 500, 100):
draw_reg_polygon(t, r, sides, 45)
t.ht()
turtle.exitonclick()

Iterating through list in 'spiral'

I have an image that looks like this:
I've converted it into a 2D list. What's the best way to iterate through this list in a 'spiral', starting at the top left and ending in the centre. The goal is to read in all non-black pixels.
Here is code I have that progresses through spirals, starting in the upper left and going clockwise. It is naive (doesn't make use of that we know there are spirals) and simply prints out the coordinates, but I hope you can modify it to what you need.
I've checked several edge cases, because you need to make sure (0,1,2,3 mod 4) x (0,1,2,3 mod 4) all work. And wide spirals and squarish spirals need to be considered.
def do_stuff_with(my_array, x, y):
print("Do stuff with", x, ",", y) #obviously, you will want to return or manipulate something. But this code is just about traversing the spiral.
def spiral(my_array, width, height):
x_left = 0
x_right = width
y_top = 0
y_bottom = height
x_c = y_c = 0
print("Bounding box {0},{1} -> {2},{3}".format(x_left, y_top, x_right, y_bottom))
if x_left >= x_right or y_top >= y_bottom:
print("Invalid spiral range.")
return
while x_left < x_right and y_top < y_bottom:
#print("Going right")
for i in range(x_left, x_right):
do_stuff_with(my_array, i, y_top)
#print("Going down")
for i in range(y_top + 1, y_bottom):
do_stuff_with(my_array, x_right - 1, i)
if y_bottom - y_top > 1: # this just prevents us from revisiting a square in, say, a 5x7 spiral
#print("Going left")
for i in range(x_right - 2, x_left, -1):
do_stuff_with(my_array, i, y_bottom - 1)
if x_right - x_left > 1: # this just prevents us from revisiting a square in, say, a 7x5 spiral
#print("Going up")
for i in range(y_bottom - 1, y_top + 1, -1):
do_stuff_with(my_array, x_left, i)
# we need to fill in the square that links an outer spiral with an inner spiral.
if x_right - x_left > 2 and y_bottom - y_top > 4:
do_stuff_with(my_array, x_left + 1, y_top + 2)
x_left += 2
x_right -= 2
y_top += 2
y_bottom -= 2
print("Top/bottom overlap", y_top >= y_bottom)
print("Left/right overlap", x_left >= x_right)
def test_spirals(xi, yi, xf, yf):
'''an exhaustive test to make sure different rectangles/spirals work'''
for x in range(xi, xf):
for y in range(yi, yf):
print(x, y, "spiral test")
my_array = []
for z in range(0, y):
my_array.append([0] * x)
spiral(my_array, x, y)
# squarish tests: it seems like the main cases are (0/1/2/3 mod 4, 0/1/2/3 mod 4) so these 16 should knock everything out
test_spirals(4, 4, 8, 8)
# rectangular tests--yes, this overlaps the above case with 5x(6/7) but we want to try all possibilities mod 4 without having too much data to read.
#test_spirals(2, 6, 6, 10)
Let me know if you need or want clarifications.
ETA: here is some pseudocode for if you know you are reading in a spiral, but I think it is a big assumption. Also, this pseudocode is untested. But the idea is: go right til you hit a wall or black square, then down, then left, then up. Then repeat. Also, check for unnecessary trackbacks that may cause you to loop at the innermost line near the end.
def traverse_known_spiral(myary, width, length):
do_stuff(0, 0)
x_c = 0
y_c = 0
while True:
x_c_i = x_c
y_c_i = y_c
while x_c < width - 1 and myary[x_c+1][y_c] == WHITE:
do_stuff(x_c+1, y_c)
x_c += 1
while y_c < height - 1 and myary[x_c][y_c+1] == WHITE:
do_stuff(x_c, y_c+1)
y_c += 1
if x_c_i == x_c and y_c_i == y_c: break # if we did not move right or down, then there is nothing more to do
x_c_i = x_c
y_c_i = y_c
if y_c_i != y_c: # if we didn't go down, then we'd be going backwards here
while x_c > 0 and myary[x_c-1][y_c] == WHITE:
do_stuff(x_c-1, y_c)
x_c -= 1
if x_c_i != x_c: # if we didn't go right, then we'd be going backwards here
while y_c > 0 and myary[x_c-1][y_c-1] == WHITE:
do_stuff(x_c, y_c-1)
y_c -= 1
if x_c_i == x_c and y_c_i == y_c: break # if we did not move left or up, then there is nothing more to do

How to draw this IFS cantor set with labels?

I am using the Zelle graphics package in Python to draw the iterations of a cantor ternary like IFS (iterated function system) graph.
The iterated function system I want to draw is:
{R;x/9+1/6,2/3+x/9,x/9+1/3,x/9+5/6}
The following code is an imitation of what I found on the Internet. Is the code correct? How do I get each line segment to have a different color, and how do I label the points in each stage?
from graphics import *
def cantor_set(win,x,y,h,Len):
if Len < 2: return
line = Line(Point(x, y), Point(x+Len, y))
line.setWidth(20)
line.draw(win)
cantor_set(win,2*x+3,y+h,h,Len//18)
cantor_set(win,2*x+12,y+h,h,Len//18)
cantor_set(win,2*x+6,y+h,h,Len//18)
cantor_set(win,2*x+15,y+h,h,Len//18)
def cantor_set_starter():
Len = 790
win = GraphWin("Cantor Set", 800, 200)
c = cantor_set(win,5,20,50,790)
win.postscript(file = "can1.eps")
#win.getMouse()
#win.close()
cantor_set_starter()
There are two major types of errors in programming, syntax and algorithm. Your program suffers from both. We can fix your syntax errors fairly easily:
from graphics import *
def cantor_set(win, x, y, h, length):
if length < 2:
return
line = Line(Point(x, y), Point(x + length, y))
line.setWidth(20)
line.draw(win)
cantor_set(win, 2 * x + 3, y + h, h, length // 18)
cantor_set(win, 2 * x + 12, y + h, h, length // 18)
cantor_set(win, 2 * x + 6, y + h, h, length // 18)
cantor_set(win, 2 * x + 15, y + h, h, length // 18)
def cantor_set_starter(length):
cantor_set(win, 5, 20, 50, length)
win = GraphWin("Cantor Set", 800, 200)
cantor_set_starter(790)
win.getMouse()
win.close()
But since there are algorithm errors, it doesn't draw a cantor ternary-like graph. To help you fix these, we need to know more about where you got the the iterated function system equation and other details. If the goal is simply to draw a Cantor Set, we can modify your program to do so, mostly by throwing away code and adjusting the math on what's left:
from graphics import *
def cantor_set(win, x, y, height, length):
if length < 2:
return
line = Line(Point(x, y), Point(x + length, y))
line.setWidth(height / 2)
line.draw(win)
length /= 3
cantor_set(win, x, y + height, height, length)
cantor_set(win, x + 2 * length, y + height, height, length)
win = GraphWin("Cantor Set", 800, 300)
cantor_set(win, 5, 20, 50, 790)
win.getMouse()
win.close()

Rounding errors with PIL

I am having trouble writing to an image with my Mandelbrot program; it is to do with rounding errors with "(maxx-minx)/width" compared to "(maxy-miny)/width" line 31, which leads to a 501 by 500 picture rather than a 500 square picture.
((width+1) * length) not (width * length).
How can I fix this?
from PIL import Image
from cmath import *
from math import sqrt
from numpy import arange
width = 500
height = 500
minx = -0.752 #float(input("Please enter the minimum x value:"))
maxx = -0.748 #float(input("Please enter the maximum x value:"))
miny = 0.098 #float(input("Please enter the minimum y value:"))
maxy = 0.102 #float(input("Please enter the maximum y value:"))
gradient = Image.open("mandelbrot.png")
gradlist = list(gradient.getdata())
def testMandelbrot(x, y):
z = 0 + 0j
c = x + (y*1j)
iter = 0
while iter <= 69 and sqrt(z.real**2 + z.imag**2) < 4:
z = (z*z) + c
iter += 1
if iter == 70:
return (0, 0, 0, 255)
else:
return gradlist[int((iter - 1) * 140 / 70)]
img = Image.new('RGBA', (width, height), color=(255, 255, 255, 255))
image = [testMandelbrot(x, y) for y in arange(miny, maxy, (maxy-miny)/height) for x in arange(minx, maxx, (maxx-minx)/width)] #this line creates the error ((maxx-minx)/width) / (maxx - min) gives (width+1) not width
print(len(image), img.size)
img.putdata(image)
img.save("picture111.png", "PNG")
I'd suggest using numpy's linspace rather than arange. It will return an array of exactly the given number of evenly spaced samples.
See that linspace(0.098, 0.102, 500, endpoint=False) is exactly 500 points long. If you want to include the endpoint, you can leave out endpoint=False or pass endpoint=True.
With endpoint=False if you generate another image with the same size height and width but offset by difference between max_ - min_ the result would be the adjacent tile depending on which it would result in one of the eight.
Your code would then be:
Y = linspace(miny, maxy, height, endpoint=False)
X = linspace(minx, maxx, width, endpoint=False)
image = [testMandelbrot(x, y) for y in Y for x in X]
I name the arrays because Y is reused len(X) times and because height and width are small (500), it doesn't cost much and aids readability.

Categories