Task

Create a top-down movement system that would feel highly responsive to the player. In your Update method you have to check for the keys that are currently being pressed, the keys correspond to the enum Direction shown below, based on which key is pressed or released your method should behave this way:

1) When a key is first pressed, the player has to change his direction to that of the current key, without moving

2) If the key is still being pressed during the next Update, the player will move towards his current direction using these vectors: (Up = { 0, +1 } , Down = { 0, -1 }, Left = { -1, 0 }, Right = { +1, 0 })

3) If a new key is pressed, it will gain precedence over the previous key and the player will act as per 1)

4-A) If the current key (A) is released, then the precedence will go back to the previous key (B) (or the one before it, if (B) is not pressed anymore, and so on), then the player will behave as per 1).

4-B) If the current key is released, and no other keys are being pressed, the player will stand still

5) If all keys are released at once, the player will not move nor change direction

6) If multiple keys are pressed at once, the order of precedence will be the following { Up, Down, Left, Right } 

Examples

    (n = pressed key, [n] = current key, p() = press, r() = release, (8,2,4,6 = up, down, left, right)):

    [] , p(8) -> [8] , p(4,6) -> 86[4] , r(6) -> 8[4] , r(4) -> [8] , r(8) -> []

    [] , p(2486) -> 642[8] , r(2,8) -> 6[4] , r(4,6) -> []

This is what you'll need to use in your code (NB: the tile coordinates cannot be changed, you'll need to assign a new Tile each time the player moves):

```python
class Tile:

    @property
    def x(self):
        return self._x

    @property
    def y(self):
        return self._y
            
    def __str__(self):
        return "({},{})".format(self._x, self._y)

class Input:

    @staticmethod
    def get_state(direction): # 2, 4, 6, 8
        return Input.STATES[direction] # pressed = true, released = false
```

Difficulty: interview

Starter code:

def __init__(self, x, y):
	

Solutions:
class PlayerMovement:
    
    PREC   = [8,        2,         4,        6]                                                                               # Order of precedence
    I_KEYS = {8: 0,     2: 1,      4: 2,      6: 3}                                                                           # Index of the keys in self.pressed
    MOVES  = {8: (0,1), 2: (0,-1), 4: (-1,0), 6: (1,0)}                                                                       # Moves directions

    def __init__(self, x, y):
        self.position  = Tile(x, y)                                                                                           # Current position
        self.direction = 8                                                                                                    # Current direction of move
        self.pressed   = [0,0,0,0]                                                                                            # Keys currently pressed or not (True/False)
        self.stack     = []                                                                                                   # Stack representing the order of the pressed keys (according to pressing order AND precedence if multiple pressing at the same time)

    def update(self):
        state       = [Input.get_state(d) for d in self.PREC]                                                                 # State of the art at update time
        
        newPressed  =     [ d for i,d in enumerate(self.PREC) if not self.pressed[i] and state[i] ]                           # All keys freshly pressed
        notReleased = next((d for d   in self.stack[::-1]  if self.pressed[self.I_KEYS[d]] and state[self.I_KEYS[d]]), None)  # Last key that has not been released yet (according to the order of the stack[::-1] because one search for the last pressed)
        releasedLst =     [ d for i,d in enumerate(self.PREC) if self.pressed[i] and not state[i] ]                           # All keys freshly released
        
        if newPressed:                                                                                                        # If new key pressed:
            self.direction = newPressed[0]                                                                                    #     Update direction with higher precedence
            for t in newPressed[::-1]: self.stack.append(t)                                                                   #     append all the new kleys to the stack, lower preccedence first
            
        elif self.direction in releasedLst:                                                                                   # If the current direction has been released:
            self.direction = notReleased or self.direction                                                                    #     upadte direction. If no pressed key remain, do not update
                
        elif notReleased:                                                                                                     # If current direction still pressed and no other key pressed in the meantime:
            self.position  = Tile(*( z+dz for z,dz in zip([self.position.x, self.position.y], self.MOVES[notReleased]) ))     #     MOVE!
            
        self.pressed = state                                                                                                  # Archive current state of keys
        for t in releasedLst: self.stack.remove(t)                                                                            # remove all the released keys from the stack, whatever their position in the stack is


