Example #1
0
    def shell_will_hit(self, shell, target, factor=1):
        """
        Returns True if shell will hit rectangular object
        """
        b = shell.angle
        e = Vector(1, 0)
        q = e.rotate(b)

        center = Vector(target.x - shell.x, target.y - shell.y)
        if center.scalar_product(q) < 0:
            return False

        a = target.angle

        c1 = center + Vector(target.width / 2 * factor,
                             target.height / 2 * factor).rotate(a)
        c2 = center + Vector(-target.width / 2 * factor,
                             target.height / 2 * factor).rotate(a)
        c3 = center + Vector(-target.width / 2 * factor,
                             -target.height / 2 * factor).rotate(a)
        c4 = center + Vector(target.width / 2 * factor,
                             -target.height / 2 * factor).rotate(a)
        if sign(c1.cross_product(q)) == sign(q.cross_product(c3)):
            #print("TEST", c1, c2, c3, c4, q)
            return True
        if sign(c2.cross_product(q)) == sign(q.cross_product(c4)):
            #print("TEST", c1, c2, c3, c4, q)
            return True
        return False
Example #2
0
    def will_hit_precise(self, tank, target, ricochet_angle=PI/4, factor=1, side_part=1):
        """
        Returns True if tank will hit rectangular object
        """
        b = tank.angle + tank.turret_relative_angle
        e = Vector(1, 0)
        q = e.rotate(b)

        tank_v = Vector(tank.x, tank.y)

        c = get_unit_corners(target, factor)

        hit_v = tank_v + MAX_DISTANCE * q

        def will_hit_side(p1, p2):
            p_mid = (p1 + p2)/2
            p1_new = p_mid + (p1 - p_mid) * side_part
            p2_new = p_mid + (p2 - p_mid) * side_part
            intersecting = segments_are_intersecting(tank_v, hit_v, p1_new, p2_new)
            angle = q.angle(p2 - p1)
            safe = ricochet_angle < angle < PI - ricochet_angle
            return intersecting and safe

        sides = [(c[0], c[1]), (c[1], c[2]), (c[2], c[3]), (c[3], c[0])]
        closest_corner = min(c, key=lambda x: x.distance(tank_v))
        closer_sides = list(filter(lambda s: s[0] == closest_corner or s[1] == closest_corner, sides))


        #result = will_hit_side(c1, c2) or will_hit_side(c2, c3) or will_hit_side(c3, c4) or will_hit_side(c4, c1)
        result = will_hit_side(closer_sides[0][0], closer_sides[0][1]) or will_hit_side(closer_sides[1][0], closer_sides[1][1])
        return result
Example #3
0
    def save_target_init_values(self, tank, x, y):
        if not self.enabled:
            return

        dist = tank.get_distance_to(x, y)
        vt = Vector(tank.speedX, tank.speedY)

        tank_v = Vector(tank.x, tank.y)
        pt_v = Vector(x, y)
        d = pt_v - tank_v

        tank_d_v = Vector(1, 0).rotate(tank.angle)

        if vt.is_zero():
            vd_angle = 0
        else:
            vd_angle = vt.angle(d)

        if d.is_zero():
            self.last_target_time_init_values = (0, 0, 0, 0, 0, 0)

        self.last_target_time_init_values = (tank_d_v.angle(d), dist, vd_angle,
                                             vt.length(), tank.angular_speed,
                                             tank.crew_health /
                                             tank.crew_max_health)
    def positions(self, tank, world):
        positions = []

        # Current position
        positions.append(Position(tank.x, tank.y, "CURRENT"))

        # Forward and back direction
        tank_v = Vector(tank.x, tank.y)
        tank_d_v = Vector(1, 0).rotate(tank.angle)
        forw = tank_v + tank_d_v * self.short_direction
        back = tank_v - tank_d_v * self.short_direction
        positions.append(Position(forw.x, forw.y, "FORWARD"))
        positions.append(Position(back.x, back.y, "BACKWARD"))

        fforw = tank_v + tank_d_v * self.long_direction
        fback = tank_v - tank_d_v * self.long_direction
        positions.append(Position(fforw.x, fforw.y, "FAR FORWARD"))
        positions.append(Position(fback.x, fback.y, "FAR BACKWARD"))

        # Low diagonal move
        DIAGONAL_ANGLE = PI / 6
        for a in [
                DIAGONAL_ANGLE, -DIAGONAL_ANGLE, PI - DIAGONAL_ANGLE,
                DIAGONAL_ANGLE - PI
        ]:
            pt = tank_v + tank_d_v.rotate(a) * 100
            positions.append(Position(pt.x, pt.y, "TURN %.0f" % degrees(a)))

        # Bonuses positions
        positions += [
            Position(b.x, b.y, "BONUS %s" % bonus_name_by_type(b.type))
            for b in world.bonuses
        ]

        return positions
Example #5
0
    def shell_will_hit(self, shell, target, factor=1):
        """
        Returns True if shell will hit rectangular object
        """
        b = shell.angle
        e = Vector(1, 0)
        q = e.rotate(b)

        center = Vector(target.x - shell.x, target.y - shell.y)
        if center.scalar_product(q) < 0:
            return False

        a = target.angle

        c1 = center + Vector(target.width/2 * factor, target.height/2 * factor).rotate(a)
        c2 = center + Vector(- target.width/2 * factor, target.height/2 * factor).rotate(a)
        c3 = center + Vector(- target.width/2 * factor, - target.height/2 * factor).rotate(a)
        c4 = center + Vector(target.width/2 * factor, - target.height/2 * factor).rotate(a)
        if sign(c1.cross_product(q)) == sign(q.cross_product(c3)):
            #print("TEST", c1, c2, c3, c4, q)
            return True
        if sign(c2.cross_product(q)) == sign(q.cross_product(c4)):
            #print("TEST", c1, c2, c3, c4, q)
            return True
        return False
    def positions(self, tank, world):
        positions = []

        # Current position
        positions.append(Position(tank.x, tank.y, "CURRENT"))

        # Forward and back direction
        tank_v = Vector(tank.x, tank.y)
        tank_d_v = Vector(1, 0).rotate(tank.angle)
        forw = tank_v + tank_d_v * self.short_direction
        back = tank_v - tank_d_v * self.short_direction
        positions.append(Position(forw.x, forw.y, "FORWARD"))
        positions.append(Position(back.x, back.y, "BACKWARD"))

        fforw = tank_v + tank_d_v * self.long_direction
        fback = tank_v - tank_d_v * self.long_direction
        positions.append(Position(fforw.x, fforw.y, "FAR FORWARD"))
        positions.append(Position(fback.x, fback.y, "FAR BACKWARD"))

        # Low diagonal move
        DIAGONAL_ANGLE = PI/6
        for a in [DIAGONAL_ANGLE, - DIAGONAL_ANGLE, PI - DIAGONAL_ANGLE, DIAGONAL_ANGLE - PI]:
            pt = tank_v + tank_d_v.rotate(a) * 100
            positions.append(Position(pt.x, pt.y, "TURN %.0f" % degrees(a)))

        # Bonuses positions
        positions += [Position(b.x, b.y, "BONUS %s" % bonus_name_by_type(b.type)) for b in world.bonuses]

        return positions
