# Problem Description
This is a path-tracking problem where an object (blue dot) moves through a grid with the following rules:
- The object starts from a given point and moves downward (row++) initially
- When it steps on a black block, it changes direction to the next counter-clockwise direction 
- The object continues this pattern until it exits the grid
- We need to count how many black blocks the object encounters before exiting

# Visual Facts
1. Grid Properties:
   - Each example shows a grid with numbered rows and columns (1-indexed)
   - Different grid sizes are shown (4x6, 5x4, 6x6, 7x6)
   - Each grid has a blue dot marking the start position

2. Movement Indicators:
   - Orange arrows show the path of movement
   - Movement alternates between downward and rightward directions
   - Path always starts with downward movement

3. Black Blocks:
   - Black squares represent a turning point
   - Some black blocks are on the path, others are not
   - Black blocks appear in different patterns in each example

4. Starting Points:
   - All examples show the start point marked with a blue dot
   - Starting positions vary (e.g., (1,2), (1,1))

# Visual Patterns
1. Movement Rules:
   - Path always starts with downward movement from the starting point
   - Direction changes when encounter a black block
   - Direction changes are always in a counter-clockwise direction
      - (down->right, right->up, up->left, left->down)

2. Exit Conditions:
   - The path terminates when reaching any grid boundary

# Solution
from typing import Tuple

def solution(m: int, n: int, start_point: Tuple[int,int], black_blocks: list[Tuple[int, int]]) -> int:     
    """
    Calculates the number of black blocks an object traverses before exiting a grid, given a matrix size, starting point, and the black blocks' positions.
    
    Parameters:
        m (int): Number of rows in the matrix
        n (int): Number of columns in the matrix
        start_point (Tuple[int,int]): Starting coordinates of the object (1-indexed)
        black_blocks (list[Tuple[int, int]]): List of coordinates representing blocked positions (1-indexed)

    Returns:
        int: Number of black blocks traversed before the object exits the grid
    """
    class Movement:
        def __init__(self):
            self.state = 0
            self.directions = [(1,0), (0,1),(-1,0) ,(0,-1)] # down, right, up, left
        def move(self, point):
            x, y = point
            return (x + self.directions[self.state][0], y + self.directions[self.state][1])
        def change_state(self):
            self.state = (self.state+ 1) % 4

    movement = Movement()
    i = start_point[0]-1
    j = start_point[1]-1
    num_blocks = 0
    while i >= 0 and i < m and j >= 0 and j < n:
        if (i+1, j+1) in black_blocks:
            movement.change_state()
            i, j = movement.move((i, j))
            num_blocks += 1
        else:
            i, j = movement.move((i, j))

    return num_blocks


# Test Script
def test():
    test_cases = [
       (4,6, (1,2),[(3,2),(3,5)],2),
       (5,4,(1,1),[(4,1),(5,2),(4,3),(2,3)],3),
       (6,4,(1,1),[(4,1), (5,2),(4,3), (2,3)],3),
       (6,6,(1,1),[(6,1),(6,6),(4,2),(4,4),(2,6),(2,2)],6),
       (7,6,(1,1),[(7,1),(7,6),(5,2),(5,4),(3,2),(3,6),(2,4)],7)
    ]

    for m, n, start_point, black_blocks, expected_output in test_cases:
        try:
            output = solution(m, n, start_point, black_blocks)
        except Exception as e:
            error_message = (
                f"An exception occurred while running the test case:\n"
                f"`m`: {m}\n"
                f"`n`: {n}\n" 
                f"`start_point`: {start_point}\n"
                f"`black_blocks`: {black_blocks}\n\n"
                f"Exception: '{str(e)}'"
            )
            assert False, error_message

        if output != expected_output:
            error_message = (
                f"The following Test Case failed:\n"
                f"`m`: {m}\n"
                f"`n`: {n}\n"
                f"`start_point`: {start_point}\n" 
                f"`black_blocks`: {black_blocks}\n\n"
                f"Actual Output: {output}\n"
                f"Expected Output: {expected_output}"
            )
            assert False, error_message

test()