# Problem Description
This problem involves determining whether two balls, starting from different positions and moving in specified directions, will collide on a 10x10 grid. The balls bounce off the boundaries of the grid according to the law of reflection (angle of incidence equals angle of reflection). The task is to simulate the movement of the balls and check if they will collide at any point.

# Visual Facts
1. The coordinate system is a 10x10 grid (from 0 to 10 on both axes).
2. Ball 1 starts at position (6, 9) and moves in the direction (-1, 1).
3. Ball 2 starts at position (9, 6) and moves in the direction (1, -1).
4. The paths of both balls are shown with dotted lines.
5. The paths indicate multiple bounces off the boundaries.
6. The collision point is marked at (2.5, 2.5).
7. The paths are straight lines between bounces.
8. The balls change direction upon hitting the boundaries.
9. The grid has clear boundaries at x=0, x=10, y=0, and y=10

# Visual Patterns
1. **Bouncing Pattern**:
   - When a ball hits a boundary, it reflects with equal angles.
   - The angle of reflection equals the angle of incidence.
   - The ball maintains a constant direction between bounces.

2. **Movement Constraints**:
   - Movement is continuous along straight lines.
   - Direction changes only occur at boundaries.
   - The balls follow predictable paths based on their initial directions and the grid boundaries.
   - The path never passes through the same point with the same direction twice (suggests a termination condition)

3. **Geometric Properties**:
   - The trajectory forms a series of connected line segments.
   - Each bounce preserves the angle but reverses one component of the direction vector.
   - The paths remain within the 10x10 grid boundaries at all times.

4. **Collision Detection**:
   - A collision occurs if both balls occupy the same position at the same time.
   - The paths of the balls can be simulated to check for intersections.

# Solution
def solution(start_1: tuple[int, int], start_2: tuple[int, int], direction_1: tuple[int, int], direction_2: tuple[int, int]) -> bool:
    """
    Determines whether the two balls will collide.

    Parameters:
    - start_1: Tuple[int, int], represents the initial position of ball 1 (x, y).
    - start_2: Tuple[int, int], represents the initial position of ball 2 (x, y).
    - direction_1: Tuple[int, int], represents the initial direction of ball 1 (dx, dy). dx and dy are integers that can be either -1, 0, or 1.
    - direction_2: Tuple[int, int], represents the initial direction of ball 2 (dx, dy). dx and dy are integers that can be either -1, 0, or 1.
    
    Returns:
    - bool: True if the balls can collide, False otherwise.
    """
    x_1, y_1 = start_1
    x_2, y_2 = start_2
    dx_1, dy_1 = direction_1
    dx_2, dy_2 = direction_2
    while True:
        x_1 += dx_1
        y_1 += dy_1
        x_2 += dx_2
        y_2 += dy_2
        
        if (x_1, y_1) == (x_2, y_2):
            return True
        if (x_1 + 0.5 * dx_1, y_1 + 0.5 * dy_1) == (x_2 + 0.5 * dx_2, y_2 + 0.5 * dy_2):
            return True
            
        if x_1 == 0 or x_1 == 10:
            dx_1 = -dx_1
        if x_2 == 0 or x_2 == 10:
            dx_2 = -dx_2
        if y_1 == 0 or y_1 == 10:
            dy_1 = -dy_1
        if y_2 == 0 or y_2 == 10:
            dy_2 = -dy_2
        if (x_1, y_1) == start_1 and (x_2, y_2) == start_2 and (dx_1, dy_1) == direction_1 and (dx_2, dy_2) == direction_2:
            return False

# Test Script
def test():
    test_cases = [
        [(8, 7), (6, 9), (1, -1), (1, 1), False],
        [(8, 7), (6, 9), (1, -1), (-1, 1), True],
        [(8, 7), (6, 9), (-1, 1), (1, -1), True],
        [(8, 7), (9, 6), (1, -1), (1, -1), False],
        
        [(0, 0), (1, 1), (1, 0), (1, 1), False],
        [(0, 0), (0, 3), (1, 0), (1, -1), True],
        [(0, 0), (2, 2), (1, 1), (0, 1), False],
        [(0, 0), (2, 2), (1, 1), (-1, -1), True],
        [(0, 0), (0, 1), (0, 1), (1, -1), False],
        
        [(2, 1), (1, 0), (-1, -1), (0, 1), False],
        
        [(10, 1), (1, 0), (-1, 1), (1, 0), False],
        [(10, 1), (9, 2), (-1, 1), (-1, 1), False],
        [(3, 4), (3, 6), (1, 1), (1, -1), True],
    ]
    for test_case in test_cases:
        start_1, start_2, direction_1, direction_2, expected_output = test_case
        
        try:
            output = solution(start_1, start_2, direction_1, direction_2)
        except Exception as e:
            error_message = (
                f"An exception occurred while running the test case:\n"
                f"`start_1`: {start_1}\n"
                f"`start_2`: {start_2}\n" 
                f"`direction_1`: {direction_1}\n"
                f"`direction_2`: {direction_2}\n\n"
                f"Exception: '{str(e)}'"
            )
            assert False, error_message

        if output != expected_output:
            error_message = (
                f"The following Test Case failed:\n"
                f"`start_1`: {start_1}\n"
                f"`start_2`: {start_2}\n"
                f"`direction_1`: {direction_1}\n" 
                f"`direction_2`: {direction_2}\n\n"
                f"Actual Output: {output}\n"
                f"Expected Output: {expected_output}"
            )
            assert False, error_message

test()