Example #7
0
 def __init__(self, amb, dif, spec, ref=0):
     self.baseColor = Vector((255, 255, 255))
     self.otherColor = Vector((0, 0, 0))
     self.amb = amb
     self.dif = dif
     self.spec = spec
     self.checkSize = 1
     self.ref = ref
Example #8
0
    def get_new_positions(self, pos, tank):
        x, y = pos[:2]
        tank_v = Vector(tank.x, tank.y)
        p = Vector(x, y)
        d = p - tank_v

        p1 = tank_v + d / 2 + d.normalize().rotate(PI / 2) * SHIFT_DISTANCE
        p2 = tank_v + d / 2 - d.normalize().rotate(PI / 2) * SHIFT_DISTANCE
        return [(p1.x, p1.y, pos[2] + " $L"), (p2.x, p2.y, pos[2] + " $R")]
def draw_tank(tank):
    draw_unit(tank)

    b = tank.angle + tank.turret_relative_angle
    e = Vector(1, 0)
    q = e.rotate(b)

    tank_v = Vector(tank.x, tank.y)
    hit_v = tank_v + MAX_DISTANCE * q

    pygame.draw.line(window, (255, 0, 0), (tank.x, tank.y), (hit_v.x, hit_v.y))
Example #10
0
def draw_tank(tank):
    draw_unit(tank)

    b = tank.angle + tank.turret_relative_angle
    e = Vector(1, 0)
    q = e.rotate(b)

    tank_v = Vector(tank.x, tank.y)
    hit_v = tank_v + MAX_DISTANCE * q

    pygame.draw.line(window, (255, 0, 0), (tank.x, tank.y), (hit_v.x, hit_v.y))
    def _get_danger(self, pos, tank1, tank2):
        tank1_v = Vector(tank1.x, tank1.y)
        tank2_v = Vector(tank2.x, tank2.y)
        tank_v = Vector(pos.x, pos.y)

        d1 = (tank1_v - tank_v).normalize()
        d2 = (tank2_v - tank_v).normalize()
        # the more 'danger' is, the more dangerous position is. Values: [0..1]
        danger = fabs(1 - d1.scalar_product(d2)) / 2

        return sqrt(danger)
    def positions(self, tank, world):
        positions = []

        # Low diagonal move
        tank_v = Vector(tank.x, tank.y)
        tank_d_v = Vector(1, 0).rotate(tank.angle)

        for a in [self.angle, -self.angle, PI - self.angle, self.angle - PI]:
            pt = tank_v + tank_d_v.rotate(a) * 100
            positions.append(Position(pt.x, pt.y, "TURN %.0f" % degrees(a)))
        return positions
    def positions(self, tank, world):
        positions = []

        # Low diagonal move
        tank_v = Vector(tank.x, tank.y)
        tank_d_v = Vector(1, 0).rotate(tank.angle)

        for a in [self.angle, - self.angle, PI - self.angle, self.angle - PI]:
            pt = tank_v + tank_d_v.rotate(a) * 100
            positions.append(Position(pt.x, pt.y, "TURN %.0f" % degrees(a)))
        return positions
Example #14
0
        def process_bullets():
            b = tank.angle + tank.turret_relative_angle
            q = Vector(1, 0).rotate(b)
            tank_v = Vector(tank.x, tank.y)
            for shell in world.shells:
                shell_v = Vector(shell.x, shell.y)
                shell_speed = Vector(shell.speedX, shell.speedY)

                def will_meet():
                    if q.collinear(shell_speed):
                        #TODO: may be bug
                        good_direction = sign(shell_speed.x) != sign(
                            q.x) and sign(shell_speed.y) != sign(q.y)
                        if good_direction:
                            return (True, (shell_v - tank_v).length() /
                                    (shell_speed.length() +
                                     INITIAL_SHELL_VELOCITY))
                        else:
                            return (False, -1)
                    else:
                        d_tank, d_shell = intersect_lines(
                            tank_v, q, shell_v, shell_speed.normalize())
                        d_tank = d_tank - 40
                        if d_tank < 0 or d_shell < 0:
                            return (False, -1)

                        t_tank = solve_quadratic(SHELL_ACCELERATION / 2,
                                                 INITIAL_SHELL_VELOCITY,
                                                 -d_tank)
                        t_shell = solve_quadratic(SHELL_ACCELERATION / 2,
                                                  shell_speed.length(),
                                                  -d_shell)
                        if fabs(t_tank - t_shell) < 1.5:
                            return (True, t_tank)
                        else:
                            return (False, -1)

                wm, meet_time = will_meet()
                if wm:
                    if shell.player_name == "evernight":
                        self.context.debug(
                            '{Shooting} Hitting bullet of ally, postpone')
                        move.fire_type = FireType.NONE
                    else:
                        if physics.shell_will_hit(
                                shell, tank, factor=0.9
                        ) and meet_time < 20 and meet_time > 5:
                            self.context.debug(
                                '{Shooting} Possible to counter flying bullet')
                            move.fire_type = FireType.REGULAR
Example #15
0
    def shell_will_hit_tank_going_to(self, shell, tank, x, y, et=None):
        if et is None:
            et = self.estimate_time_to_position(x, y, tank)

        dist = tank.get_distance_to(x, y)

        v0 = hypot(shell.speedX, shell.speedY)
        a = SHELL_ACCELERATION
        d = tank.get_distance_to_unit(shell)
        #d = shell.get_distance_to(x, y)
        t = solve_quadratic(a / 2, v0, -d)

        if self.shell_will_hit(shell, tank, factor=1.05) and (et > t):
            return 1

        #self.max_move_distance(fabs(v0), FICTIVE_ACCELERATION, 3, t) < dist):

        if dist < 150:
            # short distance
            result = self.shell_will_hit(shell,
                                         fictive_unit(tank, x, y),
                                         factor=1.05)
            if result:
                pt_v = Vector(x, y)
                tank_v = Vector(tank.x, tank.y)
                dir = pt_v - tank_v
                shell_speed = Vector(shell.speedX, shell.speedY)
                if dir.is_zero() or shell_speed.is_zero():
                    return float(result)
                if dir.angle(shell_speed) < PI / 8 and shell.get_distance_to(
                        x, y) > d:
                    return 0.6

            return result

        else:
            # long distance, check if our direction is intersecting segment between shell and shell + v_shell*t
            pt_v = Vector(x, y)
            tank_v = Vector(tank.x, tank.y)
            dir = tank_v - pt_v
            shell_v = Vector(shell.x, shell.y)
            shell_speed = Vector(shell.speedX, shell.speedY)
            next_shell = shell_v + shell_speed * (t + 5)
            if sign((shell_v - tank_v).cross_product(dir)) == sign(
                    dir.cross_product(next_shell - tank_v)):
                return True
            else:
                return False
