# Problem Description
This is a puzzle validation problem where we need to check if a given arrangement of puzzle pieces forms a valid configuration. Each piece:
- Has a numerical label representing its rotation (0°, 90°, 180°, or -90°)
- Contains interlocking edges (protrusions and indents)
- Must properly connect with adjacent pieces for a valid arrangement
The solution must verify if all pieces fit together correctly considering their rotations.

# Visual Facts
1. Single Piece Properties:
   - Base piece (0°) has specific edge configuration:
     * Top: Protrusion
     * Right: Indent
     * Bottom: Indent
     * Left: Protrusion
   - Pieces can be rotated in 90° increments

2. Demonstrated Arrangements:
   - 1×2 puzzle: Two pieces both labeled 0
   - 2×2 puzzle: Top row [0,0], bottom row [90,90]

3. Edge Configurations:
   - Each edge must be either protrusion or indent
   - Connected edges must be complementary (protrusion-to-indent)
   - Edge pattern changes with rotation

# 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. Matching Rules:
   - Adjacent pieces must have complementary connecting edges
   - Horizontal connections: right edge of left piece must match left edge of right piece
   - Vertical connections: bottom edge of top piece must match top edge of bottom piece

3. Validation Requirements:
   - Each internal edge (shared between pieces) must form valid connections
   - All pieces must be properly oriented based on their rotation values
   - The entire arrangement must form a complete, interlocking structure
   - Edge types (protrusion/indent) must align correctly after rotation


# 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': 0,
                'L': 1
            },
            90: {
                'T': 1,
                'R': 1,
                'B': 0,
                'L': 0
            },
            180: {
                'T': 0,
                'R': 1,
                'B': 1,
                'L': 0
            },
            -90: {
                'T': 0,
                'R': 0,
                '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 = [
        ([[0, 0], [90, 90]], True),
        ([[0, 0], [0, 0]], True),
        ([[0, 0, 0]], True),
        ([[0, -90, -90]], True),
        ([[0, -90, 0, -90, 0]], True),
        ([[-90, -90, -90, -90, -90]], True),
        ([[180, 90, 90, 90, 90]], True),
        ([[0], [90], [0]], True),
        ([[90],[0],[90]], True),
        ([[-90],[180],[180]], True),
        ([[180], [-90], [180]], True),
        ([[180], [180], [180]], True),
        ([[0], [180], [180]], False),
        ([[0], [0]], True),
        ([[0, 180]], 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()