# Problem Description
This is a puzzle validation problem where we need to determine if a given arrangement of puzzle pieces is valid. Each piece:
- Has a rotation angle (0°, 90°, 180°, or -90°)
- Has sides with either protrusions or indents
- Must properly connect with adjacent pieces (protrusion must match with indent)
The goal is to verify if all pieces in the arrangement fit together properly based on their rotations.

# Visual Facts
1. Piece Structure:
   - Each puzzle piece is square-shaped with special edges
   - Each edge has either a protrusion or an indent
   - The initial piece is labeled 0 and has a specific edge configuration, where the bottom edge has an indent, and the other three edges have protrusions
   - Pieces can be rotated in 90-degree increments
   

2. Rotation Examples:
   - A piece with value 0 when rotated 90° becomes a piece with value 90
   - Rotation values shown: 0, 90, 180, -90

3. Arrangements Shown:
   - 1×2 arrangement: [0, 90]
   - 2×2 arrangement: [[-90, 0], [180, 90]]

4. Edge Matching:
   - Adjacent pieces must have complementary edges (protrusion meets indent)
   - The sample arrangements shown are all valid configurations
   - Every puzzle has one indent side and three protrusion sides.For deferent rotations of puzzle, the indent side is at: 
            0: bottom side,
            90: left side,
            180: top side,
            -90: right side,
   - To sum up, let 1 to be protrusion and 0 to be indent, the status of all kind of puzzles can be defined as（T:top，R:right, B:bottom, L:left）:
      edges = {
               0: {
                  'T': 1,
                  'R': 1,
                  'B': 0,
                  'L': 1
               },
               90: {
                  'T': 1,
                  'R': 1,
                  'B': 1,
                  'L': 0
               },
               180: {
                  'T': 0,
                  'R': 1,
                  'B': 1,
                  'L': 1
               },
               -90: {
                  'T': 1,
                  'R': 0,
                  'B': 1,
                  'L': 1
               }
         }
   
# Visual Patterns
1. Rotation Rules:
   - Rotations are always in multiples of 90 degrees
   - The number in each piece represents its rotation from the base position

2. Connection Rules:
   - When two pieces are adjacent, their connecting edges must be complementary
   - A protrusion on one piece must meet an indent on the adjacent piece
   - This rule applies both horizontally and vertically

3. Validation Pattern:
   - Check evrey puzzle's protrusion sides whether they are adjacent with another puzzle's indent side or not adjacent with other puzzles.(e.g., [0, 90], the first puzzle's right side(for puzzle 0, the indent side is at bottom so the the other sides is protrusion) is adjacent with the second puzzle(90) and the second puzzle's left side is indent, therefore this combination is legal.)



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