Hi I'm pretty new to the path-finding field and I have searched all around the interwebs for an answer to my question so I figured its time to ask the experts:
My environment consists of a 2D rectangular map with walls and units to traverse the environment
(no collision between units but they can't go through walls)
Units in the environment move in a turn-based fashion in a straight line but they can move at a speed of
0 to unit.movespeed (e.g. 5)(you have control over the movespeed for every movement). (map size is range is from 20,20 to 200,200 cells but you can move to floating-point locations add walls can be at a location like eg wall[(x=10.75,y=9.34),(x=33.56,y=62.43])
In summery every tick, you tell a unit to move to a destination (x,y) of type float
I have tried A* and goal-based vector pathfinding algorithms but the problem I keep running into is figuring out how fast they should move because obviously, the optimal movespeed should be the maximum speed yet if they always move at the maximum speed they are prone to hit a wall because these algorithms don't take into account variable movement speed.
Any Ideas?
image of movespeed issue with a* star and goal-based vector pathfinding problem
Code:
class Cell:
def __init__(self,coords,vector=None,distance=None,obstacle=False):
self.coords = coords
self.vector = vector
self.distance = distance
self.obstacle = obstacle
class VectorMap:
def __init__(self,unit, dest, map=HardCoded._make_map()):
self.size = Coords(len(map), len(map[0]))
self.map = map
VectorMap.map_converter()
self.unit = unit
self.dest = dest
def _create_vector_map(self):
return self._cell_def(1,self.map[self.dest])
def _cell_def(self,dist,current_cell):
neighbors = [Coords(0, 1), Coords(0, -1), Coords(1, 0), Coords(-1, 0), Coords(1, 1), Coords(1, -1),
Coords(-1, 1), Coords(-1, -1)]
for neighbor in neighbors:
#check if out of range of arr then return map
if current_cell.coords.y + neighbor.y < self.size[1] and current_cell.coords.x + neighbor.x < self.size[0]:
neighbor_cell = self.map[current_cell.coords.x + neighbor.x][current_cell.coords.y + neighbor.y]
if neighbor_cell.obstacle:
continue
neighbor_cell.distance = dist
#neighbor_cell.vector = current_cell
return self._cell_def(self.map,dist+1,neighbor_cell)
def map_converter(self):
nmap = []
for x,element in enumerate(self.map):
tmap = []
for y,value in enumerate(element):
tmap.append(Cell(Coords(x,y),vector=None,distance=None,obstacle=False if value == 0 else True))
nmap.append(tmap)
self.map = nmap
self._create_vector_map()
for x,c in enumerate(self.map):
for y,cell in enumerate(c):
cell.vector = Coords(0,0)
right_tile_distance = (self.map[x+1][y].distance if x+1 < self.size.x else self.map[x][y].distance)
up_tile_distance = (self.map[x][y+1].distance if y+1 < self.size.y else self.map[x][y].distance)
cell.vector.x = (self.map[x-1][y].distance if x is not 0 else self.map[x][y].distance) - right_tile_distance
cell.vector.y = up_tile_distance - (self.map[x][y-1].distance if y is not 0 else self.map[x][y].distance)
if cell.vector.x == 0 and right_tile_distance < self.map[x][y].distance:
cell.vector.x = 1
if cell.vector.y == 0 and up_tile_distance < self.map[x][y].distance:
cell.vector.y = 1
cell.vector = Util.normalize(cell.vector)
def vetor_move(self):
vector = self.map[self.unit.coords.x][self.unit.coords.y].vector
movespeed = self.desired_movespeed()
dest = Coords(vector.x * movespeed,vector.y * movespeed)
return Action(mechanic='move', params=[dest], report='Moving With Maths')
def desired_movespeed(self):
pass
class Util:
#staticmethod
def normalize(vector):
norm=np.linalg.norm(vector,ord=1)
if norm == 0:
norm = np.finfo(vector.dtype).eps
return Coords(*(vector/norm))
Related
def path(given_map, x, y):
x = given_map[0][0]
y = given_map[0][0]
cnt = 0
if x == len(given_map) and y == len(given_map):
cnt += 1
return cnt
else:
if x < len(given_map) and y < len(given_map):
return path(given_map, x, y)
elif x < len(given_map) and y == len(given_map):
return path(given_map, x, y)
elif x == len(given_map) and y < len(given_map):
return path(given_map, x, y)
else:
cnt = 0
return cnt
if __name__ == '__main__':
input_map = [[1,2,9,4,9],
[1,5,8,7,9],
[9,3,9,9,2],
[2,3,7,5,9],
[1,9,9,1,0]]
print(path(input_map, 0, 0))
input_map = [[1,1,2],
[1,2,2],
[1,2,0]]
print(path(input_map, 0, 0))
The n*n list must be input to create a function that returns the number of paths that can reach from the starting point [0][0] to the ending point [n-1][n-1].
Movement can only be done downward and right, and can only be moved in one direction by the number shown in the corresponding x and y coordinates.
It does not include cases outside the map after movement.
The above code is implemented as much as possible within my ability. How can I modify it to function normally?
Unless you are required to implement this using recursion, I would suggest a BFS (Breadth First Search) approach using a dictionary to track the positions/count that you have reached at each step of the progression:
def path(M):
result = 0 # final result
bounds = range(len(M)) # inside map
paths = {(0,0):1} # one path at start
while paths: # spread counts
nextPos = dict() # track next positions
for (x,y),count in paths.items(): # extend positon/count
if x == y == len(M)-1: result += count # capture final count
dist = M[x][y] # travel distance
if not dist: continue # no movement ...
for x2,y2 in [(x,y+dist),(x+dist,y)]: # travel down & right
if x2 in bounds and y2 in bounds: # propagate count
nextPos[x2,y2] = nextPos.get((x2,y2),0)+count
paths = nextPos # next step on paths
return result
input_map = [[1,2,9,4,9],
[1,5,8,7,9],
[9,3,9,9,2],
[2,3,7,5,9],
[1,9,9,1,0]]
print(path(input_map)) # 2
input_map = [[1,1,2],
[1,2,2],
[1,2,0]]
print(path(input_map)) # 1
If you are required to implement a recursive approach, the function can be adapted like this (returning the sum of recursive path counts) but then it won't be a BFS anymore:
def path(M,x=0,y=0):
if x == y == len(M)-1: return 1 # goal reached
dist = M[x][y] # travel distance
if not dist: return 0 # no movement
bounds = range(len(M)) # inside map
return sum(path(M,x2,y2) for x2,y2 in [(x,y+dist),(x+dist,y)]
if x2 in bounds and y2 in bounds) # sum of paths
A video by Sebastion Lague explained the Bridson's algorithm really well.
To oversimplify,
Create cell grid that has sides of radius/sqrt(2).
Place initial point and list as spawnpoint.
Place point into cell in grid.
For any spawnpoint, spawn a point between radius and 2*radius.
Look at the cells 2 units away from cell of new point.
If contains other points, compare distance.
If any point is closer to new point than the radius, new point is invalid.
If new point is valid, new point is listed as spawnpoint and placed into cell in grid.
If spawnpoint spawns too many invalid points, spawnpoint is removed and turns into point.
Repeat until no more spawnpoints exists.
Return points.
I basically written the same thing down in Python 3.7.2 and pygame 1.7~, but as said in the title, I'm stuck in recursive purgatory.
I used one Point() class for this algorithm, which might seem redundant given that pygame.Vector2() exists, but I needed some elements for a separate algorithm (Delaunay's with infinite vertices) that required this class to work.
For the sake of simplicity I'm going to cut away all the Delaunay-specific elements and show the bare-bones of this class that is needed for this algorithm:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def DistanceToSquared(self,other):
return (self.x-other.x)**2 + (self.y-other.y)**2
The code that is related to the Bridson's algorithm is:
def PoissonDiskSampling(width, height, radius, startPos = None, spawnAttempts = 10):
if startPos == None:
startPos = [width//2,height//2]
cellSize = radius / math.sqrt(2)
cellNumberX = int(width // cellSize + 1) # Initialise a cells grid for optimisation
cellNumberY = int(height // cellSize + 1)
cellGrid = [[None for x in range(cellNumberX)] for y in range(cellNumberY)]
startingPoint = Point(startPos[0],startPos[1]) # Add an iniial point for spawning purposes
cellGrid[startingPoint.x//radius][startingPoint.y//radius] = startingPoint
points = [startingPoint] # Initialise 2 lists tracking all points and active points
spawnpoints = [startingPoint]
while len(spawnpoints) > 0:
spawnIndex = random.randint(0,len(spawnpoints)-1)
spawnpoint = spawnpoints[spawnIndex]
spawned = False
for i in range(spawnAttempts):
r = random.uniform(radius,2*radius)
radian = random.uniform(0,2*math.pi)
newPoint = Point(spawnpoint.x + r*math.cos(radian),
spawnpoint.y + r*math.sin(radian))
if 0 <= newPoint.x <= width and 0 <= newPoint.y <= height:
isValid = True
else:
continue
newPointIndex = [int(newPoint.x//cellSize), int(newPoint.y//cellSize)]
neighbours = FindNeighbours(cellNumberX,cellNumberY,newPointIndex,cellGrid)
for neighbour in neighbours:
if newPoint.DistanceToSquared(neighbour) < radius**2:
isValid = False
break
if isValid:
points.append(newPoint)
spawnpoints.append(newPoint)
spawned = True
break
else:
continue
if spawned == False:
spawnpoints.remove(spawnpoint)
return points
def FindNeighbours(cellNumberX, cellNumberY, index, cellGrid):
neighbours = []
for cellX in range(max(0,(index[0]-2)), min(cellNumberX,(index[1]+2))):
for cellY in range(max(0,(index[0]-2)), min(cellNumberY,(index[1]+2))):
if cellGrid[cellX][cellY] != None:
neighbours.append(cellGrid[cellX][cellY])
return neighbours
Please help.
The probably most important step is missing in your code:
If new point is valid, new point is listed as spawnpoint and placed into cell in grid.
I suggest to add the point to the cellGrid if it is valid:
if isValid:
cellGrid[newPointIndex[0]][newPointIndex[1]] = newPoint
points.append(newPoint)
spawnpoints.append(newPoint)
spawned = True
break
Further, you have to verify if the cell with the index newPointIndex is not already occupied before a point can be add:
newPointIndex = [int(newPoint.x/cellSize), int(newPoint.y/cellSize)]
if cellGrid[newPointIndex[0]][newPointIndex[1]] != None:
continue
neighbours = FindNeighbours(cellNumberX,cellNumberY,newPointIndex,cellGrid)
Finally there is an issue in the function FindNeighbours. range(start, stop) creates a range for x in start <= x < stop.
So the stop has to be index[0]+3 rather than index[0]+2.
Further the ranges which control the 2 nested for loops, run both from x-2 to y+2 rather than from x-2 to x+2 respectively from y-2 to y+2:
for cellX in range(max(0,(index[0]-2)), min(cellNumberX,(index[1]+2))):
for cellY in range(max(0,(index[0]-2)), min(cellNumberY,(index[1]+2)))
The fixed function has to be:
def FindNeighbours(cellNumberX, cellNumberY, index, cellGrid):
neighbours = []
for cellX in range(max(0, index[0]-2), min(cellNumberX, index[0]+3)):
for cellY in range(max(0, index[1]-2), min(cellNumberY, index[1]+3)):
if cellGrid[cellX][cellY] != None:
neighbours.append(cellGrid[cellX][cellY])
return neighbours
See the result, for a size of 300 x 300 and a radius of 15:
An even better result can be achieve, if always the 1st point of spawnpoints is used to rather than a random point:
# spawnIndex = random.randint(0,len(spawnpoints)-1)
spawnIndex = 0 # 0 rather than random
spawnpoint = spawnpoints[spawnIndex]
So I'm trying to create a maze game with levels for a project at school. The code is a bit repetitive sorry I've only just started coding using pygame. When run the program should output a maze that once the user completes moves onto the next level - each level is randomly generated. However, only the first level is showing properly the rest of the levels appear to be a grid- which is making me think that the game is creating a new maze over the old.
I've pasted the code below - feel free to leave any advice on how to improve what I have :)
class Maze:
def __init__(self, rows=30, cols=40):
self.rows = rows
self.cols = cols
self.keep_going = 1
self.maze = {}
for y in range(rows):
for x in range(cols):
cell = {'south' : 1, 'east' : 1, 'visited': 0}
self.maze[(x,y)] = cell
def generate(self, start_cell=None, stack=[])
if start_cell is None:
start_cell = self.maze[(self.cols-1, self.rows-1)]
if not self.keep_going:
return
self.check_finished()
neighbors = []
# if the stack is empty, add the start cell
if len(stack) == 0:
stack.append(start_cell)
# set current cell to last cell
curr_cell = stack[-1]
# get neighbors and shuffle 'em up a bit
neighbors = self.get_neighbors(curr_cell)
shuffle(neighbors)
for neighbor in neighbors:
if neighbor['visited'] == 0:
neighbor['visited'] = 1
stack.append(neighbor)
self.knock_wall(curr_cell, neighbor)
self.generate(start_cell, stack)
def get_coords(self, cell):
# grabs coords of a given cell
coords = (-1, -1)
for k in self.maze:
if self.maze[k] is cell:
coords = (k[0], k[1])
break
return coords
def get_neighbors(self, cell):
# obvious
neighbors = []
(x, y) = self.get_coords(cell)
if (x, y) == (-1, -1):
return neighbors
north = (x, y-1)
south = (x, y+1)
east = (x+1, y)
west = (x-1, y)
if north in self.maze:
neighbors.append(self.maze[north])
if south in self.maze:
neighbors.append(self.maze[south])
if east in self.maze:
neighbors.append(self.maze[east])
if west in self.maze:
neighbors.append(self.maze[west])
return neighbors
def knock_wall(self, cell, neighbor):
# knocks down wall between cell and neighbor.
xc, yc = self.get_coords(cell)
xn, yn = self.get_coords(neighbor)
# Which neighbor?
if xc == xn and yc == yn + 1:
# neighbor's above, knock out south wall of neighbor
neighbor['south'] = 0
elif xc == xn and yc == yn - 1:
# neighbor's below, knock out south wall of cell
cell['south'] = 0
elif xc == xn + 1 and yc == yn:
# neighbor's left, knock out east wall of neighbor
neighbor['east'] = 0
elif xc == xn - 1 and yc == yn:
# neighbor's right, knock down east wall of cell
cell['east'] = 0
def check_finished(self):
# Checks if we're done generating
done = 1
for k in self.maze:
if self.maze[k]['visited'] == 0:
done = 0
break
if done:
self.keep_going = 0
[...] the rest of the levels appear to be a grid- which is making me think that the game is creating a new maze over the old.
The issue is caused by a common mistake in Python.
See Default Argument Values
Important warning: The default value is evaluated only once. This makes a difference when the default is a mutable object such as a list, dictionary, or instances of most classes
In your case the arguments to the method generate of class Maze has default arguments:
class Maze:
# [...]
def generate(self, start_cell=None, stack=[]):
# [...]
In the method generate elements are append to stack. The maze is generated trusting in the default argument:
self.maze_obj.generate(self.maze_obj.maze[(0,0)])
That causes that the 1st generation of the mace succeeds, but the following generation fails, because stack contains all the elements of the former generation process.
Pass an empty list to generate to solve the issue:
self.maze_obj.generate(self.maze_obj.maze[(0,0)])
self.maze_obj.generate(self.maze_obj.maze[(0,0)], [])
Or change the default argument to None:
class Maze:
# [...]
def generate(self, start_cell=None, stack=None):
if stack == None:
stack = []
A video by Sebastion Lague explained the Bridson's algorithm really well.
To oversimplify,
Create cell grid that has sides of radius/sqrt(2).
Place initial point and list as spawnpoint.
Place point into cell in grid.
For any spawnpoint, spawn a point between radius and 2*radius.
Look at the cells 2 units away from cell of new point.
If contains other points, compare distance.
If any point is closer to new point than the radius, new point is invalid.
If new point is valid, new point is listed as spawnpoint and placed into cell in grid.
If spawnpoint spawns too many invalid points, spawnpoint is removed and turns into point.
Repeat until no more spawnpoints exists.
Return points.
I basically written the same thing down in Python 3.7.2 and pygame 1.7~, but as said in the title, I'm stuck in recursive purgatory.
I used one Point() class for this algorithm, which might seem redundant given that pygame.Vector2() exists, but I needed some elements for a separate algorithm (Delaunay's with infinite vertices) that required this class to work.
For the sake of simplicity I'm going to cut away all the Delaunay-specific elements and show the bare-bones of this class that is needed for this algorithm:
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def DistanceToSquared(self,other):
return (self.x-other.x)**2 + (self.y-other.y)**2
The code that is related to the Bridson's algorithm is:
def PoissonDiskSampling(width, height, radius, startPos = None, spawnAttempts = 10):
if startPos == None:
startPos = [width//2,height//2]
cellSize = radius / math.sqrt(2)
cellNumberX = int(width // cellSize + 1) # Initialise a cells grid for optimisation
cellNumberY = int(height // cellSize + 1)
cellGrid = [[None for x in range(cellNumberX)] for y in range(cellNumberY)]
startingPoint = Point(startPos[0],startPos[1]) # Add an iniial point for spawning purposes
cellGrid[startingPoint.x//radius][startingPoint.y//radius] = startingPoint
points = [startingPoint] # Initialise 2 lists tracking all points and active points
spawnpoints = [startingPoint]
while len(spawnpoints) > 0:
spawnIndex = random.randint(0,len(spawnpoints)-1)
spawnpoint = spawnpoints[spawnIndex]
spawned = False
for i in range(spawnAttempts):
r = random.uniform(radius,2*radius)
radian = random.uniform(0,2*math.pi)
newPoint = Point(spawnpoint.x + r*math.cos(radian),
spawnpoint.y + r*math.sin(radian))
if 0 <= newPoint.x <= width and 0 <= newPoint.y <= height:
isValid = True
else:
continue
newPointIndex = [int(newPoint.x//cellSize), int(newPoint.y//cellSize)]
neighbours = FindNeighbours(cellNumberX,cellNumberY,newPointIndex,cellGrid)
for neighbour in neighbours:
if newPoint.DistanceToSquared(neighbour) < radius**2:
isValid = False
break
if isValid:
points.append(newPoint)
spawnpoints.append(newPoint)
spawned = True
break
else:
continue
if spawned == False:
spawnpoints.remove(spawnpoint)
return points
def FindNeighbours(cellNumberX, cellNumberY, index, cellGrid):
neighbours = []
for cellX in range(max(0,(index[0]-2)), min(cellNumberX,(index[1]+2))):
for cellY in range(max(0,(index[0]-2)), min(cellNumberY,(index[1]+2))):
if cellGrid[cellX][cellY] != None:
neighbours.append(cellGrid[cellX][cellY])
return neighbours
Please help.
The probably most important step is missing in your code:
If new point is valid, new point is listed as spawnpoint and placed into cell in grid.
I suggest to add the point to the cellGrid if it is valid:
if isValid:
cellGrid[newPointIndex[0]][newPointIndex[1]] = newPoint
points.append(newPoint)
spawnpoints.append(newPoint)
spawned = True
break
Further, you have to verify if the cell with the index newPointIndex is not already occupied before a point can be add:
newPointIndex = [int(newPoint.x/cellSize), int(newPoint.y/cellSize)]
if cellGrid[newPointIndex[0]][newPointIndex[1]] != None:
continue
neighbours = FindNeighbours(cellNumberX,cellNumberY,newPointIndex,cellGrid)
Finally there is an issue in the function FindNeighbours. range(start, stop) creates a range for x in start <= x < stop.
So the stop has to be index[0]+3 rather than index[0]+2.
Further the ranges which control the 2 nested for loops, run both from x-2 to y+2 rather than from x-2 to x+2 respectively from y-2 to y+2:
for cellX in range(max(0,(index[0]-2)), min(cellNumberX,(index[1]+2))):
for cellY in range(max(0,(index[0]-2)), min(cellNumberY,(index[1]+2)))
The fixed function has to be:
def FindNeighbours(cellNumberX, cellNumberY, index, cellGrid):
neighbours = []
for cellX in range(max(0, index[0]-2), min(cellNumberX, index[0]+3)):
for cellY in range(max(0, index[1]-2), min(cellNumberY, index[1]+3)):
if cellGrid[cellX][cellY] != None:
neighbours.append(cellGrid[cellX][cellY])
return neighbours
See the result, for a size of 300 x 300 and a radius of 15:
An even better result can be achieve, if always the 1st point of spawnpoints is used to rather than a random point:
# spawnIndex = random.randint(0,len(spawnpoints)-1)
spawnIndex = 0 # 0 rather than random
spawnpoint = spawnpoints[spawnIndex]
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)