class PlayerMovement:
    def __init__(self, x, y):
        self.position = Tile(x, y)
        self.direction = 8
        self.pressed = []

    def update(self):
        can_move = bool(self.pressed)
        for key in [6, 4, 2, 8]:
            pressed = Input.get_state(key)
            if not pressed and key in self.pressed:
                self.pressed.remove(key)
            elif pressed and key not in self.pressed:
                self.pressed.append(key)
        if self.pressed:
            if self.direction == self.pressed[-1] and can_move:
                self.move(self.direction)
            self.direction = self.pressed[-1]
    
    def move(self, direction):
        dx, dy = PlayerMovement.DIR_VECT[direction]
        self.position = Tile(
            self.position.x + dx,
            self.position.y + dy
        )

PlayerMovement.DIR_VECT = {8: (0, 1), 2: (0, -1), 4: (-1, 0), 6: (1, 0)}


class PlayerMovement:                                              # class by MMMAAANNN @ codewars.com

    KEYS = 6, 4, 2, 8                                              # In order of reverse priority
    DIRS = dict(list(zip(KEYS, (( 1,  0),                               # Associating with directions
                           (-1,  0),
                           ( 0, -1),
                           ( 0,  1)))))
    
    def __init__(self, x, y):
        self.position  = Tile(x, y)
        self.direction = 8
        self.previous  = {i: False for i in self.KEYS}            # Event detection
        self.keystack  = [0]                                      # The order of keypress

    def update(self):
        current = dict(list(Input.STATES.items()))                      # Read current state
        if (current != self.previous):                            # Skip recalc if no events happened
            change = {i : current[i] - self.previous[i]           # Calculate type of change
                      for i in self.KEYS}
            for key in self.KEYS:                                 # Update the status of keystack
                if   change[key] > 0:
                    self.keystack.append(key)
                elif change[key] < 0:
                    self.keystack.remove(key)
        if (self.keystack[-1] == self.direction                   # Movement only if no turn required
            and sum(self.previous.values())):                     # Avoid moving if first event after idle
            dx, dy = self.DIRS[self.direction]                    # Reading the movement vector
            self.position = Tile(self.position.x + dx,            # Updating coordinates: x
                                 self.position.y + dy)            #               ... and y
        else:
            self.direction = self.keystack[-1] or self.direction  # Turning if needed
        self.previous = current                                   # Remember state to detect next event


class PlayerMovement:

   def __init__(self, x, y):
      self.position = Tile(x, y)
      self.direction = 8
      self.stack = []
      self.moves = {8:[0,1],2:[0,-1],4:[-1,0],6:[1,0]}

   def update(self):
      # release
      self.stack = list(filter(lambda x: Input.get_state(x),self.stack))

      # push
      firsttimer = False
      for i in [6,4,2,8]:
         if not i in self.stack and Input.get_state(i):
            firsttimer = True
            self.stack.append(i)
      
      if not self.stack:
         return
      
      winner = self.stack[-1]

      if winner != self.direction or firsttimer:
         self.direction = winner
      else:
         move = self.moves[self.direction]
         self.position =  Tile(self.position.x+move[0], self.position.y+move[1])

from enum import IntEnum

class PlayerMovement:

    class Dir(IntEnum):
        UP = 4
        DOWN = 3
        LEFT = 2
        RIGHT = 1
        
        @classmethod
        def get_dir(cls, key):
            map = {8: cls.UP, 2: cls.DOWN, 4: cls.LEFT, 6: cls.RIGHT}
            return map[key]
        
        @classmethod
        def get_key(cls, dir):
            map = {cls.UP: 8, cls.DOWN: 2, cls.LEFT: 4, cls.RIGHT: 6}
            return map[dir]
        
        @classmethod
        def get_move(cls, dir):
            map = {cls.UP: (0,1), cls.DOWN: (0, -1), cls.LEFT: (-1, 0), cls.RIGHT: (1, 0)}
            return map[dir]
            
                            
    def __init__(self, x, y):
        self.position = Tile(x, y)
        self.direction = 8
        self.directions = []

    def update_directions(self):
        pressed = [self.Dir.get_dir(x) for x in (8, 2, 4, 6) if Input.get_state(x)==True]
        self.directions = [x for x in self.directions if x in pressed]
        pressed.sort()
        for x in pressed:
            if x not in self.directions:
                self.directions.append(x)

    def update(self):
        directions_before = self.directions
        self.update_directions()
        if len(self.directions) == 0:
            pass
        elif self.directions[-1] != self.Dir.get_dir(self.direction) \
        or self.directions[-1] not in directions_before:
            self.direction = self.Dir.get_key(self.directions[-1])
        else:
            move = self.Dir.get_move(self.directions[-1])
            self.position = Tile(self.position.x + move[0], self.position.y + move[1])


