# Problem Description
This is a puzzle piece validation problem where we need to check if a given arrangement of rotated puzzle pieces forms a valid configuration. Each puzzle piece:
- Has a rotation angle (0°, 90°, 180°, or -90°)
- Contains protrusions and indents on its edges
- Must properly connect with adjacent pieces (protrusion must match indent)
The function should return true if the arrangement creates valid connections between all adjacent pieces.

# Visual Facts
1. Piece Base Configuration:
   - Base piece (0°) has one indent on the right side
   - Base piece has protrusions on top, left, and bottom sides
   - Each piece can be rotated in 90° increments

2. Rotations Demonstrated:
   - When 0° piece rotates 90° clockwise, it becomes labeled as 90
   - Available rotation angles: 0°, 90°, 180°, -90°

3. Example Arrangements:
   - 1×2 puzzle shows [90, 180]
   - 2×2 puzzle shows [[0, 90], [-90, 180]]

4. Edge Configurations:
   - Each edge must either be a protrusion or indent
   - Edges must complement each other where pieces meet
   - All shown arrangements demonstrate valid connections

# Visual Patterns
1. Rotation Effects:
   - 90° rotation shifts all edges clockwise
   - -90° rotation shifts all edges counterclockwise
   - 180° rotation flips both horizontal and vertical edges

2. Connection Rules:
   - Adjacent pieces must have complementary edges where they meet
   - A protrusion must connect with an indent
   - This applies to both horizontal and vertical adjacencies


# Solution
from typing import List

def solution(arrangement: List[List[int]]) -> bool:
    """
    Determine whether the arrangement of the given puzzle pieces is valid.

    Input:
    - arrangement: A 2D list of integers, where each element represents the rotation angle for the corresponding puzzle piece. 
      Possible angles are 0, 90, 180, or -90.

    Output:
    - A boolean value, where True indicates that the arrangement is valid and False indicates that the arrangement is invalid.
    """
    def get_edges(angle):
        """
        Returns the edges as a tuple (T, R, B, L) after rotating the piece by a given angle.
        """
        edges = {
            0: {
                'T': 1,
                'R': 0,
                'B': 1,
                'L': 1
            },
            90: {
                'T': 1,
                'R': 1,
                'B': 0,
                'L': 1
            },
            180: {
                'T': 1,
                'R': 1,
                'B': 1,
                'L': 0
            },
            -90: {
                'T': 0,
                'R': 1,
                'B': 1,
                'L': 1
            }
        }
        return edges[angle]
        
    rows = len(arrangement)
    cols = len(arrangement[0])
    for i in range(rows):
        for j in range(cols):
            if j < cols - 1:
                current_piece_edges = get_edges(arrangement[i][j])
                right_piece_edges = get_edges(arrangement[i][j + 1])
                if current_piece_edges['R'] == right_piece_edges['L']:
                    return False
            if i < rows - 1:
                current_piece_edges = get_edges(arrangement[i][j])
                bottom_piece_edges = get_edges(arrangement[i + 1][j])
                if current_piece_edges['B'] == bottom_piece_edges['T']:
                    return False
    return True


# Test Script
def test():
    test_cases = [
        ([[-90, 0], [180, 90]], False),
        ([[0, 90], [-90, 180]], True),
        ([[90, 180, 180]], True),
        ([[180, 180, 180]], True),
        ([[-90, 180, 180]], True),
        ([[90, 180, 180, 180, 180]], True),
        ([[180, 180, 180, 180]], True),
        ([[-90, 180, 180, 180, 180]], True),
        ([[90], [180], [-90]], True),
        ([[180],[-90],[-90]], True),
        ([[0],[-90],[-90]], True),
        ([[0], [-90], [180]], False),
        ([[-90], [-90], [-90]], True),
        ([[90], [-90], [-90]], False),
        ([[90], [90]], True),
        ([[90, 90]], False)
    ]
    for i, (arrangement, expected_output) in enumerate(test_cases):
        try:
            output = solution(arrangement)
        except Exception as e:
            error_message = (
                f"An exception occurred while running the test case:\n"
                f"`arrangement`: {arrangement}\n\n"
                f"Exception: '{str(e)}'"
            )
            assert False, error_message

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

test()