tkinter - multiple variables at once - python

I have a tkinter program.
I want to get the effect where, after clicking on three places in the image, the program prints appropriate coordinates: initial (x1, y1), height (y2-y1) and width (x3-x1), so the first click - x and y, the second - height and the third - width.
At the moment I have a defined function "printcoords". When I click on the image, the function "bind" runs, which call "printcoords" function and the x and y coordinates are printed
#function to be called when mouse is clicked
def printcoords(event):
#outputting x and y coords to console
print (event.x, event.y)
#mouseclick event
canvas.bind("<Button 1>",printcoords)

The following code roughly does what you are looking for.
import tkinter as tk
coords = []
def printcoords(event):
global coords
#print(event.x,event.y)
coords.append((event.x,event.y))
if len(coords) >= 3:
print(coords)
print(f"Initial: {coords[0][0]},{coords[0][1]}")
print(f"Height: {coords[1][1]-coords[0][1]}")
print(f"Width: {coords[2][0]-coords[0][0]}")
coords = []
root = tk.Tk()
c = tk.Canvas(root,width=200,height=200,bg="lightyellow")
c.bind("<Button-1>",printcoords)
c.grid()
root.mainloop()
It stores the x/y coordinates in a list until the list contains 3 values, it will then print out the first set of coordinates followed by the height and width as per the algorithm in your question.
Using a global here isn't best practice but it works for such a simple example. In a more complex program, I'd store the coordinates in a class.

from itertools import cycle
steps = cycle([1,2,3])
class Point:
def __init__(self):
self.x = 0
self.y = 0
points = [Point() for _ in range(3)]
#function to be called when mouse is clicked
def printcoords(event):
step = next(steps)
p = points[step-1]
p.x = event.x
p.y = event.y
if step==3:
#outputting x and y coords to console
print(f"Initial: {points[0].x},{points[0].y}")
print(f"Height: {points[1].y-points[0].y}")
print(f"Width: {points[2].x-points[0].x}")
#mouseclick event
canvas.bind("<Button 1>",printcoords)

Related

If I have a function that randomly creates shapes, how can I add those shapes to a group? (CMU CS Academy)

I'm working in CMU CS Academy's Sandbox and I currently have a function that will draw a rectangle of random size, color, and position:
# List of colors
app.colors = ['crimson', 'gold', 'dodgerBlue', 'mediumPurple']
# Creating random shapes
import random
# Draws a random rectangle with random points for size and center along with a random color
def drawRect(x, y):
color = random.choice(app.colors)
x = random.randint(5,390)
y = random.randint(15,300)
w = random.randint(10,40)
h = random.randint(10,40)
r = Rect(x,y,w,h,fill=color,border='dimGray')
x = r.centerX
y = r.centerY
# Draws multiple random rectangles
def drawRects():
for i in range(5):
x = 50 * i
y = 60 * i
drawRect(x,y)
drawRects()
However, I want to add all the random rectangles that the function draws to a group so that I'm able to use the .hitsShape() method.
I also thought about creating a list with the random x and y values, but I'm not sure how to create a list with coordinates in CS Academy. What should I do to my current code? What should I do next?
Firstly, you have forgotten to end your functions with return .... That way, you can keep working with this data further in the code block. It's one of "best practices" in Python.
Also, I'm assuming you mean a collection by "group"?
You could save them in a tuple in this manner:
def drawRect(x, y):
...
r = Rect(x,y,w,h,fill=color,border='dimGray')
...
return r
def drawRects():
my_shapes = []
for i in range(5):
x = 50 * i
y = 60 * i
my_shapes.append(drawRect(x,y))
return my_shapes

Click-Circle game - clicking outside and inside the circle