class PlayerMovement:
    
    PREC   = [8,        2,         4,        6]                                                                               # Order of precedence
    I_KEYS = {8: 0,     2: 1,      4: 2,      6: 3}                                                                           # Index of the keys in self.pressed
    MOVES  = {8: (0,1), 2: (0,-1), 4: (-1,0), 6: (1,0)}                                                                       # Moves directions

    def __init__(self, x, y):
        self.position  = Tile(x, y)                                                                                           # Current position
        self.direction = 8                                                                                                    # Current direction of move
        self.pressed   = [0,0,0,0]                                                                                            # Keys currently pressed or not (True/False)
        self.stack     = []                                                                                                   # Stack representing the order of the pressed keys (according to pressing order AND precedence if multiple pressing at the same time)

    def update(self):
        state       = [Input.get_state(d) for d in self.PREC]                                                                 # State of the art at update time
        
        newPressed  =     [ d for i,d in enumerate(self.PREC) if not self.pressed[i] and state[i] ]                           # All keys freshly pressed
        notReleased = next((d for d   in self.stack[::-1]  if self.pressed[self.I_KEYS[d]] and state[self.I_KEYS[d]]), None)  # Last key that has not been released yet (according to the order of the stack[::-1] because one search for the last pressed)
        releasedLst =     [ d for i,d in enumerate(self.PREC) if self.pressed[i] and not state[i] ]                           # All keys freshly released
        
        if newPressed:                                                                                                        # If new key pressed:
            self.direction = newPressed[0]                                                                                    #     Update direction with higher precedence
            for t in newPressed[::-1]: self.stack.append(t)                                                                   #     append all the new kleys to the stack, lower preccedence first
            
        elif self.direction in releasedLst:                                                                                   # If the current direction has been released:
            self.direction = notReleased or self.direction                                                                    #     upadte direction. If no pressed key remain, do not update
                
        elif notReleased:                                                                                                     # If current direction still pressed and no other key pressed in the meantime:
            self.position  = Tile(*( z+dz for z,dz in zip([self.position.x, self.position.y], self.MOVES[notReleased]) ))     #     MOVE!
            
        self.pressed = state                                                                                                  # Archive current state of keys
        for t in releasedLst: self.stack.remove(t)                                        

class PlayerMovement:
    def __init__(self, x, y):
        self.position = Tile(x, y)
        self.direction = 8
        self.pressed = []
        self.movements = {8: (0, 1), 2: (0, -1), 6: (1, 0), 4: (-1, 0)}

    def update(self):
        newkey = False
        if any(Input.get_state(x) for x in (2, 4, 6, 8)):
            for x in (6, 4, 2, 8):
                if Input.get_state(x):
                    if not x in self.pressed:
                        self.pressed.append(x)
                        self.direction = x
                        newkey = True
                else:
                    if x in self.pressed:
                        if self.pressed[-1] == x:
                            newkey = True
                            if len(self.pressed) > 1:
                                self.direction = self.pressed[-2]
                        self.pressed.remove(x)
        else:
            self.pressed = []
            newkey = True
        if not newkey:
            self.position = Tile(self.position.x + self.movements[self.direction][0], self.position.y + self.movements[self.direction][1])

