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: MEDIUM_HARD
Tags: Game theory

Ad-hoc

Starter code:
def __init__(self, x, y):
	

Solutions:
class PlayerMovement:
	PREC = [8, 2, 4, 6]
	I_KEYS = {8: 0, 2: 1, 4: 2, 6: 3}
	MOVES = {8: (0, 1), 2: (0, -1), 4: (-1, 0), 6: (1, 0)}

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

	def update(self):
		state = [Input.get_state(d) for d in self.PREC]
		newPressed = [d for (i, d) in enumerate(self.PREC) if not self.pressed[i] and state[i]]
		notReleased = next((d for d in self.stack[::-1] if self.pressed[self.I_KEYS[d]] and state[self.I_KEYS[d]]), None)
		releasedLst = [d for (i, d) in enumerate(self.PREC) if self.pressed[i] and (not state[i])]
		if newPressed:
			self.direction = newPressed[0]
			for t in newPressed[::-1]:
				self.stack.append(t)
		elif self.direction in releasedLst:
			self.direction = notReleased or self.direction
		elif notReleased:
			self.position = Tile(*(z + dz for (z, dz) in zip([self.position.x, self.position.y], self.MOVES[notReleased])))
		self.pressed = state
		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 = []

	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:
	KEYS = (6, 4, 2, 8)
	DIRS = dict(list(zip(KEYS, ((1, 0), (-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}
		self.keystack = [0]

	def update(self):
		current = dict(list(Input.STATES.items()))
		if current != self.previous:
			change = {i: current[i] - self.previous[i] for i in self.KEYS}
			for key in self.KEYS:
				if change[key] > 0:
					self.keystack.append(key)
				elif change[key] < 0:
					self.keystack.remove(key)
		if self.keystack[-1] == self.direction and sum(self.previous.values()):
			(dx, dy) = self.DIRS[self.direction]
			self.position = Tile(self.position.x + dx, self.position.y + dy)
		else:
			self.direction = self.keystack[-1] or self.direction
		self.previous = current


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):
		self.stack = list(filter(lambda x: Input.get_state(x), self.stack))
		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:

	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
				elif 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])


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
			elif 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