# Problem Description
This is a grid-based watermelon growth simulation problem where:
- Watermelons start from seeds placed on a square grid
- The seed span in the certain directions each day
- The simulation runs for T days
- Seeds can die under certain conditions
- We need to track which seeds survive until day T

# Visual Facts
1. Grid and Layout:
   - Square grid with field_size × field_size dimensions
   - Uses 0-based indexing for coordinates
   - Green cells represent living watermelon growth
   - Gray cells represent dead watermelon remains

2. Growth Pattern:
   - Day 1: Single cell (seed position)
   - Growth extends one cell in each cardinal direction (up, down, left, right) daily

3. Death Conditions Illustrated:
   - Example 1: Shows boundary death
   - Example 2: Shows overlap death
   - Example 3: Shows both boundary and overlap deaths

4. State Representation:
   - Living watermelons shown in green
   - Dead watermelons shown in gray
   - Original seed position marked with watermelon icon

# Visual Patterns
1. Growth Rules:
   - Each day: Expands 1 cell in all 4 directions (up, down, left, right)
   - Growth pattern forms a '+' shape after the first day.

2. Death Conditions:
   - Boundary Death: If any part touches/crosses grid boundary
   - Overlap Death: If growth areas of two watermelons seed intersect
   - Chain Reaction: If one watermelon seed died, all the other seeds growing from the day 1 watermelon at the seed position died.

3. State Transitions:
   - Once a watermelon dies, its occupied cells remain (shown in gray)
   - Death occurs on the day of boundary crossing or overlap

4. Survival Rules:
   - Must stay within boundaries
   - Must not overlap with any other watermelon (living or dead)

# Example

Example1 (Boundary Death& Chain Reaction): 
Suppose the original seed is (2, 2) in a matrix with shape (6 × 6) in  the first day 
In day 2, the seeds grows left then get (2, 1), the seed grows right to get (2, 3), the seed grows up to get (1, 2), and the seed grows down to get (3, 2)
In day 3, the seeds grows left then get (2, 0), the seed grows right to get (2, 4), the seed grows up to get (0, 2), and the seed grows down to get (4, 2)
In day 4, the seeds grows left then meet the boundary, the seeds grows right to get (2, 5), the seed grows up then meets the boundary, and the seed grows down to get (5, 2). 
Because some seeds meet the boundary, all seeds: (2, 2), (2, 1), (2, 3), (1, 2), (3, 2), (2, 0), (2, 4), (0, 2), (4, 2), (2, 5), (5, 2).

Example2 (Overlap Death& Chain Reaction): 
Suppose two original seeds are seed 1:(2, 1), seed 2:(1, 2) in a matrix with shape (4 × 4) in the first day.
In day 2, 
   - the seed 1: grows left then (2, 0), grows right then get (2, 2), grows upward then get (1, 1), grows downward then get (3, 1)
   - the seed 2: grows left then (1, 1), grows right then get (1, 3), grows upward then get (0, 2), grows downward then get (2, 2)
   because new seeds at (1, 1) and (2, 2) overlaped, all seeds (2, 0), (2, 2), (1, 1), (3, 1), (1, 3), (0, 2), (2, 1), (1, 2) died.

# Solution
from typing import List, Tuple
from collections import defaultdict

def solution(seed_positions: List[Tuple[int, int]], field_size: int, T: int) -> int:       
    """
    Determines which watermelon seeds remain alive at Day T, based on field size and growth rules.       

    Args:
        seed_positions: List of (x, y) coordinate tuples representing initial seed positions (0-based indexing)
        field_size: Side length of the square field (e.g., 5 means a 5x5 field)
        T: The day number at which to check for surviving seeds

    Returns:
        The number of surviving seeds at Day T
    """
    directions = {
        'up': (-1, 0),
        'down': (1, 0),
        'left': (0, -1),
        'right': (0, 1)
    }
    
    seed_indices = list(range(len(seed_positions)))
    alive_seeds = set(seed_indices)
    seed_active_fronts = {
        seed: {dir: pos for dir in directions.keys()} 
        for seed, pos in enumerate(seed_positions)
    }
    grid = set(seed_positions)
    death_schedule = defaultdict(set)

    for day in range(1, T + 1):
        new_growths = defaultdict(set)
        boundary_hit_seeds = set()

        for seed in alive_seeds:
            active_directions = list(seed_active_fronts[seed].keys())
            for dir in active_directions:
                last_pos = seed_active_fronts[seed][dir]
                dx, dy = directions[dir]
                next_x, next_y = last_pos[0] + dx, last_pos[1] + dy
                next_pos = (next_x, next_y)

                if not (0 <= next_x < field_size and 0 <= next_y < field_size):
                    boundary_hit_seeds.add(seed)
                    del seed_active_fronts[seed][dir]
                    continue

                new_growths[next_pos].add((seed, dir))

        collision_seeds = set()
        for pos, seed_dir_set in new_growths.items():
            if len(seed_dir_set) > 1:
                for seed, _ in seed_dir_set:
                    collision_seeds.add(seed)
            grid.add(pos)

        if boundary_hit_seeds:
            death_schedule[day + 1].update(boundary_hit_seeds)
        if collision_seeds:
            death_schedule[day + 1].update(collision_seeds)


        for pos, seed_dir_set in new_growths.items():
            for seed, dir in seed_dir_set:
                seed_active_fronts[seed][dir] = pos


        if day in death_schedule:
            seeds_to_die = death_schedule[day]
            alive_seeds -= seeds_to_die

    return len(alive_seeds)


# Test Script
def test():
    test_cases = [
        {
            "input": ([(2, 2)], 5, 3),
            "expected": [(2, 2)]
        },
        {
            "input": ([(2, 2)], 5, 4),
            "expected": []
        },
        {
            "input": ([(2, 1)], 4, 2),
            "expected": [(2, 1)]
        },
        {
            "input": ([(2, 1)], 4, 3),
            "expected": []
        },
        {
            "input": ([(2, 1), (1, 2)], 4, 1),
            "expected": [(2, 1), (1, 2)]
        },
        {
            "input": ([(2, 1), (1, 2)], 4, 2),
            "expected": []
        },
        {
            "input": ([(1, 1), (3, 2)], 4, 1),
            "expected": [(1 ,1), (3, 2)]
        },
        {
            "input": ([(1, 1), (3, 2)], 4, 2),
            "expected": [(1 ,1)]
        },
        {
            "input": ([(1, 1), (4, 1)], 6, 2),
            "expected": [(1, 1), (4, 1)]
        },
        {
            "input": ([(1, 1), (4, 1)], 6, 3),
            "expected": []
        }
    ]

    for i, test_case in enumerate(test_cases, start=1):
        seed_positions, field_size, T = test_case["input"]
        expected_output = test_case["expected"]
        
        try:
            output = solution(seed_positions, field_size, T)
        except Exception as e:
            error_message = (
                f"An exception occurred while running the test case:\n"
                f"`seed_positions`: {seed_positions}\n"
                f"`field_size`: {field_size}\n" 
                f"`T`: {T}\n\n"
                f"Exception: '{str(e)}'"
            )
            assert False, error_message

        if output != len(expected_output):
            error_message = (
                f"The following Test Case failed:\n"
                f"`seed_positions`: {seed_positions}\n"
                f"`field_size`: {field_size}\n"
                f"`T`: {T}\n\n"
                f"Actual Output: {output}\n"
                f"Expected Output: {len(expected_output)}"
            )
            assert False, error_message

test()