# Problem Description
This is a lock rotation problem where:
- We have a circular lock with 5 positions (0, a, b, c, d) arranged in a circle
- The lock starts at position '0' (initial position)
- We need to calculate the total rotation angle needed to input a given password
- The lock can only rotate in one direction (counter-clockwise based on the image)
- We need to reach each character in the password sequence in order
- The goal is to calculate the total cumulative angle of rotation

# Visual Facts
1. Layout Facts:
   - The lock has 5 character positions: 0, a, b, c, d
   - There's an initial position marked as '0'
   - The characters are arranged in a circular pattern
   - A pointer indicates the current position
   - The rotation direction is counter-clockwise (shown by dotted arrows)

2. Position Facts:
   - Initial position is '0'
   - From '0', character positions are:
     * 'a' is at 45° counter-clockwise
     * 'b' is at 135° counter-clockwise
     * 'c' is at 225° counter-clockwise
     * 'd' is at 315° counter-clockwise

3. Example Movement Facts:
   - Shows progression: '' → 'a' → 'ad'
   - First rotation: 45° to reach 'a'
   - Second rotation: 270° from 'a' to 'd'

# Visual Patterns
1. Rotation Patterns:
   - The lock only rotates counter-clockwise
   - Angles between adjacent positions are 90°
   - Total angle is the cumulative sum of all rotations

2. Movement Rules:
   - When moving between characters, calculate the counter-clockwise angle needed


# Solution
def solution(passwd: str) -> int:
    """
    Calculate the total angle needed to rotate the lock to unlock it. The lock can only rotate in one direction.

    Args:
        passwd (str): A string representing the passwd to unlock the lock, consisting of a sequence of
                        characters 'a', 'b', 'c', and 'd'. The passwd does not contain repeated adjacent characters.

    Returns:
        int: The total angle needed to spin the lock to unlock it, following the given passwd.
    """
    order = ['a', 'b', 'c', 'd']
    angle_per_step = 90  # 360 degrees divided by 4 positions
    original_angle = [45, 45+90, 45+180, 45+270]
    
    # Initialize total angle
    total_angle = original_angle[order.index(passwd[0])]
    
    # Start from the first character in the passwd
    for i in range(1, len(passwd)):
        # Find the current and previous positions
        current_position = order.index(passwd[i])
        previous_position = order.index(passwd[i-1])
        
        # Calculate the angle difference in clockwise direction
        steps = (current_position - previous_position) % 4
        total_angle += steps * angle_per_step
    
    return total_angle

# Test Script
def test():
    test_cases = [
        ('a', 45),
        ('ab', 135),
        ('bc', 225),
        ('d', 315),
        ('b', 135),
        ('cb', 495),
        ('ad', 315),
        ('ac', 225),
        ('aca', 405),
        ('adba', 765),
        ('abcd', 315),
        ('abcabcd', 675),
        ('acbdacbd', 1395)
    ]
    for passwd, expected_output in test_cases:
        try:
            output = solution(passwd)
        except Exception as e:
            error_message = (
                f"An exception occurred while running the test case:\n"
                f"`passwd`: {passwd}\n\n"
                f"Exception: '{str(e)}'"
            )
            assert False, error_message
            
        if output != expected_output:
            error_message = (
                f"The following Test Case failed:\n"
                f"`passwd`: {passwd}\n\n"
                f"Actual Output: {output}\n"
                f"Expected Output: {expected_output}"
            )
            assert False, error_message

test()