I am trying to code a game that has a red circle in which the user is supposed to click up to 7 times in the window. If the user clicks outside the circle, the circle will change its position to where the user clicked. And the game should end when the user has clicked 3 times inside the circle (does not have to be in a row) or when the user has clicked 7 times in total.
I have coded and done quite most of it I think, its just I cant seem to make it work as I want to.
from graphics import *
def draw_circle(win, c=None):
x = random.randint(0,500)
y = random.randint(0,500)
if var is None:
centa = Point(x,y)
var = Circle(centa,50)
var.setFill(color_rgb(200,0,0))
var.draw(win)
else:
p1 = c.p1
x_dif = (p1.x - x) * -1
y_dif = (p1.y - y) * -1
var.move(x_dif, y_dif)
return (var, x, y)
def main():
win= GraphWin("game",800,800)
score = 0
var,x,y = draw_circle(win)
while score <= 7:
mouseClick2=win.getMouse()
if mouseClick2.y >= y-50 and mouseClick2.y <= y +50 and
mouseClick2.x >= x-50 and mouseClick2.x <= x+50:
score=score + random.randint(0,5)
var,x,y = draw_circle(win, c)
print ("Success!")
print (("the score is, {0}").format(score))
thanks for the help in advance!
I see a couple problems.
your if mouseClick2.y >= y-50... conditional is spread out on two lines, but you don't have a line continuation character.
You never call main().
You don't import random.
You call draw_circle in the while loop with an argument of c, but there is no variable by that name in the global scope. You probably meant to pass in var.
c in draw_circle ostensibly refers to the circle object you want to manipulate, but half the time you manipulate var instead of c.
you assign a value to cvar in the loop, but never use it.
Your else block in draw_circle calculates the movement delta by subtracting the cursor coordinates from c.p1. But c.p1 is the upper-left corner of the circle, not the center of the circle. So your hit detection is off by fifty pixels.
import random
from graphics import *
def draw_circle(win, c=None):
x = random.randint(0,500)
y = random.randint(0,500)
if c is None:
centa = Point(x,y)
c = Circle(centa,50)
c.setFill(color_rgb(200,0,0))
c.draw(win)
else:
center_x = c.p1.x + 50
center_y = c.p1.y + 50
x_dif = (center_x - x) * -1
y_dif = (center_y - y) * -1
c.move(x_dif, y_dif)
return (c, x, y)
def main():
win= GraphWin("game",800,800)
score = 0
var,x,y = draw_circle(win)
while score <= 7:
mouseClick2=win.getMouse()
if mouseClick2.y >= y-50 and mouseClick2.y <= y +50 and \
mouseClick2.x >= x-50 and mouseClick2.x <= x+50:
score=score + random.randint(0,5)
var,x,y = draw_circle(win, var)
print ("Success!")
print (("the score is, {0}").format(score))
main()
Additional possible improvements:
Your hit detection checks whether the cursor is in a 50x50 rectangle centered on the circle. You could instead check whether the cursor is inside the circle if you measured the distance between the cursor and the center, and checked whether it was less than the radius.
var and c could stand to have more descriptive names.
mouseClick2 doesn't make much sense as a name, considering there's no mouseClick1.
The movement delta arithmetic could be simplified: (a-b) * -1 is the same as (b-a).
If you only use a variable's value once, you can sometimes avoid creating the variable at all if you nest expressions.
it might be nice to define constants, such as for the circle's radius, instead of having magic numbers in your code.
You can save five characters by using += to increment the score.
import math
import random
from graphics import *
RADIUS = 50
def draw_circle(win, circle=None):
x = random.randint(0,500)
y = random.randint(0,500)
if circle is None:
circle = Circle(Point(x,y),RADIUS)
circle.setFill(color_rgb(200,0,0))
circle.draw(win)
else:
circle.move(
x - circle.p1.x - RADIUS,
y - circle.p1.y - RADIUS
)
return (circle, x, y)
def main():
win= GraphWin("game",800,800)
score = 0
circle,x,y = draw_circle(win)
while score <= 7:
cursor = win.getMouse()
if math.hypot(cursor.x - x, cursor.y - y) <= RADIUS:
score += random.randint(0,5)
circle,x,y = draw_circle(win, circle)
print ("Success!")
print (("the score is, {0}").format(score))
main()
I'm not really a python guy, but I see that your hitbox is wrong. If there are any other issues then comment it/them to me.
Solving hitbox to be circle:
What you have already written is good to have thing but you should check if click was in circle not square. Pythagoras triangle is solution for this.
Check:
if (math.sqrt(delta_x **2 + delta_y **2) <= circle_radius)
where delta_x and delta_y is center coordinate minus mouse position

Creating a Button (from image) in Zelle's Graphics

I have a start button image that I am trying to turn into a button in my program. However, I believe I am doing the math wrong or something wrong obviously because it's not working. Basically, what I am trying to do is if the person clicks on the button, it will initiate an if statement. Any ideas? Thanks in advance!
#Assigning Mouse x,y Values
mousePt = win.getMouse()
xValue = startImage.getHeight()
yValue = startImage.getWidth()
#Assigning Buttons
if mousePt <= xValue and mousePt <= yValue:
hour = 2
startImage is the image I want to make a button. hour is a variable stated in other code.
You're comparing apples to oranges. This line:
if mousePt <= xValue and mousePt <= yValue:
is roughly the same as saying:
if Point(123, 45) <= 64 and Point(123, 45) <= 64:
It makes no sense to compare Points to widths and heights. You need to combine the widths and heights with the center position of the image and extract the X & Y values from the mouse position:
from graphics import *
win = GraphWin("Image Button", 400, 400)
imageCenter = Point(200, 200)
# 64 x 64 GIF image from http://www.iconsdb.com/icon-sets/web-2-green-icons/video-play-icon.html
startImage = Image(imageCenter, "video-play-64.gif")
startImage.draw(win)
imageWidth = startImage.getWidth()
imageHeight = startImage.getHeight()
imageLeft, imageRight = imageCenter.getX() - imageWidth/2, imageCenter.getX() + imageWidth/2
imageBottom, imageTop = imageCenter.getY() - imageHeight/2, imageCenter.getY() + imageHeight/2
start = False
while not start:
# Obtain mouse Point(x, y) value
mousePt = win.getMouse()
# Test if x,y is inside image
x, y = mousePt.getX(), mousePt.getY()
if imageLeft < x < imageRight and imageBottom < y < imageTop:
print("Bullseye!")
break
win.close()
This particular icon shows up as a circle, the area you can click includes its rectangular bounding box, some of which is outside the circle. It's possible to limit the clicks to exactly the visible image but that takes more work.

