In Python create a circle With two mouse clicks - python

I need to use Python to create an archery target with five rings(yellow,red,blue,black,white)
It needs to get the first mouse click to select where the center ring should go.
The second mouse click will indicate somewhere on the circumference of the inner circle.
The remaining rings (red,blue,black,white) width need top be the same as the inner circles radius.
I am having trouble figuring out how take information from the second mouse click and give it to the inner circle to find it's radius.
from graphics import *
def main():
win = GraphWin('Archery Target',300,300)
center = win.getMouse()
w = Circle(center, 100)
w.setFill('white')
w.draw(win)
bl = Circle(center, 80)
bl.setFill('black')
bl.draw(win)
b = Circle(center, 60)
b.setFill('blue')
b.draw(win)
r = Circle(center, 40)
r.setFill('red')
r.draw(win)
y = Circle(center, 20)
y.setFill('yellow')
y.draw(win)
win.getMouse() # pause for click in window
win.close()
main()
This code just lets you decide where the center will be placed, but has a single ring size.
final output

Assuming that the graphics module you load is the one of John Zelle (found e.g. here), what you need is another point
point = win.getMouse()
and then, as #Kevin suggested, the Pythagorean formula to calculate the distance between your points (needs from math import sqrt)
dx = point.getX() - center.getX()
dy = point.getY() - center.getY()
radius = sqrt(dx*dx + dy*dy)
You can then use radius to plot your circles.
If this doesn't solve your question, please give some more details.

Related

Undraw all objects within a certain region Python Graphics

I needed to know whether it is possible to clear all objects within a given set of coordinates regardless of the name of the object. I don't have any code for this at the moment as I've been trying to brainstorm this for a while now but have come up with nothing. An example I can give is that there are squares and circles within the coordinates of (200,100) and (300,200) how would I delete everything within these coordinates?
Yes, this can be done. First, the GraphWin object keeps track of items drawn on it but doesn't officially export this list. So you should keep track of what you draw onto the window.
The items you want (e.g. Rectangle and Circle) belong to the class _BBox which has a getCenter() method you can use to manipulate objects within your boundaries:
from random import randrange
from graphics import *
win = GraphWin("My Example", 400, 400)
boundary = Rectangle(Point(200, 100), Point(300, 200))
boundary.setOutline('blue')
boundary.draw(win)
graphics = []
for _ in range(150):
circle = Circle(Point(randrange(400), randrange(400)), 5)
circle.setOutline('green')
circle.draw(win)
graphics.append(circle)
x, y = randrange(400), randrange(400)
rectangle = Rectangle(Point(x, y), Point(x + 10, y + 10))
rectangle.setOutline('orange')
rectangle.draw(win)
graphics.append(rectangle)
for graphic in graphics:
center = graphic.getCenter()
if 200 < center.getX() < 300 and 100 < center.getY() < 200:
graphic.setFill('red')
graphic.undraw()
win.getMouse()
win.close()

Pygame: How do I blit and rotate an image to connect two points on the screen?

Here is a test program. I started with two random dots and the line connecting them. Now, I want to take a given image (with x,y dimensions of 79 x 1080) and blit it on top of the guide line. I understand that arctan will give me the angle between the points on a cartesian grid, but because y is backwards the screen (x,y), I have to invert some values. I'm confused about the negating step.
If you run this repeatedly, you'll see the image is always parallel to the line, and sometimes on top, but not consistently.
import math
import pygame
import random
pygame.init()
screen = pygame.display.set_mode((600,600))
#target = (126, 270)
#start = (234, 54)
target = (random.randrange(600), random.randrange(600))
start = (random.randrange(600), random.randrange(600))
BLACK = (0,0,0)
BLUE = (0,0,128)
GREEN = (0,128,0)
pygame.draw.circle(screen, GREEN, start, 15)
pygame.draw.circle(screen, BLUE, target, 15)
pygame.draw.line(screen, BLUE, start, target, 5)
route = pygame.Surface((79,1080))
route.set_colorkey(BLACK)
BMP = pygame.image.load('art/trade_route00.png').convert()
(bx, by, bwidth, bheight) = route.get_rect()
route.blit(BMP, (0,0), area=route.get_rect())
# get distance within screen in pixels
dist = math.sqrt((start[0] - target[0])**2 + (start[1] - target[1])**2)
# scale to fit: use distance between points, and make width extra skinny.
route = pygame.transform.scale(route, (int(bwidth * dist/bwidth * 0.05), int( bheight * dist/bheight)))
# and rotate... (invert, as negative is for clockwise)
angle = math.degrees(math.atan2(-1*(target[1]-start[1]), target[0]-start[0]))
route = pygame.transform.rotate(route, angle + 90 )
position = route.get_rect()
HERE = (abs(target[0] - position[2]), target[1]) # - position[3]/2)
print(HERE)
screen.blit(route, HERE)
pygame.display.update()
print(start, target, dist, angle, position)
The main problem
The error is not due to the inverse y coordinates (0 at top, max at bottom) while rotating as you seems to think. That part is correct. The error is here:
HERE = (abs(target[0] - position[2]), target[1]) # - position[3]/2)
HERE must be the coordinates of the top-left corner of the rectangle inscribing your green and blue dots connected by the blue line. At those coordinates, you need to place the Surface route after rescaling.
You can get this vertex by doing:
HERE = (min(start[0], target[0]), min(start[1], target[1]))
This should solve the problem, and your colored dots should lay on the blue line.
A side note
Another thing you might wish to fix is the scaling parameter of route:
route = pygame.transform.scale(route, (int(bwidth * dist/bwidth * 0.05), int( bheight * dist/bheight)))
If my guess is correct and you want to preserve the original widht/height ratio in the rescaled route (since your original image is not a square) this should be:
route = pygame.transform.scale(route, (int(dist* bwidth/bheight), int(dist)))
assuming that you want height (the greater size in the original) be scaled to dist. So you may not need the 0.05, or maybe you can use a different shrinking parameter (probably 0.05 will shrink it too much).