Example #16
0
 def store_shell_velocity(self, world):
     if not self.enabled:
         return
     for shell in world.shells:
         self.shell_velocity[shell.id].append(
             Vector(shell.speedX, shell.speedY).length())
     if world.tick % 100 == 0:
         pickle.dump(self.shell_velocity, open('shells.dump', 'wb'))
Example #17
0
    def vector_is_intersecting_object(self, p, d, target, factor=1):
        center = Vector(target.x - p.x, target.y - p.y)
        if center.scalar_product(d) < 0:
            return False

        a = target.angle

        w, h = target.width/2 * factor, target.height/2 * factor
        c1 = center + Vector(w ,h).rotate(a)
        c2 = center + Vector(- w, h).rotate(a)
        c3 = center + Vector(- w, - h).rotate(a)
        c4 = center + Vector(w, - h).rotate(a)
        if sign(c1.cross_product(d)) == sign(d.cross_product(c3)):
            return True
        if sign(c2.cross_product(d)) == sign(d.cross_product(c4)):
            return True
        return False
Example #18
0
    def estimate_time_to_position(self, x, y, tank):
        dist = tank.get_distance_to(x, y)
        vt = Vector(tank.speedX, tank.speedY)

        tank_v = Vector(tank.x, tank.y)
        pt_v = Vector(x, y)
        d = pt_v - tank_v

        if d.is_zero():
            return 0

        tank_d_v = Vector(1, 0).rotate(tank.angle)

        if vt.is_zero() or d.is_zero():
            vd_angle = 0
        else:
            vd_angle = vt.angle(d)

        if tank_d_v.is_zero() or d.is_zero():
            d_angle = 0
        else:
            d_angle = tank_d_v.angle(d)

        # Short distances fix
        if dist < 100:
            if fabs(d_angle) < LOW_ANGLE:
                v0 = vt.projection(d)
                return solve_quadratic(FICTIVE_ACCELERATION/2, v0, -dist)
                #return dist/6
            elif PI - fabs(d_angle) < LOW_ANGLE:
                v0 = vt.projection(d)
                return solve_quadratic(FICTIVE_ACCELERATION/2 * 0.75, v0, -dist)
                #return dist/6 /0.75

        if d.is_zero():
            values = (0, 0, 0, 0, 0, 0, 0, 1)
        else:

            values = (
                d_angle,
                dist,
                vd_angle,
                vt.length(),
                tank.angular_speed,
                tank.crew_health/tank.crew_max_health,
                d_angle ** 2,
                1
                )

        return sum([x * y for (x, y) in zip(values, TIME_ESTIMATION_COEF)])
    def value(self, target):
        tank = self.context.tank

        target_v = Vector(target.x, target.y)
        tank_v = Vector(tank.x, tank.y)
        penalty = 0
        obstacle = self.context.world.obstacles[0]
        if self.context.physics.vector_is_intersecting_object(
                tank_v, target_v - tank_v, obstacle, 1.05):
            penalty = self.max_value
        #TODO: fix this


#        if obstacle_is_attacked(self.context, target):
#            penalty = self.max_value
#        else:
#            penalty = 0
        return -penalty
Example #20
0
    def value(self, pos):
        enemies = self.context.enemies
        tank = self.context.tank

        pos_v = Vector(pos.x, pos.y)
        result = 0
        obstacle = self.context.world.obstacles[0]
        for enemy in enemies:
            enemy_v = Vector(enemy.x, enemy.y)

            if self.context.physics.vector_is_intersecting_object(
                    pos_v, enemy_v - pos_v, obstacle, 1.05):
                if self.context.health_fraction <= 0.3 or tank.remaining_reloading_time > 100:
                    result += self.max_value
                else:
                    result -= self.max_value

        return result
    def positions(self, tank, world):
        positions = []

        # Forward and back direction
        tank_v = Vector(tank.x, tank.y)
        tank_d_v = Vector(1, 0).rotate(tank.angle)

        forw = tank_v + tank_d_v * 40
        back = tank_v - tank_d_v * 40
        positions.append(Position(forw.x, forw.y, "FORWARD"))
        positions.append(Position(back.x, back.y, "BACKWARD"))

        fforw = tank_v + tank_d_v * 80
        fback = tank_v - tank_d_v * 80
        positions.append(Position(fforw.x, fforw.y, "FAR FORWARD"))
        positions.append(Position(fback.x, fback.y, "FAR BACKWARD"))

        return positions
Example #22
0
 def getTangentVectorAt(self, t):
     """
     Returns the tangent vector at value
     :param t: parameter value, between 0 and 1
     :return: unit tangent vector at t
     """
     x = 2*(1 - t)*(self.p1.x - self.p0.x) + 2*t*(self.p2.x - self.p1.x)
     y = 2*(1 - t)*(self.p1.y - self.p0.y) + 2*t*(self.p2.y - self.p1.y)
     return Vector(x, y).normalize()
Example #23
0
    def test_case_4(self):
        groove_area = [
            Vector(0, 10),
            Vector(100, 10),
            Vector(0, -10),
            Vector(100, -10)
        ]
        lines = GrooveEvaluator.calculate_groove_passes(groove_area,
                                                        True,
                                                        tool_thickness=8,
                                                        tool_interlock=1)
        expected_lines = [
            Line(Vector(0, -6), Vector(100, -6)),
            Line(Vector(0, 6), Vector(100, 6)),
            Line(Vector(0, 1), Vector(100, 1))
        ]

        self.assertListEqual(expected_lines, lines)
Example #24
0
    def shell_will_hit_tank_going_to(self, shell, tank, x, y, et=None):
        if et is None:
            et = self.estimate_time_to_position(x, y, tank)

        dist = tank.get_distance_to(x, y)

        v0 = hypot(shell.speedX, shell.speedY)
        a = SHELL_ACCELERATION
        d = tank.get_distance_to_unit(shell)
        #d = shell.get_distance_to(x, y)
        t = solve_quadratic(a/2, v0, -d)

        if self.shell_will_hit(shell, tank, factor=1.05) and (et > t):
            return 1

        #self.max_move_distance(fabs(v0), FICTIVE_ACCELERATION, 3, t) < dist):

        if dist < 150:
            # short distance
            result = self.shell_will_hit(shell, fictive_unit(tank, x, y), factor=1.05)
            if result:
                pt_v = Vector(x, y)
                tank_v = Vector(tank.x, tank.y)
                dir = pt_v - tank_v
                shell_speed = Vector(shell.speedX, shell.speedY)
                if dir.is_zero() or shell_speed.is_zero():
                    return float(result)
                if dir.angle(shell_speed) < PI/8 and shell.get_distance_to(x, y) > d:
                    return 0.6

            return result

        else:
            # long distance, check if our direction is intersecting segment between shell and shell + v_shell*t
            pt_v = Vector(x, y)
            tank_v = Vector(tank.x, tank.y)
            dir = tank_v - pt_v
            shell_v = Vector(shell.x, shell.y)
            shell_speed = Vector(shell.speedX, shell.speedY)
            next_shell = shell_v + shell_speed * (t + 5)
            if sign((shell_v - tank_v).cross_product(dir)) == sign(dir.cross_product(next_shell - tank_v)):
                return True
            else:
                return False