How to change the coordinates of a shape (i.e. oval) in tkinter?

I am trying to make a program which when you press the S button it moves shape to the square below it on the grid. I have managed to get the shape to move the first time but after that it just keeps getting bigger.
Here is my code:
from tkinter import *
root = Tk()
global y
y = 0
x = 0
def down(event):
global y
global x
y = y+100
x = x+ 100
global pirate
canvas.delete(pirate)
pirate = canvas.create_oval((x,y), (100,100), fill = 'red')
print(y)
canvas = Canvas(root, width = 1000, height = 1000)
canvas.pack()
for a in range (10):
i = a*100
canvas.create_line((i,0), (i,1000))
for a in range (10):
i = a*100
canvas.create_line((0,i), (1000,i))
pirate = canvas.create_oval((x, y),(100, 100), fill = 'red')
root.bind('<Key - S>', down)
root.mainloop()
As ArtOfWarfare mentioned in comments, instead of creating new ovals everytime, create one and move that thing around.
def down(event):
canvas.move(pirate, 0, 100)
Above code is sufficient to move your oval one square down in your code.
Assuming you'll need to move oval other than just down, instead of binding only S to canvas, I think you should get all key events and do stuff depending on pressed char.
def keyPressed(event):
if event.char.lower() == 's': #move it down if it's S or s
canvas.move(pirate, 0, 100)
root.bind('<Key>', keyPressed) #get all key pressed events
You have a problem that in python-tk the oval is NOT specified create_oval(x,y,w,h), but create_oval(x1,y1,x2,y2). Hope that's helpful!

Checking if clicks are within a graphic object [Python Graphics Module]

Here is the module I'm using: http://mcsp.wartburg.edu/zelle/python/graphics/graphics.pdf
I want to see whether a user's clicks are within a shape or not. I used the in operator, but I know that is incorrect. Below is a chunk of my code:
win = GraphWin("Click Speed", 700, 700)
theTarget = drawTarget(win, random.randrange(0,685), random.randrange(0,685))
while theTarget in win:
click = win.getMouse()
if click in theTarget:
print("Good job")
I left out the code that draws theTarget shape because it is length and unnecessary. It is a moving circle.
I'm using a while loop so it allows me to constantly get the user's clicks.
How do I go about checking whether or not a user's clicks are in the specified Target shape by using the getMouse() command?
I'm going to have to use this in the future for more abstract shapes (not simple circles).
Circle
For the simple case of a circle, you can determine whether the mouse is inside using the distance formula. For example:
# checks whether pt1 is in circ
def inCircle(pt1, circ):
# get the distance between pt1 and circ using the
# distance formula
dx = pt1.getX() - circ.getCenter().getX()
dy = pt1.getY() - circ.getCenter().getY()
dist = math.sqrt(dx*dx + dy*dy)
# check whether the distance is less than the radius
return dist <= circ.getRadius()
def main():
win = GraphWin("Click Speed", 700, 700)
# create a simple circle
circ = Circle(Point(350,350),50)
circ.setFill("red")
circ.draw(win)
while True:
mouse = win.getMouse()
if inCircle(mouse,circ):
print ("Good job")
main()
Oval
For the more advanced example of an ellipse we will need to use a formula found here. Here is the function implemting that:
def inOval(pt1, oval):
# get the radii
rx = abs(oval.getP1().getX() - oval.getP2().getX())/2
ry = abs(oval.getP1().getY() - oval.getP2().getY())/2
# get the center
h = oval.getCenter().getX()
k = oval.getCenter().getY()
# get the point
x = pt1.getX()
y = pt1.getY()
# use the formula
return (x-h)**2/rx**2 + (y-k)**2/ry**2 <= 1
Polygon
For a polygon of abitrary shape we need to reference this. I have converted that to a python equivalent for you. Check the link to see why it works because I am honestly not sure
def inPoly(pt1, poly):
points = poly.getPoints()
nvert = len(points) #the number of vertices in the polygon
#get x and y of pt1
x = pt1.getX()
y = pt1.getY()
# I don't know why this works
# See the link I provided for details
result = False
for i in range(nvert):
# note: points[-1] will give you the last element
# convenient!
j = i - 1
#get x and y of vertex at index i
vix = points[i].getX()
viy = points[i].getY()
#get x and y of vertex at index j
vjx = points[j].getX()
vjy = points[j].getY()
if (viy > y) != (vjy > y) and (x < (vjx - vix) * (y - viy) / (vjy - viy) + vix):
result = not result
return result

Categories