class PlayerMovement:
    def __init__(self, x, y):
        self.position = Tile(x, y)
        self.direction = 8
        self.pressed = []
        self.movements = {8: (0, 1), 2: (0, -1), 6: (1, 0), 4: (-1, 0)}

    def update(self):
        newkey = False
        if any(Input.get_state(x) for x in (2, 4, 6, 8)):
            if Input.get_state(6):
                if not 6 in self.pressed:
                    self.pressed.append(6)
                    self.direction = 6
                    newkey = True
            else:
                if 6 in self.pressed:
                    if self.pressed[-1] == 6:
                        newkey = True
                        if len(self.pressed) > 1:
                            self.direction = self.pressed[-2]
                    self.pressed.remove(6)
    
            if Input.get_state(4):
                if not 4 in self.pressed:
                    self.pressed.append(4)
                    self.direction = 4
                    newkey = True
            else:
                if 4 in self.pressed:
                    if self.pressed[-1] == 4:
                        newkey = True
                        if len(self.pressed) > 1:
                            self.direction = self.pressed[-2]
                    self.pressed.remove(4)
            
            if Input.get_state(2):
                if not 2 in self.pressed:
                    self.pressed.append(2)
                    self.direction = 2
                    newkey = True
            else:
                if 2 in self.pressed:
                    if self.pressed[-1] == 2:
                        newkey = True
                        if len(self.pressed) > 1:
                            self.direction = self.pressed[-2]
                    self.pressed.remove(2)
            
            if Input.get_state(8):
                if not 8 in self.pressed:
                    self.pressed.append(8)
                    self.direction = 8
                    newkey = True
            else:
                if 8 in self.pressed:
                    if self.pressed[-1] == 8:
                        newkey = True
                        if len(self.pressed) > 1:
                            self.direction = self.pressed[-2]
                    self.pressed.remove(8)
                    
        else:
            self.pressed = []
            newkey = True
        
        if not newkey and self.pressed:
            self.position = Tile(self.position.x + self.movements[self.direction][0], self.position.y + self.movements[self.direction][1])


from collections import Counter
class PlayerMovement:

    def __init__(self, x, y):
        self.position = Tile(x, y)
        self.direction = 8
        self.moves = {8:[0,1],2:[0,-1],4:[-1,0],6:[1,0]}
        self.keys = []
    def update(self):
        release = [i for i in [8,2,4,6] if not Input.get_state(i)]
        if any(i in self.keys for i in release):
            if len(self.keys)==1 : return self.keys.pop()
            for i in release:
                if i in self.keys : self.keys.pop(len(self.keys)-self.keys[::-1].index(i)-1)
            if not self.keys : return
            if self.direction == self.keys[-1]:
                  inc,dec = self.moves[self.direction]
                  self.position = Tile(self.position.x+inc,self.position.y+dec)
            self.direction = self.keys[-1]
        else:
            direction = [i for i in [8,2,4,6] if Input.get_state(i)]
            new, c = list((Counter(direction)-Counter(self.keys)).elements()), bool(self.keys)
            if not new and not self.keys : return
            self.keys.extend(sorted(new,key=[6,4,2,8].index))
            d = self.keys[-1]
            if not c or (d != self.direction): self.direction = d ; return
            inc,dec = self.moves[self.direction]
            self.position = Tile(self.position.x+inc,self.position.y+dec)

KEYS = ["Right", "Left", "Down", "Up"]
DIRS = [6, 4, 2, 8]
KEYS_DIR = {6: "Right", 4: "Left", 2: "Down", 8: "Up"}
KEYS_DIR_REV = {"Right": 6, "Left": 4, "Down": 2, "Up": 8}
MOVE = [(1, 0), (-1, 0), (0, -1), (0, 1)]
firstTime = False

class PlayerMovement:

    def __init__(self, x, y):
        self.position = Tile(x, y)
        self.direction = 8
        self.pressedList = []

    def update(self):
        savedDirection = self.direction
        savedLen = len(self.pressedList)
        i = 0
        firstTime = False
        while i < 4:
            Dir = KEYS_DIR_REV[KEYS[i]]
            Key = KEYS_DIR[DIRS[i]]
            if Input.get_state(Dir):
                if Key not in self.pressedList:
                    self.pressedList.append(Key)
                    self.direction = KEYS_DIR_REV[Key]
                    firstTime = True
            else:
                if Key in self.pressedList:
                    if self.pressedList[-1] == Key:
                        del self.pressedList[-1]
                        if len(self.pressedList) > 0:
                            self.direction = KEYS_DIR_REV[self.pressedList[-1]]
                            firstTime = True
                    else:
                        del self.pressedList[self.pressedList.index(Key)]
            i += 1
        if len(self.pressedList) > 0 and firstTime == False:
            index = KEYS.index(self.pressedList[-1])
            self.position = Tile(self.position.x + MOVE[index][0], self.position.y + MOVE[index][1])
        if savedLen > 0:
            check = 0
            for D in DIRS:
                if Input.get_state(D):
                    check = 1
                    break
            if check == 0:
                self.direction = savedDirection