Example #25
0
    def position_is_blocked(self, x, y, tank, world):
        #if tank.get_distance_to(x, y) > 400:
        #    return False
        tank_v = Vector(tank.x, tank.y)
        p = Vector(x, y)
        dist = tank.get_distance_to(x, y)
        if (p - tank_v).is_zero():
            return False

        obstacles = chain(filter(NOT_TANK(tank.id), world.tanks),
                          world.obstacles)
        for obj in obstacles:
            obj_dist = tank.get_distance_to_unit(obj)
            if self.vector_is_intersecting_object(
                    tank_v, p - tank_v, obj, factor=1.2) and obj_dist < dist:
                if not is_going_to_move(obj):
                    return obj
                if obj_dist < max(obj.width, obj.height) + 50:
                    return obj
        return False
Example #26
0
    def save_target_init_values(self, tank, x, y):
        if not self.enabled:
            return

        dist = tank.get_distance_to(x, y)
        vt = Vector(tank.speedX, tank.speedY)

        tank_v = Vector(tank.x, tank.y)
        pt_v = Vector(x, y)
        d = pt_v - tank_v

        tank_d_v = Vector(1, 0).rotate(tank.angle)

        if vt.is_zero():
            vd_angle = 0
        else:
            vd_angle = vt.angle(d)

        if d.is_zero():
            self.last_target_time_init_values = (0, 0, 0, 0, 0, 0)

        self.last_target_time_init_values = (
            tank_d_v.angle(d),
            dist,
            vd_angle,
            vt.length(),
            tank.angular_speed,
            tank.crew_health/tank.crew_max_health
        )
Example #27
0
    def will_hit_precise(self,
                         tank,
                         target,
                         ricochet_angle=PI / 4,
                         factor=1,
                         side_part=1):
        """
        Returns True if tank will hit rectangular object
        """
        b = tank.angle + tank.turret_relative_angle
        e = Vector(1, 0)
        q = e.rotate(b)

        tank_v = Vector(tank.x, tank.y)

        c = get_unit_corners(target, factor)

        hit_v = tank_v + MAX_DISTANCE * q

        def will_hit_side(p1, p2):
            p_mid = (p1 + p2) / 2
            p1_new = p_mid + (p1 - p_mid) * side_part
            p2_new = p_mid + (p2 - p_mid) * side_part
            intersecting = segments_are_intersecting(tank_v, hit_v, p1_new,
                                                     p2_new)
            angle = q.angle(p2 - p1)
            safe = ricochet_angle < angle < PI - ricochet_angle
            return intersecting and safe

        sides = [(c[0], c[1]), (c[1], c[2]), (c[2], c[3]), (c[3], c[0])]
        closest_corner = min(c, key=lambda x: x.distance(tank_v))
        closer_sides = list(
            filter(lambda s: s[0] == closest_corner or s[1] == closest_corner,
                   sides))

        #result = will_hit_side(c1, c2) or will_hit_side(c2, c3) or will_hit_side(c3, c4) or will_hit_side(c4, c1)
        result = will_hit_side(closer_sides[0][0],
                               closer_sides[0][1]) or will_hit_side(
                                   closer_sides[1][0], closer_sides[1][1])
        return result
    def value(self, target):
        tank = self.context.tank

        tank_v = Vector(tank.x, tank.y)
        target_v = Vector(target.x, target.y)

        #b = tank.angle + tank.turret_relative_angle
        b = (target_v - tank_v).angle(Vector(1, 0))

        target_direction = Vector(1, 0).rotate(target.angle)
        target_speed = Vector(target.speedX, target.speedY)

        def get_hit_time():
            v0 = INITIAL_SHELL_VELOCITY
            a = SHELL_ACCELERATION
            d = max(0, tank.get_distance_to_unit(target) - 60)
            return solve_quadratic(a / 2, v0, -d)

        def max_move_distance(v0, a, max_v, t):
            v0 = max(-max_v, min(v0, max_v))
            if fabs(v0 + a * t) > max_v:
                if a > 0:
                    t1 = fabs((max_v - v0) / a)
                else:
                    t1 = fabs((-max_v - v0) / a)
                t2 = t - t1
            else:
                t1 = t
                t2 = 0
            if a > 0:
                return a * t1**2 / 2 + v0 * t1 + max_v * t2
            else:
                return a * t1**2 / 2 + v0 * t1 - max_v * t2

        t = get_hit_time()

        v0 = target_speed.projection(target_direction)
        target_health_fraction = target.crew_health / target.crew_max_health
        efficency = (1 + target_health_fraction) / 2

        target_avoid_distance_forward = max_move_distance(
            v0, FICTIVE_TARGET_ACCELERATION * efficency,
            MAX_TARGET_SPEED * efficency, t)
        target_avoid_distance_backward = max_move_distance(
            v0, -FICTIVE_TARGET_ACCELERATION * 0.75 * efficency,
            MAX_TARGET_SPEED * efficency, t)

        target_turret_n_cos = fabs(cos(fabs(b - target.angle) + PI / 2))

        var = fabs(
            (target_avoid_distance_forward - target_avoid_distance_backward) *
            target_turret_n_cos)

        return (1 - var / 200) * self.max_value
    def positions(self, tank, world):
        positions = []

        # Current position
        positions.append(Position(tank.x, tank.y, "CURRENT"))

        # Forward and back direction
        tank_v = Vector(tank.x, tank.y)
        tank_d_v = Vector(1, 0).rotate(tank.angle)

        fforw = tank_v + tank_d_v * 80
        fback = tank_v - tank_d_v * 80
        positions.append(Position(fforw.x, fforw.y, "FAR FORWARD"))
        positions.append(Position(fback.x, fback.y, "FAR BACKWARD"))

        # Bonuses positions
        positions += [
            Position(b.x, b.y, "BONUS %s" % bonus_name_by_type(b.type))
            for b in world.bonuses
        ]

        return positions
Example #30
0
    def vector_is_intersecting_object(self, p, d, target, factor=1):
        center = Vector(target.x - p.x, target.y - p.y)
        if center.scalar_product(d) < 0:
            return False

        a = target.angle

        w, h = target.width / 2 * factor, target.height / 2 * factor
        c1 = center + Vector(w, h).rotate(a)
        c2 = center + Vector(-w, h).rotate(a)
        c3 = center + Vector(-w, -h).rotate(a)
        c4 = center + Vector(w, -h).rotate(a)
        if sign(c1.cross_product(d)) == sign(d.cross_product(c3)):
            return True
        if sign(c2.cross_product(d)) == sign(d.cross_product(c4)):
            return True
        return False
