from collections import UserList
from enum import Enum

from attr import attrs, attrib
import pygame

from input import InputSystem


x_distances = (
    (25, 3, 23, 4),
    (0, 0, 0, 0),
    (25, 3, 23, 4),
)
y_distances = (
    (2, 9, 2, 9),
    (0, 0, 0, 0),
    (8, 2, 9, 2),
)
BOUNDARY = pygame.Rect(10, 30, 320 - 10, 155 - 30)


class Direction(Enum):
    LEFT = -1
    RIGHT = 1


@attrs(slots=True)
class Movement:
    input = attrib(type=InputSystem)
    position = attrib(
        type=pygame.Rect,
        converter=lambda p: pygame.Rect(p[0], p[1], 0, 0),
    )
    frame_num = attrib(type=int, default=0)
    direction = attrib(type=Direction, default=Direction.RIGHT)

    next_position = attrib(
        type=pygame.Rect,
        default=lambda: pygame.Rect(0, 0, 0, 0),
    )
    next_frame = attrib(type=int, default=0)
    move_frame = attrib(type=int, default=0)

    attack_frame = attrib(type=int, default=None)
    attack_anim_length = attrib(type=int, default=None)


class MovementSystem(UserList):
    def update(self):
        for mover in self.data:
            if mover.attack_frame is not None:
                mover.attack_frame += 1
                if mover.attack_frame < mover.attack_anim_length:
                    continue
                else:
                    mover.attack_frame = None
                    mover.attack_anim_length = None

            if mover.input.fire:
                mover.attack_frame = 0
                continue

            new_position, frame = MovementSystem.next_position(mover)
            direction = mover.input.direction
            if direction.x:
                mover.direction = Direction(mover.input.direction.x)

            x, y, new_position = MovementSystem.clamp_to_boundary(
                mover.position, direction, new_position)
            direction.x = x
            direction.y = y

            frame = frame * ((direction.x | direction.y) & 1)
            mover.next_frame = frame
            mover.next_position = new_position

    @staticmethod
    def next_position(mover):
        new_position = pygame.Rect(mover.position)

        direction = mover.input.direction

        is_moving = (direction.x | direction.y) & 1
        move_frame = ((mover.frame_num + 1) % 4) * is_moving

        x_delta = x_distances[direction.x + 1][move_frame] * direction.x
        new_position.x = mover.position.x + x_delta

        y_delta = y_distances[direction.y + 1][move_frame] * direction.y
        new_position.y = mover.position.y + y_delta
        return new_position, move_frame

    @staticmethod
    def clamp_to_boundary(position, direction, new_position):
        x = direction.x
        y = direction.y

        clamped = new_position.clamp(BOUNDARY)
        if clamped.x != new_position.x:
            new_position.x = position.x
            x = 0
        if clamped.y != new_position.y:
            new_position.y = position.y
            y = 0
        return x, y, new_position


movement_system = MovementSystem()

@@ -983,16 +983,940 @@
 =None)%0A%0A
+    def get_next_position(self):%0A        new_position = pygame.Rect(self.position)%0A%0A        direction = self.input.direction%0A%0A        is_moving = (direction.x %7C direction.y) & 1%0A        move_frame = ((self.frame_num + 1) %25 4) * is_moving%0A%0A        x_delta = x_distances%5Bdirection.x + 1%5D%5Bmove_frame%5D * direction.x%0A        new_position.x = self.position.x + x_delta%0A%0A        y_delta = y_distances%5Bdirection.y + 1%5D%5Bmove_frame%5D * direction.y%0A        new_position.y = self.position.y + y_delta%0A        return new_position, move_frame%0A%0A    def clamp_to_boundary(self, direction, new_position):%0A        x = direction.x%0A        y = direction.y%0A%0A        clamped = new_position.clamp(BOUNDARY)%0A        if clamped.x != new_position.x:%0A            new_position.x = self.position.x%0A            x = 0%0A        if clamped.y != new_position.y:%0A            new_position.y = self.position.y%0A            y = 0%0A        return x, y, new_position%0A%0A
 %0Aclass M
@@ -2429,31 +2429,26 @@
 frame = 
-MovementSystem.
+mover.get_
 next_pos
@@ -2453,21 +2453,16 @@
 osition(
-mover
 )%0A      
@@ -2632,30 +2632,21 @@
 ition = 
-MovementSystem
+mover
 .clamp_t
@@ -2660,41 +2660,8 @@
 ary(
-%0A                mover.position, 
 dire
@@ -2837,16 +2837,16 @@
 = frame%0A
+
         
@@ -2889,964 +2889,8 @@
 on%0A%0A
-    @staticmethod%0A    def next_position(mover):%0A        new_position = pygame.Rect(mover.position)%0A%0A        direction = mover.input.direction%0A%0A        is_moving = (direction.x %7C direction.y) & 1%0A        move_frame = ((mover.frame_num + 1) %25 4) * is_moving%0A%0A        x_delta = x_distances%5Bdirection.x + 1%5D%5Bmove_frame%5D * direction.x%0A        new_position.x = mover.position.x + x_delta%0A%0A        y_delta = y_distances%5Bdirection.y + 1%5D%5Bmove_frame%5D * direction.y%0A        new_position.y = mover.position.y + y_delta%0A        return new_position, move_frame%0A%0A    @staticmethod%0A    def clamp_to_boundary(position, direction, new_position):%0A        x = direction.x%0A        y = direction.y%0A%0A        clamped = new_position.clamp(BOUNDARY)%0A        if clamped.x != new_position.x:%0A            new_position.x = position.x%0A            x = 0%0A        if clamped.y != new_position.y:%0A            new_position.y = position.y%0A            y = 0%0A        return x, y, new_position%0A%0A
 %0Amov

change staticmethod to methods for Movement