In short, I want to make a radar of sorts, using specifically the graphics.py library, that detects if pre-drawn Shape exists at a Point.
Below I've drafted up a code that will show me all the points within a radar (and return it as an array), but I want to be able to take a point/coordinate and check if it is inside a Circle or Rectangle etc without knowing anything else (i.e only x and y parameters, no knowledge of other existing Shapes). Something like:
if Point(x,y).getObject() is None:
return False
or even
if Point(x,y).getObject().config["fill"] == "#f00":
return True
Here's what I've done so far:
from graphics import *
def getAllPointsInRadius(circle, win):
cx = int(circle.getCenter().getX())
cy = int(circle.getCenter().getY())
r = circle.getRadius()# + 2
points = []
print cx, cy
for i in range(cx-r, cx+r):
for j in range(cy-r, cy+r):
x = i-circle.getRadius()
y = j - circle.getRadius()
if ((i - cx) * (i - cx) + (j - cy) * (j - cy) <= r * r):
points.append(Point(i,j)) #if within, append as Point
p = Point(i, j)
p.setFill('#551A8B')
p.draw(win) #illustrate all points inside radar
else:
p = Point(i, j)
p.setFill('#0000ff')
p.draw(win) #points outside
return points
win = GraphWin('Radar', width = 500, height = 500)
win.setCoords(0, 0, 30, 30)
win.setBackground('black')
#object to be detected
objectCircle = Circle(Point(11, 12), 2)
objectCircle.setFill('#f00')
objectCircle.draw(win)
#radius within objects may be detected
radiusCircle = Circle(Point(10, 10), 3)
radiusCircle.setOutline('#fff')
radiusCircle.draw(win)
print getAllPointsInRadius(radiusCircle, win)
#print Point(11, 12).config["outline"]
win.getMouse()
Addendum: I think maybe by making Point(x,y) I'm actually creating a point and not returning the coordinates. I'm not sure how to do it otherwise, though.
Related
I'm making an implementation of Conway's Game of Life using matplotlib and numpy. However, I'm having lots of trouble trying to figure out how to make the grid interactable. Basically, whenever I click, the code checks whether the mouse is not on the grid (which would return a tuple None, None), and then using the mouse's position if it is on the grid the code sets the pixel of the grid closest to the mouse to ON (255). I tried to implement this using this function:
def mouse_move(event):
xy = event.x, event.y
return xy
and then in the function doing the updating of the grid:
def update(frameNum, img, grid, N, msx = 0, msy = 0):
# copy grid because we need 8 neighbors
# and we go line by line
newGrid = grid.copy()
# this is the part where I use the mouse pos
if ms.is_pressed('left') == True:
msx, msy = plt.connect('motion_notify_event', mouse_move)
print(msx, msy)
newGrid[msx, msy] = ON
# this is the end of the part where I use the mouse pos
for i in range(N):
for j in range(N):
# compute 8-neighbor sum using toroidal boundary conditions
# x and y wrap around so that sim is toroidal
total = int((grid[i, (j-1)%N] + grid[i, (j+1)%N] +
grid[(i-1)%N, j] + grid[(i+1)%N, j] +
grid[(i-1)%N, (j-1)%N] + grid[(i-1)%N, (j+1)%N] +
grid[(i+1)%N, (j-1)%N] + grid[(i+1)%N, (j+1)%N])/255)
# apply rules
if grid[i, j] == ON:
if (total < 2) or (total > 3):
newGrid[i, j] = OFF
else:
if (total == 3):
newGrid[i, j] = ON
# update data
img.set_data(newGrid)
grid[:] = newGrid[:]
return img
The issue here is that plt.connect() was only returning 1, 0 for some reason and not the mouse's position.
I then tried to ditch using motion_notify_event altogether and opted to use the mouse package:
def get_ms():
# the pixel dimensions of the grid are (200, 140) and (575, 510) on my screen
x, y = ms.get_position()
if (x > 575 or x < 200) and (y > 510 or y < 140):
return None, None
else:
return x, y
But this is where I'm stumped. Even though I have my mouse's pixel position, it isn't the grid position that I want. I know there has to be a way to do this with plt.connect('motion_notify_event', mouse_move) but I don't know what it is. I've already used basic debugging techniques. Am I doing something wrong?
Also, I've included the entire update() function in case I messed up there.
I want to finish my tower defense game as fast as I can.
I have trouble with if the tower is on the path with a given path list.
It will never work even is I try as hard as I can.
I already tried a lot of times to solve that problem like tech with tim's tower defense youtube tutorial. It made almost perfect sense why it was not working.
But no matter how hard I try, It seems to never work properly.
Find the link to the tutorial here.
WARNING:The video is pretty long.
x, y = tower.x, tower.y
for n, point in enumerate(path):
point_x, point_y = point[0], point[1]
dis_x = abs(point_x - x)
dis_y = abs(point_y - y)
dis = math.sqrt((dis_x - x)**2 + (dis_y - y)**2)
print(dis)
if dis < 130:
return False
return True
You might be thinking 'Why did I do this' so I changed it a bit:
import numpy as np
closest = []
x, y = tower.x, tower.y
for n, point in enumerate(path):
point_x, point_y = point[0], point[1]
dis_x = abs(point_x - x)
dis_y = abs(point_y - y)
dis = math.sqrt((dis_x - x)**2 + (dis_y - y)**2)
print(dis)
if len(closest) <= 2:
if dis < 130:
closest.append(point)
p1 = np.array([x, y])
p2 = np.array(closest[0])
p3 = np.array(closest[1])
dis = np.cross(p2-p1,p3-p1)/np.linalg.norm(p2-p1)
if dis < 90:
return False
return True
I did not recive any error messages, but you can still place towers on some spots on the path,
and you can't place towers on some points not on the path, and I was expecting it to be pretty neat.
As #ImperishableNight stated, the issue is that your function only compares each point on the path and checks if the distance is less than a certain threshold (130 pixels in your case). But this is not enough, since we are not only interested in the end points in each segment of the path, but also all of the points in between. For that, we need to calculate the distance between a point and a line segment.
I have written and commented on the following code using a simple Point class to replace whatever pygame provides. I break the problem up into a bunch of tiny functions to solve your problem. Please let me know if any of this is unclear.
import math
import random
class Point:
def __init__(self, x=0, y=0):
"""
A really simple Point class.
Replaces whatever pygame provides for this example.
"""
self.x = x
self.y = y
def __repr__(self):
"""
A convenient representation of a Point class.
So we see the x and y values when printing these objects.
"""
return "({0}, {1})".format(self.x, self.y)
def gen_rand_point(width, height):
"""
Creates random x and y values for a Point object
"""
rand_x = random.randint(0, width)
rand_y = random.randint(0, height)
point = Point(x=rand_x, y=rand_y)
return point
def gen_rand_point_list(width, height, num_points):
"""
Creates a list of random points using the previous function.
"""
points = []
for i in range(num_points):
point = gen_rand_point(width, height)
points.append(point)
return points
def points_to_segments(points, loop=False):
"""
Converts a list of points into a list of segments.
Offsets the point list and zips it to create "segments".
A segment is just a tuple containing two Point objects.
"""
starts = points
ends = points[1:] + [points[0]]
segments = list(zip(starts, ends))
if loop:
return segments
else:
return segments[:-1]
def calc_sqr_dist(point_a, point_b):
"""
Calculates the square distance between two points.
Can be useful to save a wasteful math.sqrt call.
"""
delta_x = point_b.x - point_a.x
delta_y = point_b.y - point_a.y
sqr_dist = (delta_x ** 2) + (delta_y ** 2)
return sqr_dist
def calc_dist(point_a, point_b):
"""
Calculates the distance between two points.
When you need a wasteful math.sqrt call.
"""
sqr_dist = calc_sqr_dist(point_a, point_b)
dist = math.sqrt(sqr_dist)
return dist
def calc_dot_product(segment_a, segment_b):
"""
Calculates the dot product of two segments.
Info about what the dot product represents can be found here:
https://math.stackexchange.com/q/805954
"""
a0, a1 = segment_a
b0, b1 = segment_b
ax = a1.x - a0.x
ay = a1.y - a0.y
bx = b1.x - b0.x
by = b1.y - b0.y
dot = (ax * bx) + (ay * by)
return dot
def calc_point_segment_dist(point, segment):
"""
Gets the distance between a point and a line segment.
Some explanation can be found here:
https://stackoverflow.com/a/1501725/2588654
"""
start, end = segment
sqr_dist = calc_sqr_dist(start, end)
#what if the segment's start and end are the same?
if sqr_dist == 0:
dist = calc_dist(point, start)
return dist
#what if it is not that easy?
else:
segment_a = (start, point)
segment_b = (start, end)#really is just segment...
dot = calc_dot_product(segment_a, segment_b)
t = float(dot) / sqr_dist
clamped_t = max(0, min(1, t))#clamps t to be just within the segment
#the interpolation is basically like a lerp (linear interpolation)
projection = Point(
x = start.x + (t * (end.x - start.x)),
y = start.y + (t * (end.y - start.y)),
)
dist = calc_dist(point, projection)
return dist
def calc_point_path_dist(point, path):
"""
Gets the distances between the point and each segment.
Then returns the minimum distance of all of these distances.
"""
dists = [calc_point_segment_dist(point, segment) for segment in path]
min_dist = min(dists)
return min_dist
if __name__ == "__main__":
"""
A fun example!
"""
width = 800
height = 600
num_path_points = 5
tower_range = 50
tower = gen_rand_point(width, height)
path_points = gen_rand_point_list(width, height, num_path_points)
path = points_to_segments(path_points)
dist = calc_point_path_dist(tower, path)
in_range = dist <= tower_range
print(dist, in_range)
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
So I've been trying to figure out how to find the distance between two objects on a canvas and I've exhausted most relevant links on Google with little success.
I'm trying to make it so that it calculates the distance between the drawn ovals and the line on the canvas.
from __future__ import division
from Tkinter import *
import tkMessageBox
class MyApp(object):
def __init__(self):
self.root = Tk()
self.root.wm_title("Escape")
self.canvas = Canvas(self.root, width=800, height=800, bg='white')
self.canvas.pack()
self.canvas.create_line(100, 100, 200, 200, fill='black')
self.canvas.bind("<B1-Motion>", self.tracer)
self.root.mainloop()
def tracer(self, e):
self.canvas.create_oval(e.x-5, e.y-5, e.x+5, e.y+5, fill='blue', outline='blue')
rx = "%d" % (e.x)
ry = "%d" % (e.y)
print rx, ry
MyApp()
Two circles:
dist = math.sqrt((circle1.x-circle2.x)**2 + (circle1.y-circle2.y)**2) - circle1.r - circle2.r
Kind of obvious, their Euclid distance is calculated using the Pythagorean theorem.
Point/segment:
a = segment[1].y - segment[0].y
b = segment[0].x - segment[1].x
c = - segment[0].x * a - segment[0].y * b
dx = segment[1].x - segment[0].x
dy = segment[1].y - segment[0].y
nc0 = - segment[0].x * dx - segment[0].y * dy
nc1 = - segment[1].x * dx - segment[1].y * dy
if ((dx * x + dy * y + nc0) < 0) dist = math.sqrt((x-segment[0].x)**2 + (y-segment[0].y)**2)
elif((dx * x + dy * y + nc1) < 0) dist = math.sqrt((x-segment[1].x)**2 + (y-segment[1].y)**2)
else dist = (a*x + b*y + c) / math.sqrt(a**2 + b**2)
Circle/segment - same as point/segment, just substract circle's radius
Polygon/polygon - Loop through each vertex of polygon 1 and segment of polygon 2, then loop through each vertex of polygon 2 and segment of polygon 1, then find the smallest.
Don't use magic numbers inside your code. That radius of 5 isn't good.
dist = math.sqrt((oval.x-point.x)**2 + (oval.y-point.y)**2)
not really sure if this answers your question but this is the distance formula
there are several problems with the original question.
at what point are you trying to find the distance?
you are not saving the oval positions so it will be very hard to look up later
why are you trying to get the distance?/what are you planning on doing with it?
are you trying to find distance to edge or distance to center?
in general I would guess you want to get it in the def trace function
step 1. Point of interest is (e.x,e.y)
step 2. Find the closest point on the line (line = (100,100) to (200,200) ) (this is probably a question in its own right. http://nic-gamedev.blogspot.com/2011/11/using-vector-mathematics-and-bit-of_08.html )
step 3. Apply distance method to point of interest and closest point on line
def dist(pt1,pt2):
return ((pt1.x-pt2.x)**2 + (pt1.y-pt2.y)**2)**.5
on another side note ... your ovals look an awful lot like circles ...
So I'm trying to write a Python implementation of the A* algorithm. My algorithm finds the path to the target without trouble, but when I get the program to visualize the closed and open lists, I notice that the closed list, whenever the obstacles are a little complicated, will balloon into a large, perfect diamond shape. In other words, my algorithm is adding nodes to the closed list that are significantly further from the target than any of the neighbors of the "expected" closed list should be.
To illustrate, when a point in the closed list is (2, 1) away from the target, but a wall is blocking the way, the algorithm will add both the node at (2, 2) away from the target (to try and get over the wall) and the algorithm at (3, 1) away from the target... for no good reason, since it's clearly further.
I'm not sure what I'm doing wrong. This distance calculation is meant to use the "Manhattan method" (imperfect for my purposes, but it shouldn't be causing such problems), but other methods continue to offer the same problem (or indeed worse problems).
def distance(self, tile1, tile2):
self.xDist = abs(tile1.col * TILE_SIZE - tile2.col * TILE_SIZE)
self.yDist = abs(tile1.row * TILE_SIZE - tile2.row * TILE_SIZE)
self.totalDist = self.straightCost * (self.xDist + self.yDist)
return self.totalDist
The code for filling the closed list looks like this. Here, .score2 is the H value, calculated using the distance method shown above.
while self.endTile not in self.openList:
self.smallestScore = MAX_DIST * 50
self.bestTile = None
for tile in self.openList:
if tile.score[2] <= self.smallestScore:
self.bestTile = tile
self.smallestScore = tile.score[2]
self.examine(self.bestTile)
The "examine" function adds the examined tile to the closed list and its viable neighbors to the open list, and seems to be working fine. The algorithm appears to be admitting all tiles with an H score of X (where X varies depending on the complexity of the maze the target is set in).
Slowing it down to a node-by-node visualization basically reveals that it is filling in an area of size X, and finds the path when the entrance into the maze is hit by the fill. I really have no clue what I'm doing wrong, and I've been trying to puzzle this out for two days now.
EDIT: in the interest of solving the problem, here is a fuller extract of code. This is the examine() function:
def examine(self, tile):
#Add the tile to the closed list, color it in, remove it from open list.
self.closedList.append(tile)
tile.color = CLOSED
pygame.draw.rect(windowSurface, tile.color, tile.rect)
pygame.display.update(tile.rect)
self.openList.remove(tile)
#Search all neighbors.
for a, b in ((tile.col + 1, tile.row), (tile.col - 1, tile.row),
(tile.col + 1, tile.row + 1), (tile.col + 1, tile.row - 1),
(tile.col - 1, tile.row + 1), (tile.col - 1, tile.row - 1),
(tile.col, tile.row + 1), (tile.col, tile.row - 1)):
#Ignore if out of range.
if a not in range(GRID_WIDTH) or b not in range(GRID_HEIGHT):
pass
#If the neighbor is pathable, add it to the open list.
elif self.tileMap[b][a].pathable and self.tileMap[b][a] not in self.openList and self.tileMap[b][a] not in self.closedList:
self.where = abs((a - tile.col)) + abs((b - tile.row))
if self.where == 0 or self.where == 2:
self.G = tile.score[1] + self.diagCost
self.H = self.distance(self.endTile, self.tileMap[b][a])
self.F = self.G + self.H
elif self.where == 1:
self.G = tile.score[1] + self.straightCost
self.H = self.distance(self.endTile, self.tileMap[b][a])
self.F = self.G + self.H
#Append to list and modify variables.
self.tileMap[b][a].score = (self.F, self.G, self.H)
self.tileMap[b][a].parent = tile
self.tileMap[b][a].color = OPEN
pygame.draw.rect(windowSurface, self.tileMap[b][a].color, self.tileMap[b][a].rect)
pygame.display.update(self.tileMap[b][a].rect)
self.openList.append(self.tileMap[b][a])
#If it's already in one of the lists, check to see if this isn't a better way to get to it.
elif self.tileMap[b][a] in self.openList or self.tileMap[b][a] in self.closedList:
self.where = abs((a - tile.col)) + abs((b - tile.row))
if self.where == 0 or self.where == 2:
if tile.score[1] + self.diagCost < self.tileMap[b][a].score[1]:
self.tileMap[b][a].score = (self.tileMap[b][a].score[0], tile.score[1] + self.diagCost, self.tileMap[b][a].score[2])
self.tileMap[b][a].parent = tile
print "parent changed!"
elif self.where == 1:
if tile.score[1] + self.straightCost < self.tileMap[b][a].score[1]:
self.tileMap[b][a].score = (self.tileMap[b][a].score[0], tile.score[1] + self.straightCost, self.tileMap[b][a].score[2])
self.tileMap[b][a].parent = tile
print "parent changed!"
And this is a newer version of the iteration that slows down so I can watch it progress. It is meant to find the node with the lowest score[0] (score is a tuple of F, G, H scores).
def path(self):
self.openList.append(self.startTile)
self.startTile.score = (self.distance(self.startTile, self.endTile), 0, self.distance(self.startTile, self.endTile))
self.examine(self.openList[0])
self.countDown = 0
while self.endTile not in self.openList:
if self.countDown <= 0:
self.countDown = 5000
self.smallestScore = MAX_DIST * 50
self.bestTile = self.startTile
for tile in self.openList:
if tile.score[0] <= self.smallestScore:
self.bestTile = tile
self.smallestScore = tile.score[0]
self.examine(self.bestTile)
else:
self.countDown -= timePassed
The following is an image illustrating the excess search area, using self.totalDist = self.diagCost * math.sqrt(pow(self.xDist, 2) + pow(self.yDist, 2))
Alternatively, removing the TILE_SIZE constant from the equation gets me this equally inefficient-looking search area:
It seems to me that it shouldn't be searching all that extra area - just the area immediately surrounding the obstacles.
My theory: it's because Manhattan distance is not admissible in this case, because you can move diagonal as well.
Try this:
def distance(self, tile1, tile2):
self.xDist = abs(tile1.col * TILE_SIZE - tile2.col * TILE_SIZE)
self.yDist = abs(tile1.row * TILE_SIZE - tile2.row * TILE_SIZE)
self.totalDist = self.diagCost * math.sqrt(self.xDist*self.xDist + self.yDist*self.yDist)
# or it might be self.straightCost, depending on their values.
# self.diagCost is probably right, though.
return self.totalDist