Example #31
0
    def test_case_1(self):
        groove_area = [
            Vector(0, 5),
            Vector(100, 5),
            Vector(0, -5),
            Vector(100, -5)
        ]
        lines = GrooveEvaluator.calculate_groove_passes(groove_area,
                                                        True,
                                                        tool_thickness=10,
                                                        tool_interlock=1)
        expected_lines = [Line(Vector(0, 0), Vector(100, 0))]

        self.assertListEqual(expected_lines, lines)
Example #32
0
    def _make_turn(self, tank, world, move):
        # Precalc for estimation
        self.enemies = list(filter(ALIVE_ENEMY_TANK, world.tanks))
        self.allies = list(filter(ALLY_TANK(tank.id), world.tanks))
        self.health_fraction = tank.crew_health / tank.crew_max_health
        self.hull_fraction = tank.hull_durability / tank.hull_max_durability
        self.enemies_count = len(self.enemies)
        self.est_time_cache = {}

        positions = []
        for pg in self.position_getters:
            positions += pg.positions(tank, world)
        self.debug('Got %d positions' % len(positions))
        if not positions:
            return

        for e in self.position_estimators:
            e.context = self
        pos_and_values = [
            (pos, sum([e.value(pos) for e in self.position_estimators]))
            for pos in positions
        ]

        if self.debug_mode:
            top_pos = [
                item[0] for item in list(
                    reversed(sorted(pos_and_values, key=operator.itemgetter(
                        1))))[:6]
            ]

            self.debug(' ' * 50, end='')
            for e in self.position_estimators:
                self.debug('%14s' % e.NAME, end='')
            self.debug('%14s' % 'RESULT', end='')
            self.debug('')

            def out_pos(pos):
                self.debug('%-50s' % (str(pos) + ' : '), end='')
                res = 0
                for e in self.position_estimators:
                    v = e.value(pos)
                    self.debug('%14.2f' % v, end='')
                    res += v
                self.debug('%14.2f' % res, end='')
                self.debug('')

            for pos in top_pos:
                out_pos(pos)
            self.debug('=' * 16)
            for pos in positions:
                if pos.name.find('BONUS') != -1:
                    out_pos(pos)
            self.debug('=' * 16)
            for pos in positions:
                if pos.name == 'FORWARD' or pos.name == 'BACKWARD' or pos.name == 'CURRENT':
                    out_pos(pos)

            self.debug('=' * 16)
            for pos in positions:
                if pos.name.find('BORDER') != -1:
                    out_pos(pos)

        next_position = None

        position_iteration = 0
        #average_F = sum([pos[0] for pos in pos_f])/len(pos_f)
        pos_queue = PriorityQueue()
        for p_v in pos_and_values:
            pos_queue.put((-p_v[1] + random() * 1e-3, p_v[0]))

        while True:
            cur = pos_queue.get()[1]
            if not self.physics.position_is_blocked(
                    cur.x, cur.y, tank,
                    world) or position_iteration >= MAX_POSITION_ITERATIONS:
                next_position = cur
                break
            position_iteration += 1
            self.debug('!!! Skipping best position, iteration %d' %
                       position_iteration)
            if self.debug_mode:
                self.debug('(blocked by %s)' % str(
                    self.physics.position_is_blocked(cur.x, cur.y, tank,
                                                     world)))

        self.debug(
            "GOING TO [%10s] (%8.2f, %8.2f); distance=%8.2f, ETA=%8.2f" %
            (next_position.name, next_position.x, next_position.y,
             self.tank.get_distance_to(next_position.x, next_position.y),
             self.physics.estimate_time_to_position(next_position.x,
                                                    next_position.y, tank)))
        self.memory.last_target_position[tank.id] = next_position
        self.physics.move_to_position(next_position.x, next_position.y, tank,
                                      move)

        self.debug('=' * 16)
        if self.debug_mode:
            for shell in world.shells:
                v0 = hypot(shell.speedX, shell.speedY)
                a = SHELL_ACCELERATION
                d = tank.get_distance_to_unit(shell)
                tank_v = Vector(tank.x, tank.y)
                shell_v = Vector(shell.x, shell.y)
                shell_speed = Vector(shell.speedX, shell.speedY)
                #d = shell.get_distance_to(x, y)
                t = solve_quadratic(a / 2, v0, -d)

                self.debug(
                    'SHELL [%12s] (%8.2f, %8.2f) v=%s, will hit=%s, hit time=%8.2f'
                    %
                    (shell.player_name, shell.x, shell.y, shell_speed.length(),
                     str(self.physics.shell_will_hit(shell, tank,
                                                     factor=1.05)), t))

            self.debug('=' * 16)
Example #33
0
    def estimate_time_to_position(self, x, y, tank):
        dist = tank.get_distance_to(x, y)
        vt = Vector(tank.speedX, tank.speedY)

        tank_v = Vector(tank.x, tank.y)
        pt_v = Vector(x, y)
        d = pt_v - tank_v

        if d.is_zero():
            return 0

        tank_d_v = Vector(1, 0).rotate(tank.angle)

        if vt.is_zero() or d.is_zero():
            vd_angle = 0
        else:
            vd_angle = vt.angle(d)

        if tank_d_v.is_zero() or d.is_zero():
            d_angle = 0
        else:
            d_angle = tank_d_v.angle(d)

        # Short distances fix
        if dist < 100:
            if fabs(d_angle) < LOW_ANGLE:
                v0 = vt.projection(d)
                return solve_quadratic(FICTIVE_ACCELERATION / 2, v0, -dist)
                #return dist/6
            elif PI - fabs(d_angle) < LOW_ANGLE:
                v0 = vt.projection(d)
                return solve_quadratic(FICTIVE_ACCELERATION / 2 * 0.75, v0,
                                       -dist)
                #return dist/6 /0.75

        if d.is_zero():
            values = (0, 0, 0, 0, 0, 0, 0, 1)
        else:

            values = (d_angle, dist, vd_angle, vt.length(), tank.angular_speed,
                      tank.crew_health / tank.crew_max_health, d_angle**2, 1)

        return sum([x * y for (x, y) in zip(values, TIME_ESTIMATION_COEF)])
