-2

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.

Song
  • 298
  • 5
  • 20
  • It isn't that easy.I changed the number 130 in if dis < 130 but it never worked, even if I tried all the possible numbers. help [link](https://stackoverflow.com/questions/39840030/distance-between-point-and-a-line-from-two-points) – Song Jun 27 '19 at 18:44
  • What is your expected result? And where pygame enters in this question? – Valentino Jun 27 '19 at 19:50
  • 1
    I think you have actually identified one aspect of your problem: What you need is probably the distance from a point to a *line segment*, instead of to another point. – Imperishable Night Jun 27 '19 at 20:25
  • However, without some examples inputs/outputs it would be very hard for the community to help you out. So provide them! – Imperishable Night Jun 27 '19 at 20:26
  • My second try was this: – Song Jun 28 '19 at 13:26
  • 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 – Song Jun 28 '19 at 13:28
  • That might be a mess to you – Song Jun 28 '19 at 13:28
  • more answers please – Song Jul 01 '19 at 11:41

1 Answers1

1

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)
CodeSurgeon
  • 2,435
  • 2
  • 15
  • 36