Bouncing ball in a circle (Python Turtle)

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()

Writing a function that asks a user to input a color and then fills a shape with that color

My task is to write a function, drawCircle(radius,fillColor), that asks a user for the specific radius of the circle and which color they'd like the circle to be filled.
I have the circle-drawing down, but I'm struggling with getting the circle to fill with the user-defined color. Any help would be greatly appreciated.
import turtle
def drawCircle(radius, fillColor):
x=360/300 #This gives the angle
r=radius#This is the radius of the circle.
c=fillColor
c=str("")
z=1 #Placeholder for the while loop.
win=turtle.Screen()
tom=turtle.Turtle()
fillColor=tom.color()
tom.begin_fill()
while (z<=300):
tom.forward(r)
tom.right(x)
tom.forward(r)
z=z+1
win.exitonclick()
tom.end_fill()
This is my function call: drawCircle(1,"red")
There are several problems with your code:
You call win.exitonclick before tom.end_fill, so programm exits before filling (as it happens on end_fill)
You do "fillColor=tom.color()" with gets you current color. Instead use "tom.fillcolor(fillColor)"
Unnecessary copying of variables radius->r and fillColor->c
This is python. Use for whenever possible. Instead of counting using z use:
for _ in range(300):
My final code:
import turtle
def drawCircle(radius, fillColor):
x = 360/300 # This gives the angle
win = turtle.Screen()
tom = turtle.Turtle()
tom.fillcolor(fillColor)
tom.begin_fill()
for _ in range(300):
tom.forward(radius)
tom.right(x)
tom.forward(radius)
tom.end_fill()
win.exitonclick()
drawCircle(1, "red")
I have the circle-drawing down
Before addressing your fill issue, I'd argue that your premise isn't true, you don't have circle-drawing down. As #Mysak0CZ shows, your circle of radius 1 is huge -- 1 what? You're drawing a circle but have no real control over its size.
As a professional turtle wrangler, I'd go about the problem as follows. Not only your angle needs to be divided by the number of segments you plan to draw, but you need to compute a circumference based on the requested radius and chop that up as well. I do so below and include a call to turtle's own .circle() method to show that we're in the right ballpark. And I fix your minor fill issue:
import math
from turtle import Turtle, Screen # force object-oriented turtles
SEGMENTS = 60 # how many lines make up the circle
def drawCircle(radius, fillColor):
distance = math.pi * radius * 2 / SEGMENTS # circumference / SEGMENTS
angle = 360 / SEGMENTS
turtle.fillcolor(fillColor)
turtle.begin_fill()
for _ in range(SEGMENTS):
turtle.forward(distance)
turtle.left(angle) # left for .circle() compatibility
turtle.end_fill()
screen = Screen()
turtle = Turtle()
drawCircle(100, 'red')
turtle.circle(100) # for comparison
screen.exitonclick()
Here is my code for a pink circle if you are going through the same Python learning resource. I think the original code was missing an argument.
import turtle
def drawPolygon(t, sideLength, numSides):
t.goto(0, 0)
turnAngle = 360 / numSides
for i in range(numSides):
t.forward(sideLength)
t.right(turnAngle)
def drawCircle(anyTurtle, radius):
circumference = 2 * 3.1415 * radius
sideLength = circumference / 360
drawPolygon(anyTurtle, sideLength, 360)
def drawFilledCircle(anyTurtle, radius, color):
anyTurtle.fillcolor(color)
anyTurtle.begin_fill()
drawCircle(anyTurtle, radius)
anyTurtle.end_fill()
anyTurtle.hideturtle()
wn = turtle.Screen()
wheel = turtle.Turtle()
drawFilledCircle(wheel, 80, "pink")
wn.exitonclick()

turtle drawing automatic centering

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()

Categories

Resources