Example #34
0
 def getCellsForCube(self, typeString, container):
     x = int(typeString)
     cells = {}
     index = 1
     for k in range(6):
         if k == 0:
             normal = Vector(1, 0, 0)
             up = Vector(0, 0, 1)
         elif k == 1:
             normal = Vector(0, 1, 0)
             up = Vector(0, 0, 1)
         elif k == 2:
             normal = Vector(0, 0, 1)
             up = Vector(1, 0, 0)
         elif k == 3:
             normal = Vector(-1, 0, 0)
             up = Vector(0, 0, 1)
         elif k == 4:
             normal = Vector(0, -1, 0)
             up = Vector(0, 0, 1)
         elif k == 5:
             normal = Vector(0, 0, -1)
             up = Vector(1, 0, 0)
         right = normal.cross(up).unit()
         for j in range(x):
             for i in range(x):
                 position = Locus(0,0,0).translateByVector(normal.scalarMultiply(x)).translateByVector(up.scalarMultiply(x - 1 - (2 * j))).translateByVector(right.scalarMultiply(x - 1 - (2 * i)))
                 neighborJunctions = []
                 
                 neighborJunctions.append(position.translateByVector(right))
                 neighborJunctions.append(position.translateByVector(right.scalarMultiply(-1)))
                 neighborJunctions.append(position.translateByVector(up))
                 neighborJunctions.append(position.translateByVector(up.scalarMultiply(-1)))
                 
                 cells[index] = Cell(index, position, normal, up, [1,1,1,1], neighborJunctions, 4, container)
                 index += 1
                 
     self.cells = cells
     self.generateNeighbors()
Example #35
0
 def __init__(self, enemy, width):
     self.enemy_v = Vector(enemy.x, enemy.y)
     self.turret_v = Vector(1, 0).rotate(enemy.angle +
                                         enemy.turret_relative_angle)
     self.width = width
def get_target_data(context):
    # New Decision Maker
    tank = context.tank
    target = context.cur_target
    physics = context.physics

    b = tank.angle + tank.turret_relative_angle
    #e = Vector(1, 0)
    #q = e.rotate(b)
    tank_v = Vector(tank.x, tank.y)

    target_v = Vector(target.x, target.y)
    target_direction = Vector(1, 0).rotate(target.angle)
    target_speed = Vector(target.speedX, target.speedY)

    def get_hit_time():
        v0 = INITIAL_SHELL_VELOCITY
        a = SHELL_ACCELERATION
        d = max(0, tank.get_distance_to_unit(target) - 60)
        return solve_quadratic(a/2, v0, -d)

    t = get_hit_time()
    #center = Vector(target.x - tank.x, target.y - tank.y)

    v0 = target_speed.projection(target_direction)
    target_health_fraction = target.crew_health/target.crew_max_health
    efficency = (1 + target_health_fraction)/2

    target_avoid_distance_forward = physics.max_move_distance(v0, FICTIVE_TARGET_ACCELERATION * efficency, MAX_TARGET_SPEED * efficency, t)
    target_avoid_distance_backward = physics.max_move_distance(v0, -FICTIVE_TARGET_ACCELERATION * BACKWARDS_FICTIVE_MULTIPLIER * efficency, MAX_TARGET_SPEED * efficency, t)

    #max_pos = target_v + target_avoid_distance_forward * target_direction
    #min_pos = target_v + target_avoid_distance_backward * target_direction

    target_turret_n_cos = fabs(cos(fabs(b - target.angle) + PI/2))

    #var = fabs((target_avoid_distance_forward - target_avoid_distance_backward) * target_turret_n_cos)

    allies_targeting = inverse_dict(context.memory.target_id, target.id)

    def single_attacker():
        #estimate_pos = target_v + target_direction * ((target_avoid_distance_forward + target_avoid_distance_backward) / 2)
        #vulnerable_width = max(90 * target_turret_n_cos, 60 * (1 - target_turret_n_cos))

        target_avoid_distance_forward_new = target_avoid_distance_forward * context.memory.tank_precision[tank.id]
        target_avoid_distance_backward_new = target_avoid_distance_backward * context.memory.tank_precision[tank.id]
        max_pos = target_v + target_avoid_distance_forward_new * target_direction
        min_pos = target_v + target_avoid_distance_backward_new * target_direction

        max_pos_fu = fictive_unit(target, max_pos.x, max_pos.y)
        min_pos_fu = fictive_unit(target, min_pos.x, min_pos.y)

        #shoot = physics.will_hit(tank, max_pos_fu, 0.9) and physics.will_hit(tank, min_pos_fu, 0.9)

        shoot_precise = physics.will_hit_precise(tank, max_pos_fu, factor=0.8, side_part=0.8) and \
                        physics.will_hit_precise(tank, min_pos_fu, factor=0.8, side_part=0.8) and \
                        physics.will_hit_precise(tank, target, factor=0.8, side_part=0.8)
        shoot = shoot_precise
        fabs(target_turret_n_cos)

        #all_corners = get_unit_corners(max_pos_fu) + get_unit_corners(min_pos_fu)
        all_corners = get_unit_corners(target)
        #closest_corner = min(all_corners, key=lambda c: c.distance(tank_v))
        # Instead of closest corner try to target middle of colsest side
        sc1, sc2 = sorted(all_corners, key=lambda c: c.distance(tank_v))[:2]
        closest_corner = 0.75 * sc1 + 0.25 * sc2

        middle_position = (max_pos + min_pos)/2

        w = fabs(target_turret_n_cos)**2
        estimate_pos = w*middle_position + (1 - w) * closest_corner

        comment = 'SINGLE, %s' % w

        if obstacle_is_attacked(context, estimate_pos):
            shoot = False
            comment += ' blocked'

        if context.debug_mode:
            if shoot and tank.remaining_reloading_time == 0:
                debug_data = {
                    "units": [min_pos_fu, max_pos_fu, target],
                    "tanks": [tank]
                }
                debug_dump(debug_data, "/".join([str(context.memory.battle_id), str(context.world.tick) + "_" + str(tank.id) + "_single"]))

        return ((estimate_pos.x, estimate_pos.y), shoot, target_avoid_distance_forward, target_avoid_distance_backward, comment)

    def multiple_attackers(attackers):
        cnt = len(allies_targeting)
        ind = index_in_sorted(allies_targeting, tank.id)

        segment = (target_avoid_distance_forward - target_avoid_distance_backward)/(cnt + 1)

        shift = segment * (ind + 1) + target_avoid_distance_backward
        #shift *
        if cnt == 2:
            if ind == 0:
                shift = target_avoid_distance_backward + 45
            else:
                shift = target_avoid_distance_forward - 45

        estimate_pos = target_v + target_direction * shift
        shift_fu = fictive_unit(target, estimate_pos.x, estimate_pos.y)

        shoot = (physics.will_hit_precise(tank, shift_fu, factor=0.8) and
                 all([lambda a: a.remaining_reloading_time < MULTIPLE_MAX_TIME_DIFFERENCE or a.reloading_time - a.remaining_reloading_time < MULTIPLE_MAX_TIME_DIFFERENCE, attackers])
                 #and target_avoid_distance_forward - target_avoid_distance_backward < 200
            )

        comment = 'MULTIPLE(%d), shift=%8.2f' % (ind, shift)
        if obstacle_is_attacked(context, estimate_pos):
            shoot = False
            comment += ' blocked'

        if context.debug_mode:
            if shoot and tank.remaining_reloading_time == 0 and all([context.memory.good_to_shoot.get(t.id) or t.id == tank.id for t in attackers]):
                max_pos = target_v + target_avoid_distance_forward * target_direction
                min_pos = target_v + target_avoid_distance_backward * target_direction
                max_pos_fu = fictive_unit(target, max_pos.x, max_pos.y)
                min_pos_fu = fictive_unit(target, min_pos.x, min_pos.y)
                debug_data = {
                    "units": [min_pos_fu, max_pos_fu, target],
                    "tanks": attackers
                }
                debug_dump(debug_data, "/".join([str(context.memory.battle_id), str(context.world.tick) + "_" + str(tank.id) + "_multiple"]))

        return ((estimate_pos.x, estimate_pos.y), shoot, target_avoid_distance_forward, target_avoid_distance_backward, comment)

    context.memory.good_to_shoot[tank.id] = False

    try_single = single_attacker()
    if len(allies_targeting) == 2 and try_single[1] == False:

        if tank.id in allies_targeting:
            attackers = []
            for t in context.world.tanks:
                if t.id in allies_targeting:
                    attackers.append(t)
            if all([lambda a: a.remaining_reloading_time < 70 or a.reloading_time - a.remaining_reloading_time < MULTIPLE_MAX_TIME_DIFFERENCE, attackers]):
                try_multiple = multiple_attackers(attackers)
                if try_multiple[1]:
                    if tank.remaining_reloading_time < 3:
                        context.memory.good_to_shoot[tank.id] = True
                    ok = all([context.memory.good_to_shoot.get(t.id) for t in attackers])
                    if not ok:
                        try_multiple = (try_multiple[0], False)

                return try_multiple
    return try_single
