I am trying to draw a polygon in python using the turtle library.
I want to set the size to fit the screen size but I don't know how.
For now I tried two ways and it does work but I want that no matter the size the polygon fits in the screen. For example if I put size = 50 and n = 100 it will not show in the screen.
from turtle import *
def main():
sides = int(input('Enter the number of sides of a polygon: '))
angle = 360/sides
size = 500/sides
# =============================================================================
# if sides <= 20:
# size = 50
# else:
# size= 10
# =============================================================================
for i in range(sides):
forward(size)
left(angle)
i += 1
hideturtle()
done()
main()
If I understand correctly, you are asking how to choose a size (side length) such that the shape fills but does not exceed the screen. First, we need to check the height or width of the screen (whichever is smaller):
min_dimension = min([tr.window_width(), tr.window_height()]) - 20
(I'm not sure why it is necessary to reduce the dimension by about 20 pixels, but if I don't do this then the shape is drawn slightly outside the visible area.)
The circumradius is the radius of the circle that passes through all the points of the polygon and the apothem is the distance from the center to the midpoint of a side. For a polygon with odd numbers of sides to just fit the screen, the circumradius needs to be half the height of the screen; for a polygon with even numbers of sides to just fit the screen, the apothem + circumradius should equal the height of the screen.
Reversing the formulae for the circumradius and apothem, we can determine the maximum side length given the height. Also note that to fill the screen we should start drawing near the bottom of the screen rather than in the center.
def main():
sides = int(input('Enter the number of sides of a polygon: '))
angle = 360/sides
min_dimension = min([tr.window_width(), tr.window_height()]) - 20
# Determine max side length
a = math.pi / sides
if sides % 2: # odd number of sides
size = min_dimension * 2 * math.sin(a) / (1 + math.cos(a))
else: # even number
size = min_dimension * math.tan(a)
# Start near the bottom of the screen
penup()
right(90)
forward(min_dimension / 2)
right(90)
forward(size / 2)
left(180)
pendown()
# Draw polygon
for _ in range(sides):
forward(size)
left(angle)
done()
Related
I'm trying to draw any regular polygon, so from triangles to polygons with so many corners, it looks like a circle. to make it easier, they must be regular, so a normal pentagon/hexagon/octagon etc. I want to be able to rotate them. What ive tried is to draw a circle and divide 360 by the amount of points i want then, create a point every nth degrees around the circle, putting these points in pygame.draw.polygon() then creates the shape i want, the problem is it isn't the right size, i also want to be able to have stretch the shapes, so have a different width and height.
def regular_polygon(hapi, x, y, w, h, n, rotation, angle_offset = 0):
#angle_offset is the starting angle in the circle where rotation is rotating the circle
#so when its an oval, rotation rotates the oval and angle_offset is where on the oval to start from
if n < 3:
n = 3
midpoint = pygame.Vector2(x + w//2, y + h//2)
r = sqrt(w**2 + h**2)
#if angle_offset != 0:
#w = (w//2)//cos(angle_offset)
#if angle_offset != 90:
#h = (h//2)//sin(angle_offset)
w,h = r,r
points = []
for angle in range(0, 360, 360//n):
angle = radians(angle + angle_offset)
d = pygame.Vector2(-sin(angle)*w//2, -cos(angle)*h//2).rotate(rotation) #the negative sign is because it was drawing upside down
points.append(midpoint + d)
#draws the circle for debugging
for angle in range(0, 360, 1):
angle = radians(angle + angle_offset)
d = pygame.Vector2(-sin(angle)*w//2, -cos(angle)*h//2).rotate(rotation)
pygame.draw.rect(screen, (0,255,0), (midpoint[0] + d[0], midpoint[1] + d[1], 5, 5))
pygame.draw.polygon(screen,(255,0,0),points)
the red square is the the function above is making, the blue one behind is what it should be
as you can see the circle does line up with the edges of the rect, but because the angles of the circle are not even, the rectangle the func makes is not right.
i think i need to change the circle to an oval but cannot find out how to find the width radius and height radius of it. currently ive found the radius by using pythag.
this is what happens when i dont change the width or height
I found the solution
doing w *= math.sqrt(2) and h *= math.sqrt(2) works perfectly. dont quite understand the math, but after trial and error, this works. You can probable find the maths here but i just multiplied the width and height by a number and printed that number when it lined up which was very close to the sqrt(2)
I am currently working on a circular billiards program using Turtle. My problem is that I can't figure out what angle or position I need to give Python once the ball has reached the sides of the circle in order to make it bounce. Here is the part of my program that needs to be fixed:
while nbrebonds>=0:
forward(1)
if (distance(0,y)>rayon): #rayon means radius
print(distance(0,y))
left(2*angleinitial) #I put this angle as a test but it doesn't work
forward(1)
nbrebonds+=(-1)
From what I was able to understand about this issue, you should be able to calculate what you need using turtle's heading() and towards() methods:
from random import *
from turtle import *
radius = 100
nbrebonds = 10
# draw circle around (0, 0)
penup()
sety(-radius)
down()
circle(radius)
# move turtle to somewhat random position & heading inside circle
penup()
home()
setx(randrange(radius//4, radius//2))
sety(randrange(radius//4, radius//2))
setheading(randrange(0, 360))
pendown()
while nbrebonds >= 0:
forward(1)
if distance(0, 0) > radius:
incoming = heading()
normal = towards(0, 0)
outgoing = 2 * normal - 180 - incoming
setheading(outgoing)
forward(1)
nbrebonds -= 1
mainloop()
I'm being really bugged by a task:
User inputs radius r and then turtle draws the circle then proceeds to draw another circle with the same center but 10 px smaller until the radius is 0
First let us approximate a circle as a regular polygon with 36 sides/segments.
To draw this shape given a radius r we need to know;
The length of each segment
The angle to turn between each segment
To calculate the length, we first need the circumference, which is 2πr (we will approximate pi as 3.1415), giving us
circumference = 2 * 3.1415 * radius
Next we divide this by the number of segments we are approximating, giving
circumference = 2 * 3.1415 * radius
seg_lenght = circumferece/36
Now we need the angle difference between the segments, or the external angle. This is simply 360/n for a regular n-gon(polygon with n sides), so we do 360/36 = 10
We can now define a function to generate the segment length and draw the circle:
def circle_around_point(radius):
circumference = 2 * 3.1415 * radius
seg_length = circumference/36
penup()
fd(radius) #Move from the centre to the circumference
right(90) #Face ready to start drawing the circle
pendown()
for i in range(36): #Draw each segment
fd(seg_length)
right(10)
penup()
right(90) #Face towards the centre of the circle
fd(radius) #Go back to the centre of the circle
right(180) #Restore original rotation
pendown()
Now for the concentric circles:
def concentric_circles(radius):
while radius > 0:
circle_around_point(radius)
radius -= 10
It's not clear why #IbraheemRodrigues felt the need to recode turtle's circle() function based on your problem description, but we can simplify his solution by not reinventing the wheel:
def circle_around_point(turtle, radius):
is_down = turtle.isdown()
if is_down:
turtle.penup()
turtle.forward(radius) # move from the center to the circumference
turtle.left(90) # face ready to start drawing the circle
turtle.pendown()
turtle.circle(radius)
turtle.penup()
turtle.right(90) # face awary from the center of the circle
turtle.backward(radius) # go back to the center of the circle
if is_down:
turtle.pendown() # restore original pen state
def concentric_circles(turtle, radius):
for r in range(radius, 0, -10):
circle_around_point(turtle, r)
The key to circle() is that the current position is on the edge of the circle so you need to shift your position by the radius to make a specific point the center of the circle.
However, to solve this problem, I might switch from drawing to stamping and do it this way to speed it up and simplify the code:
import turtle
STAMP_SIZE = 20
radius = int(input("Please input a radius: "))
turtle.shape('circle')
turtle.fillcolor('white')
for r in range(radius, 0, -10):
turtle.shapesize(r * 2 / STAMP_SIZE)
turtle.stamp()
turtle.mainloop()
However, this draws crude circles as it's blowing up a small one:
To fix that, I might compromise between the two solutions above and do:
import turtle
radius = int(input("Please input a radius: "))
turtle.penup()
turtle.forward(radius)
turtle.left(90)
turtle.pendown()
turtle.begin_poly()
turtle.circle(radius)
turtle.penup()
turtle.end_poly()
turtle.addshape('round', turtle.get_poly()) # 'circle' is already taken
turtle.right(90)
turtle.backward(radius)
turtle.shape('round')
turtle.fillcolor('white')
for r in range(radius - 10, 0, -10):
turtle.shapesize(r / radius)
turtle.stamp()
turtle.mainloop()
This improves circle quality by shrinking a large one instead of enlarging a small one:
Where quality of the circle can be controlled using the steps= argument to the call to circle().
But, if I really wanted to minimize code while keeping quality high and speed fast, I might do:
import turtle
radius = int(input("Please input a radius: "))
for diameter in range(radius * 2, 0, -20):
turtle.dot(diameter, 'black')
turtle.dot(diameter - 2, 'white')
turtle.hideturtle()
turtle.mainloop()
The dot() method draws from the center instead of the edge, uses diameters instead of radii, draws only filled circles, and seems our best solution to this particular exercise:
import turtle
#### ##### #### Below class draws concentric circles.
class Circle:
def __init__(self, pen, cx, cy, radius):
self.pen = pen
self.cx = cx
self.cy = cy
self.radius = radius
def drawCircle(self):
self.pen.up()
self.pen.setposition( self.cx, self.cy - self.radius )
self.pen.down()
self.pen.circle(self.radius)
def drawConCircle(self, minRadius = 10, delta = 10):
if( self.radius > minRadius ) :
self.drawCircle()
self.radius -= delta # reduce radius of next circle
self.drawConCircle()
#### End class circle #######
win = turtle.Screen()
win.bgcolor("white")
s = Circle( turtle.Turtle(), 0, 0, 200 )
s.drawConCircle()
win.exitonclick()
I'm looking for best way to automatically find starting position for new turtle drawing so that it would be centered in graphics window regardless of its size and shape.
So far I've developed a function that checks with each drawn element turtle position to find extreme values for left, right, top and bottom and that way I find picture size and can use it to adjust starting position before releasing my code. This is example of simple shape drawing with my picture size detection added:
from turtle import *
Lt=0
Rt=0
Top=0
Bottom=0
def chkPosition():
global Lt
global Rt
global Top
global Bottom
pos = position()
if(Lt>pos[0]):
Lt = pos[0]
if(Rt<pos[0]):
Rt= pos[0]
if(Top<pos[1]):
Top = pos[1]
if(Bottom>pos[1]):
Bottom = pos[1]
def drawShape(len,angles):
for i in range(angles):
chkPosition()
forward(len)
left(360/angles)
drawShape(80,12)
print(Lt,Rt,Top,Bottom)
print(Rt-Lt,Top-Bottom)
This method does work however it seems very clumsy to me so I would like to ask more experiences turtle programmers is there a better way to find starting position for turtle drawings to make them centered?
Regards
There is no universal method to center every shape (before you draw it and find all your max, min points).
For your shape ("almost" circle) you can calculate start point using geometry.
alpha + alpha + 360/repeat = 180
so
alpha = (180 - 360/repeat)/2
but I need 180-alpha to move right (and later to move left)
beta = 180 - aplha = 180 - (180 - 360/repeat)/2
Now width
cos(alpha) = (lengt/2) / width
so
width = (lengt/2) / cos(alpha)
Because Python use radians in cos() so I need
width = (length/2) / math.cos(math.radians(alpha))
Now I have beta and width so I can move start point and shape will be centered.
from turtle import *
import math
# --- functions ---
def draw_shape(length, repeat):
angle = 360/repeat
# move start point
alpha = (180-angle)/2
beta = 180 - alpha
width = (length/2) / math.cos(math.radians(alpha))
#color('red')
penup()
right(beta)
forward(width)
left(beta)
pendown()
#color('black')
# draw "almost" circle
for i in range(repeat):
forward(length)
left(angle)
# --- main ---
draw_shape(80, 12)
penup()
goto(0,0)
pendown()
draw_shape(50, 36)
penup()
goto(0,0)
pendown()
draw_shape(70, 5)
penup()
goto(0,0)
pendown()
exitonclick()
I left red width on image.
I admire #furas' explanation and code, but I avoid math. To illustrate that there's always another way to go about a problem here's a math-free solution that produces the same concentric polygons:
from turtle import Turtle, Screen
def draw_shape(turtle, radius, sides):
# move start point
turtle.penup()
turtle.sety(-radius)
turtle.pendown()
# draw "almost" circle
turtle.circle(radius, steps=sides)
turtle = Turtle()
shapes = [(155, 12), (275, 36), (50, 5)]
for shape in shapes:
draw_shape(turtle, *shape)
turtle.penup()
turtle.home()
turtle.pendown()
screen = Screen()
screen.exitonclick()
I need help capping off my for loops. (They can go on forever, yet I need them to be in range of 12 and 10.) My thinking is instead of this
for i in range(sides):
to do:
for sides in range(12):
#and for repeat do
for repeat in range(10):
This, in theory, should stop the possibly infinite process of looping, right? The second problem is my end_fill() doesn't fill in each shape with a different color. The outline and inside should be the same, and on a new shape, should be a random color (using randint function).
from turtle import *
import math
import random
#Continue being lazy and not creating turtles, also using a .cfg file to make the window.
colormode(255)
sides = int(input("Enter a number of sides (1-12): "))
repeat = int(input("How many times to repeat? (1-10): "))
#Turtle stuff.
penup()
goto(0,0)
size = 100
angle1 = 360 / sides
angle2 = 360 / repeat
#Here is where I am having issues, I am not sure how much to turn the turtle by do to the needed input
#Also I need to cap this off to only 12 sides and 10 repeats
#As it stands it can go for infinitum.
begin_fill()
for count in range(repeat):
for i in range(sides):
pendown() #Puts the pen down.
pensize(5) #Sets the pen to something more visible
colors1 = random.randint(0, 255) #Random red
colors2 = random.randint(0, 255) #Random blue
colors3 = random.randint(0, 255) #Random green
forward(size) #Goes forward 10 pixels
left(angle1)
penup()
#Goes left 360 / sides degrees. So 360 / 10 = 36 degrees.
pencolor(colors1, colors2, colors3) #Sets pencolor to a random RGB code
fillcolor(colors1, colors2, colors3) #Sets fillcolor to a random RGB code.
left(angle2) #Move left 90 degrees afterwards.
forward(5) #Moves forward 5 times.
end_fill() #Fills in the shape.
Code also available here: https://gist.github.com/anonymous/3984f7a1a04e9957ea55
Your first problem can be solved using Python 3 turtle's graphic numinput() instead of console input():
numinput(title, prompt, default=None, minval=None, maxval=None)
This will limit the range of the user's input. Your second issue is due to having begin_fill and end_fill at two different indentation levels. Usually, they should be at the same level. Here's a rework of your code with the above changes:
from turtle import Screen, Turtle
import random
screen = Screen()
screen.colormode(255)
sides = screen.numinput("Color Polygons", "Enter a number of sides (1-12)", default=6, minval=1, maxval=12)
if sides is None: # user cancelled operation
sides = 6
repeat = screen.numinput("Color Polygons", "How many times to repeat? (1-10)", default=5, minval=1, maxval=10)
if repeat is None:
repeat = 5
turtle = Turtle()
turtle.speed('fastest')
turtle.pensize(5)
turtle.penup()
size = 100
angle1 = 360 / sides
angle2 = 360 / repeat
for count in range(int(repeat)):
red = random.randint(0, 255)
green = random.randint(0, 255)
blue = random.randint(0, 255)
turtle.color(red, green, blue)
turtle.begin_fill()
for i in range(int(sides)):
turtle.pendown()
turtle.forward(size)
turtle.left(angle1)
turtle.penup()
turtle.end_fill()
turtle.left(angle2)
turtle.forward(5)
turtle.hideturtle()
screen.exitonclick()