Example #37
0
 def attacked_area(self, x, y):
     pt_v = Vector(x, y)
     if (pt_v - self.enemy_v).scalar_product(self.turret_v) <= 0:
         return 0
     dist = pt_v.distance_to_line(self.enemy_v, self.turret_v)
     return max(0, (self.width - dist) / self.width)
Example #38
0
 def attacked_area(self, x, y):
     pt_v = Vector(x, y)
     if (pt_v - self.enemy_v).scalar_product(self.turret_v) <= 0:
         return 0
     dist = pt_v.distance_to_line(self.enemy_v, self.turret_v)
     return max(0, (self.width - dist)/self.width)
Example #39
0
    def test_case_5(self):
        groove_area = [
            Vector(-1, 479.1),
            Vector(1001, 479.1),
            Vector(-1, 469.1),
            Vector(1001, 469.1)
        ]
        lines = GrooveEvaluator.calculate_groove_passes(groove_area,
                                                        True,
                                                        tool_thickness=2.2,
                                                        tool_interlock=1)

        expected_lines = [
            Line(Vector(-1, 470.2), Vector(1001, 470.2)),
            Line(Vector(-1, 478.0), Vector(1001, 478.0)),
            Line(Vector(-1, 471.4), Vector(1001, 471.4)),
            Line(Vector(-1, 476.8), Vector(1001, 476.8)),
            Line(Vector(-1, 472.6), Vector(1001, 472.6)),
            Line(Vector(-1, 475.6), Vector(1001, 475.6)),
            Line(Vector(-1, 473.8), Vector(1001, 473.8)),
            Line(Vector(-1, 474.4), Vector(1001, 474.4))
        ]

        self.assertListEqual(expected_lines, lines)
    def _make_turn(self, tank, world, move):
        # Precalc for estimation
        self.enemies = list(filter(ALIVE_ENEMY_TANK, world.tanks))
        self.allies = list(filter(ALLY_TANK(tank.id), world.tanks))
        self.health_fraction = tank.crew_health / tank.crew_max_health
        self.hull_fraction = tank.hull_durability / tank.hull_max_durability
        self.enemies_count = len(self.enemies)
        self.est_time_cache = {}

        positions = []
        for pg in self.position_getters:
            positions += pg.positions(tank, world)
        self.debug('Got %d positions' % len(positions))
        if not positions:
            return

        for e in self.position_estimators:
            e.context = self
        pos_and_values = [(pos, sum([e.value(pos) for e in self.position_estimators]))
                          for pos in positions]

        if self.debug_mode:
            top_pos = [item[0] for item in list(reversed(sorted(pos_and_values, key=operator.itemgetter(1))))[:6]]

            self.debug(' ' * 50, end='')
            for e in self.position_estimators:
                self.debug('%14s' % e.NAME, end='')
            self.debug('%14s' % 'RESULT', end='')
            self.debug('')

            def out_pos(pos):
                self.debug('%-50s' % (str(pos) + ' : '), end='')
                res = 0
                for e in self.position_estimators:
                    v = e.value(pos)
                    self.debug('%14.2f' % v, end='')
                    res += v
                self.debug('%14.2f' % res, end='')
                self.debug('')

            for pos in top_pos:
                out_pos(pos)
            self.debug('=' * 16)
            for pos in positions:
                if pos.name.find('BONUS') != -1:
                    out_pos(pos)
            self.debug('=' * 16)
            for pos in positions:
                if pos.name == 'FORWARD' or pos.name == 'BACKWARD' or pos.name == 'CURRENT':
                    out_pos(pos)

            self.debug('=' * 16)
            for pos in positions:
                if pos.name.find('BORDER') != -1:
                    out_pos(pos)


        next_position = None

        position_iteration = 0
        #average_F = sum([pos[0] for pos in pos_f])/len(pos_f)
        pos_queue = PriorityQueue()
        for p_v in pos_and_values:
            pos_queue.put( (-p_v[1] + random() * 1e-3, p_v[0]) )

        while True:
            cur = pos_queue.get()[1]
            if not self.physics.position_is_blocked(cur.x, cur.y, tank, world) or position_iteration >= MAX_POSITION_ITERATIONS:
                next_position = cur
                break
            position_iteration += 1
            self.debug('!!! Skipping best position, iteration %d' % position_iteration)
            if self.debug_mode:
                self.debug('(blocked by %s)' % str(self.physics.position_is_blocked(cur.x, cur.y, tank, world)))

        self.debug("GOING TO [%10s] (%8.2f, %8.2f); distance=%8.2f, ETA=%8.2f" %
                   (next_position.name, next_position.x, next_position.y,
                    self.tank.get_distance_to(next_position.x, next_position.y), self.physics.estimate_time_to_position(next_position.x, next_position.y, tank))
        )
        self.memory.last_target_position[tank.id] = next_position
        self.physics.move_to_position(next_position.x, next_position.y, tank, move)

        self.debug('=' * 16)
        if self.debug_mode:
            for shell in world.shells:
                v0 = hypot(shell.speedX, shell.speedY)
                a = SHELL_ACCELERATION
                d = tank.get_distance_to_unit(shell)
                tank_v = Vector(tank.x, tank.y)
                shell_v = Vector(shell.x, shell.y)
                shell_speed = Vector(shell.speedX, shell.speedY)
                #d = shell.get_distance_to(x, y)
                t = solve_quadratic(a/2, v0, -d)

                self.debug('SHELL [%12s] (%8.2f, %8.2f) v=%s, will hit=%s, hit time=%8.2f' %
                           (shell.player_name, shell.x, shell.y, shell_speed.length(), str(self.physics.shell_will_hit(shell, tank, factor=1.05)), t) )

            self.debug('=' * 16)
Example #41
0
    def test_case_7(self):
        groove_area = [
            Vector(0, 10),
            Vector(20, 10),
            Vector(0, 0),
            Vector(20, 0)
        ]
        lines = GrooveEvaluator.calculate_groove_passes(groove_area,
                                                        True,
                                                        tool_thickness=3,
                                                        tool_interlock=1)
        expected_lines = [
            Line(Vector(0, 1.5), Vector(20, 1.5)),
            Line(Vector(0, 8.5), Vector(20, 8.5)),
            Line(Vector(0, 3.5), Vector(20, 3.5)),
            Line(Vector(0, 6.5), Vector(20, 6.5)),
            Line(Vector(0, 5.5), Vector(20, 5.5)),
        ]

        self.assertListEqual(expected_lines, lines)
Example #42
0
    def test_case_6(self):
        groove_area = [
            Vector(-1, 479.1),
            Vector(451, 479.1),
            Vector(-1, 469.1),
            Vector(451, 469.1)
        ]
        lines = GrooveEvaluator.calculate_groove_passes(groove_area,
                                                        True,
                                                        tool_thickness=3.2,
                                                        tool_interlock=1)
        expected_lines = [
            Line(Vector(-1, 470.7), Vector(451, 470.7)),
            Line(Vector(-1, 477.5), Vector(451, 477.5)),
            Line(Vector(-1, 472.9), Vector(451, 472.9)),
            Line(Vector(-1, 475.3), Vector(451, 475.3)),
            Line(Vector(-1, 475.1), Vector(451, 475.1)),
        ]

        self.assertListEqual(expected_lines, lines)
Example #43
0
    def inverse_kinematics(self, goal, tol_limit=0.05, max_iterations=100):
        '''
		Preforms Inverse Kinematics on the arm using the known lengths and last positions.
		This uses the FABRIK method.
		See the more information section for information on the FABRIK method.


		Parameters
		----------
		goal : arraylike or Point
			desired location in the format of [x,y,z]
		tol_limit : scalar (optional)
			tolerance between the desired location and actual one after iteration
		max_iterations : scalar (optional)
			maximum itterations that the function is allowed to run


		Returns
		-------
		q1 : scalar
			rotation of the base about the z axis
		q2 : scalar
			rotation of first link relative to the base
		q3 : scalar
			rotation of the second link relative to the first link
		q4 : scalar
			rotation of the third link relative to the second link
		x_joints : array_like
			x points for all the joint locations
		y_joints : array_like
			y points for all the joint locations
		z_joints : array_like
			z points for all the joint locations


		More Information
		----------------
		Overall algorithim:
			solve for unit vectors going backwards from the desired point to the original point
			of the joints, then forwards from the origin, see youtube video
			https://www.youtube.com/watch?v=UNoX65PRehA&t=817s

		'''
        if isinstance(goal, list):
            if len(goal) != 3:
                raise IndexError(
                    "goal unclear. Need x,y,z coordinates in Point or list form."
                )
            goal = Point(goal[0], goal[1], goal[2])

        # Find base rotation
        # Initial angle of where end effector is
        initial_base_roation = np.arctan2(self.y_joints[-1], self.x_joints[-1])

        # Desired angle
        # arctan2(y,x)
        self.q1 = np.arctan2(goal.y, goal.x)

        # Base rotation
        base_rotation = self.q1 - initial_base_roation

        # Base rotation matrix about z
        z_rot = np.array([[np.cos(base_rotation), -np.sin(base_rotation), 0.0],
                          [np.sin(base_rotation),
                           np.cos(base_rotation), 0.0], [0.0, 0.0, 1.0]])

        # Rotate the location of each joint by the base rotation
        # This will force the FABRIK algorithim to only solve
        # in two dimensions, else each joint will move as if it has
        # a 3 DOF range of motion

        point4 = Point(
            np.dot(z_rot,
                   [self.x_joints[3], self.y_joints[3], self.z_joints[3]]))
        point3 = Point(
            np.dot(z_rot,
                   [self.x_joints[2], self.y_joints[2], self.z_joints[2]]))
        point2 = Point(
            np.dot(z_rot,
                   [self.x_joints[1], self.y_joints[1], self.z_joints[1]]))
        point1 = Point(
            np.dot(z_rot,
                   [self.x_joints[0], self.y_joints[0], self.z_joints[0]]))

        # store starting point of the first joint
        starting_point1 = point1

        iterations = 0

        # Make sure the desired x,y,z point is reachable
        if Vector.magnitude(goal) > self.total_arm_length:
            print ' desired point is likely out of reach'

        for _ in range(1, max_iterations + 1):

            # backwards
            point3 = Vector.project_along_vector(goal, point3,
                                                 self.third_link_length)
            point2 = Vector.project_along_vector(point3, point2,
                                                 self.second_link_length)
            point1 = Vector.project_along_vector(point2, point1,
                                                 self.first_link_length)

            # forwards
            point2 = Vector.project_along_vector(point1, point2,
                                                 self.first_link_length)
            point3 = Vector.project_along_vector(point2, point3,
                                                 self.second_link_length)
            point4 = Vector.project_along_vector(point3, goal,
                                                 self.third_link_length)

            # Solve for tolerance between iterated point and desired x,y,z,
            tol = point4 - goal

            # Make tolerance relative to x,y,z
            tol = Vector.magnitude(tol)

            iterations = iterations + 1

            # Check if tolerance is within the specefied limit
            if tol < tol_limit:
                break

        # Re-organize points into a big matrix for plotting elsewhere
        self.p_joints = np.transpose([
            starting_point1.as_array(),
            point2.as_array(),
            point3.as_array(),
            point4.as_array()
        ])

        self.x_joints = self.p_joints[0]
        self.y_joints = self.p_joints[1]
        self.z_joints = self.p_joints[2]

        # Return the joint angles by finding the angles with the dot produt
        vector21 = Vector(point2 - point1)
        vector32 = Vector(point3 - point2)
        vector43 = Vector(point4 - point3)

        # returns -pi to pi
        self.q2 = np.arctan2(
            vector21.end.z, Vector.magnitude([vector21.end.x, vector21.end.y]))

        # Negative sign because of dh notation, a rotation away from the previous link
        # and towards the x-y plane is a negative moment about the relative z axis.
        # the relative z axis of each link is out of the page if looking at the arm
        # in 2D
        # the x axis in dh convention is typically along the link direction.

        self.q3 = -1.0 * vector21.angle(vector32)
        self.q4 = -1.0 * vector32.angle(vector43)

        return [
            self.q1, self.q2, self.q3, self.q4, self.x_joints, self.y_joints,
            self.z_joints
        ]