diff --git a/cooperative_cuisine/__init__.py b/cooperative_cuisine/__init__.py
index c32875fb9546c2096f130f0f95c37af06046c29e..b733be005bbcc76e9ca7ffb13f1caf5027178800 100644
--- a/cooperative_cuisine/__init__.py
+++ b/cooperative_cuisine/__init__.py
@@ -309,7 +309,7 @@ orders:
 
 player_config:
   radius: 0.4
-  player_speed_units_per_seconds: 8
+  speed_units_per_seconds: 8
   interaction_range: 1.6
 ```
 
@@ -367,6 +367,7 @@ On the left you can find the navigation panel that brings you to the implementat
 websockets,
 - the **hooks**, you can adapt and extend recording, scoring, msgs to players, etc. via hooks,
 - the **info msgs** to players, based on hook events, short text msgs can be generated,
+- the calculation of player **movement**,
 - the **orders**, how to sample incoming orders and their attributes,
 - the **player**/agent, that interacts in the environment,
 - the **pygame 2d visualization**, GUI, drawing, and video generation,
diff --git a/cooperative_cuisine/configs/agents/random_agent.py b/cooperative_cuisine/configs/agents/random_agent.py
index 1c36a303bdfd238d4ef0c6c41a06aed8c3c21436..db05c7a9fca1f19bf7989eace7bf48edf325d3a3 100644
--- a/cooperative_cuisine/configs/agents/random_agent.py
+++ b/cooperative_cuisine/configs/agents/random_agent.py
@@ -61,6 +61,8 @@ async def agent():
                 json.dumps({"type": "get_state", "player_hash": args.player_hash})
             )
             state = json.loads(await websocket.recv())
+            if not state["all_players_ready"]:
+                continue
 
             if counters is None:
                 counters = defaultdict(list)
diff --git a/cooperative_cuisine/configs/environment_config.yaml b/cooperative_cuisine/configs/environment_config.yaml
index d589789dcb43f573f2cc9fa212845b880399873c..6de17894c80e96ec709cb2fab37b858764c7c0d8 100644
--- a/cooperative_cuisine/configs/environment_config.yaml
+++ b/cooperative_cuisine/configs/environment_config.yaml
@@ -82,7 +82,7 @@ orders:
 
 player_config:
   radius: 0.4
-  player_speed_units_per_seconds: 6
+  speed_units_per_seconds: 6
   interaction_range: 1.6
   restricted_view: False
   view_angle: 70
diff --git a/cooperative_cuisine/configs/study/level1/level1_config.yaml b/cooperative_cuisine/configs/study/level1/level1_config.yaml
index 7dad55acb1a62c84628b0b4b29411c4483a5fc95..6732fa298663a9e6c70094abef84dff2d3d5cbc4 100644
--- a/cooperative_cuisine/configs/study/level1/level1_config.yaml
+++ b/cooperative_cuisine/configs/study/level1/level1_config.yaml
@@ -81,7 +81,7 @@ orders:
 
 player_config:
   radius: 0.4
-  player_speed_units_per_seconds: 6
+  speed_units_per_seconds: 6
   interaction_range: 1.6
   restricted_view: False
   view_angle: 70
diff --git a/cooperative_cuisine/configs/study/level2/level2_config.yaml b/cooperative_cuisine/configs/study/level2/level2_config.yaml
index 918a653361e4e3ef1d3c1eef99319dcd44de4288..5bc7dd67857bafa549a9863138bb013749010df0 100644
--- a/cooperative_cuisine/configs/study/level2/level2_config.yaml
+++ b/cooperative_cuisine/configs/study/level2/level2_config.yaml
@@ -81,7 +81,7 @@ orders:
 
 player_config:
   radius: 0.4
-  player_speed_units_per_seconds: 6
+  speed_units_per_seconds: 6
   interaction_range: 1.6
   restricted_view: True
   view_angle: 100
diff --git a/cooperative_cuisine/configs/study/study_config.yaml b/cooperative_cuisine/configs/study/study_config.yaml
index e20ee61ff83613bd758ea6d6b5406ed2b640067b..4feba6bfa4f86a29e47dd178779854a9bf577ea6 100644
--- a/cooperative_cuisine/configs/study/study_config.yaml
+++ b/cooperative_cuisine/configs/study/study_config.yaml
@@ -19,5 +19,5 @@ levels:
     name: "Level 1-4: Bottleneck"
 
 
-num_players: 1
-num_bots: 0
+num_players: 2
+num_bots: 12
diff --git a/cooperative_cuisine/configs/tutorial_env_config.yaml b/cooperative_cuisine/configs/tutorial_env_config.yaml
index 1a41ba318239c52e435ae32ad9bdd34d34de6c6e..9b9011aee9cd1c57fe7edcc0f0b61555236eba40 100644
--- a/cooperative_cuisine/configs/tutorial_env_config.yaml
+++ b/cooperative_cuisine/configs/tutorial_env_config.yaml
@@ -79,7 +79,7 @@ orders:
 
 player_config:
   radius: 0.4
-  player_speed_units_per_seconds: 6
+  speed_units_per_seconds: 6
   interaction_range: 1.6
   restricted_view: False
   view_angle: 70
diff --git a/cooperative_cuisine/counter_factory.py b/cooperative_cuisine/counter_factory.py
index 2dc90d33f9ef70c837f042b1e991304f732bfe6a..5480a4c950e314638aa9d72b2ecedbb91da06a7a 100644
--- a/cooperative_cuisine/counter_factory.py
+++ b/cooperative_cuisine/counter_factory.py
@@ -34,7 +34,7 @@ layout_chars:
 import inspect
 import sys
 from random import Random
-from typing import Any, Type, TypeVar
+from typing import Any, Type, TypeVar, Tuple
 
 import numpy as np
 import numpy.typing as npt
@@ -384,3 +384,129 @@ class CounterFactory:
     def get_counter_of_type(counter_type: Type[T], counters: list[Counter]) -> list[T]:
         """Filter all counters in the environment for a counter type."""
         return list(filter(lambda counter: isinstance(counter, counter_type), counters))
+
+    def parse_layout_file(
+        self,
+        layout_config: str,
+    ) -> Tuple[list[Counter], list[npt.NDArray], list[npt.NDArray], int, int]:
+        """Creates layout of kitchen counters in the environment based on layout file.
+        Counters are arranged in a fixed size grid starting at [0,0]. The center of the first counter is at
+        [counter_size/2, counter_size/2], counters are directly next to each other (of no empty space is specified
+        in layout).
+
+        Args:
+            layout_config: the layout config string. Each character corresponds to a counter and each line to a row.
+        """
+
+        starting_at: float = 0.0
+        current_y: float = starting_at
+        counters: list[Counter] = []
+        designated_player_positions: list[npt.NDArray] = []
+        free_positions: list[npt.NDArray] = []
+
+        lines = layout_config.split("\n")
+
+        grid = []
+
+        max_width = 0
+
+        lines = list(filter(lambda l: l != "", lines))
+        for line in lines:
+            line = line.replace(" ", "")
+            if not line or line.startswith(";"):
+                break
+            current_x: float = starting_at
+            grid_line = []
+
+            for character in line:
+                # character = character.capitalize()
+                pos = np.array([current_x, current_y])
+
+                assert self.can_map(
+                    character
+                ), f"{character=} in layout file can not be mapped"
+                if self.is_counter(character):
+                    counters.append(self.get_counter_object(character, pos))
+                    grid_line.append(1)
+                else:
+                    grid_line.append(0)
+                    match self.map_not_counter(character):
+                        case "Agent":
+                            designated_player_positions.append(pos)
+                        case "Free":
+                            free_positions.append(np.array([current_x, current_y]))
+
+                current_x += 1
+
+            if len(line) >= max_width:
+                max_width = len(line)
+
+            grid.append(grid_line)
+            current_y += 1
+
+        grid = [line + ([0] * (max_width - len(line))) for line in grid]
+
+        kitchen_width = int(max_width + starting_at)
+        kitchen_height = int(current_y)
+
+        determine_counter_orientations(
+            counters, grid, np.array([kitchen_width / 2, kitchen_height / 2])
+        )
+
+        self.post_counter_setup(counters)
+
+        return (
+            counters,
+            designated_player_positions,
+            free_positions,
+            kitchen_width,
+            kitchen_height,
+        )
+
+
+def determine_counter_orientations(counters, grid, kitchen_center):
+    grid = np.array(grid).T
+
+    grid_width = grid.shape[0]
+    grid_height = grid.shape[1]
+
+    last_counter = None
+    fst_counter_in_row = None
+    for c in counters:
+        grid_idx = np.floor(c.pos).astype(int)
+        neighbour_offsets = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]], dtype=int)
+
+        neighbours_free = []
+        for offset in neighbour_offsets:
+            neighbour_pos = grid_idx + offset
+            if (
+                neighbour_pos[0] > (grid_width - 1)
+                or neighbour_pos[0] < 0
+                or neighbour_pos[1] > (grid_height - 1)
+                or neighbour_pos[1] < 0
+            ):
+                pass
+            else:
+                if grid[neighbour_pos[0]][neighbour_pos[1]] == 0:
+                    neighbours_free.append(offset)
+        if len(neighbours_free) > 0:
+            vector_to_center = c.pos - kitchen_center
+            vector_to_center /= np.linalg.norm(vector_to_center)
+            n_idx = np.argmin(
+                np.linalg.norm(vector_to_center - n) for n in neighbours_free
+            )
+            nearest_vec = neighbours_free[n_idx]
+            # print(nearest_vec, type(nearest_vec))
+            c.set_orientation(nearest_vec)
+
+        elif grid_idx[0] == 0:
+            if grid_idx[1] == 0 or fst_counter_in_row is None:
+                # counter top left
+                c.set_orientation(np.array([1, 0]))
+            else:
+                c.set_orientation(fst_counter_in_row.orientation)
+            fst_counter_in_row = c
+        else:
+            c.set_orientation(last_counter.orientation)
+
+        last_counter = c
diff --git a/cooperative_cuisine/environment.py b/cooperative_cuisine/environment.py
index 8b289e15d3ad9723d593deeab3be66f8173cb057..16c568eaaffbe701308f8563bb379e593a79f5c1 100644
--- a/cooperative_cuisine/environment.py
+++ b/cooperative_cuisine/environment.py
@@ -12,19 +12,19 @@ from datetime import timedelta, datetime
 from enum import Enum
 from pathlib import Path
 from random import Random
-from typing import Literal, TypedDict, Callable, Tuple
+from typing import Literal, TypedDict, Callable
 
 import networkx
 import numpy as np
 import numpy.typing as npt
 import yaml
 from networkx import DiGraph
-from scipy.spatial import distance_matrix
 
 from cooperative_cuisine import ROOT_DIR
-from cooperative_cuisine.counter_factory import CounterFactory
+from cooperative_cuisine.counter_factory import (
+    CounterFactory,
+)
 from cooperative_cuisine.counters import (
-    Counter,
     PlateConfig,
 )
 from cooperative_cuisine.effect_manager import EffectManager
@@ -52,13 +52,17 @@ from cooperative_cuisine.hooks import (
     ITEM_INFO_CONFIG,
     POST_STEP,
 )
+from cooperative_cuisine.movement import Movement
 from cooperative_cuisine.orders import (
     OrderManager,
     OrderConfig,
 )
 from cooperative_cuisine.player import Player, PlayerConfig
 from cooperative_cuisine.state_representation import InfoMsg
-from cooperative_cuisine.utils import create_init_env_time, get_closest
+from cooperative_cuisine.utils import (
+    create_init_env_time,
+    get_closest,
+)
 
 log = logging.getLogger(__name__)
 
@@ -165,6 +169,8 @@ class Environment:
         if self.as_files:
             with open(env_config, "r") as file:
                 env_config = file.read()
+            with open(layout_config, "r") as layout_file:
+                layout_config = layout_file.read()
 
         self.environment_config: EnvironmentConfig = yaml.load(
             env_config, Loader=yaml.Loader
@@ -218,11 +224,6 @@ class Environment:
         )
         """The manager for the orders and score update."""
 
-        self.kitchen_height: int = 0
-        """The height of the kitchen, is set by the `Environment.parse_layout_file` method"""
-        self.kitchen_width: int = 0
-        """The width of the kitchen, is set by the `Environment.parse_layout_file` method"""
-
         self.counter_factory = CounterFactory(
             layout_chars_config=self.environment_config["layout_chars"],
             item_info=self.item_info,
@@ -253,22 +254,20 @@ class Environment:
             self.counters,
             self.designated_player_positions,
             self.free_positions,
-        ) = self.parse_layout_file()
+            self.kitchen_width,
+            self.kitchen_height,
+        ) = self.counter_factory.parse_layout_file(self.layout_config)
         self.hook(LAYOUT_FILE_PARSED)
 
-        self.world_borders = np.array(
-            [[-0.5, self.kitchen_width - 0.5], [-0.5, self.kitchen_height - 0.5]],
-            dtype=float,
+        self.movement = Movement(
+            counter_positions=np.array([c.pos for c in self.counters]),
+            player_config=self.environment_config["player_config"],
+            world_borders=np.array(
+                [[-0.5, self.kitchen_width - 0.5], [-0.5, self.kitchen_height - 0.5]],
+                dtype=float,
+            ),
         )
 
-        self.player_movement_speed = self.environment_config["player_config"][
-            "player_speed_units_per_seconds"
-        ]
-        self.player_radius = self.environment_config["player_config"]["radius"]
-        self.player_interaction_range = self.environment_config["player_config"][
-            "interaction_range"
-        ]
-
         progress_counter_classes = list(
             filter(
                 lambda cl: hasattr(cl, "progress"),
@@ -287,8 +286,6 @@ class Environment:
         )
         """Counters that needs to be called in the step function via the `progress` method."""
 
-        self.counter_positions = np.array([c.pos for c in self.counters])
-
         self.order_manager.create_init_orders(self.env_time)
         self.start_time = self.env_time
         """The relative env time when it started."""
@@ -316,7 +313,7 @@ class Environment:
 
     def overwrite_counters(self, counters):
         self.counters = counters
-        self.counter_positions = np.array([c.pos for c in self.counters])
+        self.movement.counter_positions = np.array([c.pos for c in self.counters])
 
         progress_counter_classes = list(
             filter(
@@ -340,15 +337,6 @@ class Environment:
         """Whether the game is over or not based on the calculated `Environment.env_time_end`"""
         return self.env_time >= self.env_time_end
 
-    def set_collision_arrays(self):
-        number_players = len(self.players)
-        self.world_borders_lower = self.world_borders[np.newaxis, :, 0].repeat(
-            number_players, axis=0
-        )
-        self.world_borders_upper = self.world_borders[np.newaxis, :, 1].repeat(
-            number_players, axis=0
-        )
-
     def get_env_time(self):
         """the internal time of the environment. An environment starts always with the time from `create_init_env_time`.
 
@@ -442,134 +430,6 @@ class Environment:
         """TODO"""
         raise NotImplementedError
 
-    def parse_layout_file(
-        self,
-    ) -> Tuple[list[Counter], list[npt.NDArray], list[npt.NDArray]]:
-        """Creates layout of kitchen counters in the environment based on layout file.
-        Counters are arranged in a fixed size grid starting at [0,0]. The center of the first counter is at
-        [counter_size/2, counter_size/2], counters are directly next to each other (of no empty space is specified
-        in layout).
-        """
-
-        starting_at: float = 0.0
-        current_y: float = starting_at
-        counters: list[Counter] = []
-        designated_player_positions: list[npt.NDArray] = []
-        free_positions: list[npt.NDArray] = []
-
-        if self.as_files:
-            with open(self.layout_config, "r") as layout_file:
-                self.layout_config = layout_file.read()
-        lines = self.layout_config.split("\n")
-
-        grid = []
-
-        max_width = 0
-
-        lines = list(filter(lambda l: l != "", lines))
-        for line in lines:
-            line = line.replace(" ", "")
-            if not line or line.startswith(";"):
-                break
-            current_x: float = starting_at
-            grid_line = []
-
-            for character in line:
-                # character = character.capitalize()
-                pos = np.array([current_x, current_y])
-
-                assert self.counter_factory.can_map(
-                    character
-                ), f"{character=} in layout file can not be mapped"
-                if self.counter_factory.is_counter(character):
-                    counters.append(
-                        self.counter_factory.get_counter_object(character, pos)
-                    )
-                    grid_line.append(1)
-                else:
-                    grid_line.append(0)
-                    match self.counter_factory.map_not_counter(character):
-                        case "Agent":
-                            designated_player_positions.append(pos)
-                        case "Free":
-                            free_positions.append(np.array([current_x, current_y]))
-
-                current_x += 1
-
-            if len(line) >= max_width:
-                max_width = len(line)
-
-            grid.append(grid_line)
-            current_y += 1
-
-        grid = [line + ([0] * (max_width - len(line))) for line in grid]
-
-        self.kitchen_width: float = max_width + starting_at
-        self.kitchen_height = current_y
-
-        self.determine_counter_orientations(
-            counters, grid, np.array([self.kitchen_width / 2, self.kitchen_height / 2])
-        )
-
-        self.counter_factory.post_counter_setup(counters)
-
-        return counters, designated_player_positions, free_positions
-
-    def determine_counter_orientations(self, counters, grid, kitchen_center):
-        grid = np.array(grid).T
-
-        grid_width = grid.shape[0]
-        grid_height = grid.shape[1]
-
-        last_counter = None
-        fst_counter_in_row = None
-        for c in counters:
-            grid_idx = np.floor(c.pos).astype(int)
-            neighbour_offsets = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]], dtype=int)
-
-            neighbours_free = []
-            for offset in neighbour_offsets:
-                neighbour_pos = grid_idx + offset
-                if (
-                    neighbour_pos[0] > (grid_width - 1)
-                    or neighbour_pos[0] < 0
-                    or neighbour_pos[1] > (grid_height - 1)
-                    or neighbour_pos[1] < 0
-                ):
-                    pass
-                else:
-                    if grid[neighbour_pos[0]][neighbour_pos[1]] == 0:
-                        neighbours_free.append(offset)
-            if len(neighbours_free) > 0:
-                vector_to_center = c.pos - kitchen_center
-                vector_to_center /= np.linalg.norm(vector_to_center)
-                n_idx = np.argmin(
-                    np.linalg.norm(vector_to_center - n) for n in neighbours_free
-                )
-                nearest_vec = neighbours_free[n_idx]
-                # print(nearest_vec, type(nearest_vec))
-                c.set_orientation(nearest_vec)
-
-            elif grid_idx[0] == 0:
-                if grid_idx[1] == 0 or fst_counter_in_row is None:
-                    # counter top left
-                    c.set_orientation(np.array([1, 0]))
-                else:
-                    c.set_orientation(fst_counter_in_row.orientation)
-                fst_counter_in_row = c
-            else:
-                c.set_orientation(last_counter.orientation)
-
-            last_counter = c
-
-        # for c in counters:
-        #     near_counters = [
-        #         other
-        #         for other in counters
-        #         if np.isclose(np.linalg.norm(c.pos - other.pos), 1)
-        #     ]
-        #     # print(c.pos, len(near_counters))
-
     def perform_action(self, action: Action):
         """Performs an action of a player in the environment. Maps different types of action inputs to the
         correct execution of the players.
@@ -588,7 +448,7 @@ class Environment:
                 self.env_time + timedelta(seconds=action.duration),
             )
         else:
-            counter = self.get_facing_counter(player)
+            counter = get_closest(player.facing_point, self.counters)
             if player.can_reach(counter):
                 if action.action_type == ActionType.PUT:
                     player.put_action(counter)
@@ -606,169 +466,6 @@ class Environment:
 
         self.hook(POST_PERFORM_ACTION, action=action)
 
-    def get_facing_counter(self, player: Player):
-        """Determines the counter which the player is looking at.
-        Adds a multiple of the player facing direction onto the player position and finds the closest
-        counter for that point.
-
-        Args:
-            player: The player for which to find the facing counter.
-
-        Returns:
-
-        """
-        facing_counter = get_closest(player.facing_point, self.counters)
-        return facing_counter
-
-    def get_counter_collisions(self, player_positions):
-        counter_diff_vecs = (
-            player_positions[:, np.newaxis, :]
-            - self.counter_positions[np.newaxis, :, :]
-        )
-        counter_distances = np.max((np.abs(counter_diff_vecs)), axis=2)
-        closest_counter_positions = self.counter_positions[
-            np.argmin(counter_distances, axis=1)
-        ]
-        nearest_counter_to_player = player_positions - closest_counter_positions
-        relevant_axes = np.abs(nearest_counter_to_player).argmax(axis=1)
-
-        distances = np.linalg.norm(
-            np.max(
-                [
-                    np.abs(counter_diff_vecs) - 0.5,
-                    np.zeros(counter_diff_vecs.shape),
-                ],
-                axis=0,
-            ),
-            axis=2,
-        )
-
-        collided = np.any(distances < self.player_radius, axis=1)
-
-        return collided, relevant_axes, nearest_counter_to_player
-
-    def get_player_push(self, player_positions):
-        distances_players_after_scipy = distance_matrix(
-            player_positions, player_positions
-        )
-
-        player_diff_vecs = -(
-            player_positions[:, np.newaxis, :] - player_positions[np.newaxis, :, :]
-        )
-        collisions = distances_players_after_scipy < (2 * self.player_radius)
-        eye_idxs = np.eye(len(player_positions), len(player_positions), dtype=bool)
-        collisions[eye_idxs] = False
-        player_diff_vecs[collisions == False] = 0
-        push_vectors = np.sum(player_diff_vecs, axis=0)
-        collisions = np.any(collisions, axis=1)
-        return collisions, push_vectors
-
-    def perform_movement(self, duration: timedelta):
-        """Moves a player in the direction specified in the action.action. If the player collides with a
-        counter or other player through this movement, then they are not moved.
-        (The extended code with the two ifs is for sliding movement at the counters, which feels a bit smoother.
-        This happens, when the player moves diagonally against the counters or world boundary.
-        This just checks if the single axis party of the movement could move the player and does so at a lower rate.)
-
-        The movement action is a unit 2d vector.
-
-        Detects collisions with other players and pushes them out of the way.
-
-        Args:
-            duration: The duration for how long the movement to perform.
-        """
-        d_time = duration.total_seconds()
-
-        player_positions = np.array([p.pos for p in self.players.values()], dtype=float)
-        player_movement_vectors = np.array(
-            [
-                p.current_movement if self.env_time <= p.movement_until else [0, 0]
-                for p in self.players.values()
-            ],
-            dtype=float,
-        )
-
-        targeted_positions = player_positions + (
-            player_movement_vectors * (self.player_movement_speed * d_time)
-        )
-
-        # Collisions player between player
-        force_factor = 1.2
-        _, push_vectors = self.get_player_push(targeted_positions)
-        updated_movement = (force_factor * push_vectors) + player_movement_vectors
-        new_targeted_positions = player_positions + (
-            updated_movement * (self.player_movement_speed * d_time)
-        )
-        # same again to prevent squeezing into other players
-        _, push_vectors2 = self.get_player_push(new_targeted_positions)
-        updated_movement = (force_factor * push_vectors2) + updated_movement
-        new_targeted_positions = player_positions + (
-            updated_movement * (self.player_movement_speed * d_time)
-        )
-
-        # Check collisions with counters
-        (
-            collided,
-            relevant_axes,
-            nearest_counter_to_player,
-        ) = self.get_counter_collisions(new_targeted_positions)
-
-        # Check if sliding against counters is possible
-        for idx, player in enumerate(player_positions):
-            axis = relevant_axes[idx]
-            if collided[idx]:
-                # collide with counter left or top
-                if nearest_counter_to_player[idx][axis] > 0:
-                    updated_movement[idx, axis] = np.max(
-                        [updated_movement[idx, axis], 0]
-                    )
-                # collide with counter right or bottom
-                if nearest_counter_to_player[idx][axis] < 0:
-                    updated_movement[idx, axis] = np.min(
-                        [updated_movement[idx, axis], 0]
-                    )
-        new_positions = player_positions + (
-            updated_movement * (self.player_movement_speed * d_time)
-        )
-
-        # Check collisions with counters again, now absolute with no sliding possible
-        (
-            collided,
-            relevant_axes,
-            nearest_counter_to_player,
-        ) = self.get_counter_collisions(new_positions)
-        new_positions[collided] = player_positions[collided]
-
-        # Check player collisions a final time
-        # collided, _ = self.get_player_push(new_positions)
-        # if np.any(collided):
-        #     print(".", end="")
-
-        # Collisions player world borders
-        new_positions = np.clip(
-            new_positions,
-            self.world_borders_lower + self.player_radius,
-            self.world_borders_upper - self.player_radius,
-        )
-
-        for idx, p in enumerate(self.players.values()):
-            if not (new_positions[idx] == player_positions[idx]).all():
-                p.pos = new_positions[idx]
-
-            p.turn(player_movement_vectors[idx])
-
-            facing_distances = np.linalg.norm(
-                p.facing_point - self.counter_positions, axis=1
-            )
-            closest_counter = self.counters[facing_distances.argmin()]
-            p.current_nearest_counter = (
-                closest_counter
-                if facing_distances.min() <= self.player_interaction_range
-                else None
-            )
-            if p.last_interacted_counter != p.current_nearest_counter:
-                p.perform_interact_stop()
-
     def add_player(self, player_name: str, pos: npt.NDArray = None):
         """Add a player to the environment.
 
@@ -805,27 +502,9 @@ class Environment:
                 log.debug("No free positions left in kitchens")
             player.update_facing_point()
 
-        self.set_collision_arrays()
+        self.movement.set_collision_arrays(len(self.players))
         self.hook(PLAYER_ADDED, player_name=player_name, pos=pos)
 
-    def detect_collision_world_bounds(self, player: Player):
-        """Checks for detections of the player and the world bounds.
-
-        Args:
-            player: The player which to not let escape the world.
-
-        Returns: True if the player touches the world bounds, False if not.
-        """
-        collisions_lower = any(
-            (player.pos - (player.radius))
-            < [self.world_borders_x[0], self.world_borders_y[0]]
-        )
-        collisions_upper = any(
-            (player.pos + (player.radius))
-            > [self.world_borders_x[1], self.world_borders_y[1]]
-        )
-        return collisions_lower or collisions_upper
-
     def step(self, passed_time: timedelta):
         """Performs a step of the environment. Affects time based events such as cooking or cutting things, orders
         and time limits.
@@ -839,7 +518,9 @@ class Environment:
             for player in self.players.values():
                 player.progress(passed_time, self.env_time)
 
-            self.perform_movement(passed_time)
+            self.movement.perform_movement(
+                passed_time, self.env_time, self.players, self.counters
+            )
 
             for counter in self.progressing_counters:
                 counter.progress(passed_time=passed_time, now=self.env_time)
@@ -872,7 +553,6 @@ class Environment:
 
         Args:
             player_id: The player for which to get the state.
-            play_beep: Signal the GUI to play a beep when all connected players are ready to play the game.
 
         Returns: The state of the game formatted as a json-string
 
diff --git a/cooperative_cuisine/movement.py b/cooperative_cuisine/movement.py
new file mode 100644
index 0000000000000000000000000000000000000000..3a082ed5509694eff0ba2cd9512fcdc526455f5c
--- /dev/null
+++ b/cooperative_cuisine/movement.py
@@ -0,0 +1,181 @@
+from datetime import timedelta, datetime
+
+import numpy as np
+from scipy.spatial import distance_matrix
+
+from cooperative_cuisine.counters import Counter
+from cooperative_cuisine.player import Player
+
+
+class Movement:
+    world_borders_lower = None
+    world_borders_upper = None
+
+    def __init__(self, counter_positions, player_config, world_borders):
+        self.counter_positions = counter_positions
+        self.player_radius = player_config["radius"]
+        self.player_interaction_range = player_config["interaction_range"]
+        self.player_movement_speed = player_config["speed_units_per_seconds"]
+        self.world_borders = world_borders
+        self.set_collision_arrays(1)
+
+    def set_collision_arrays(self, number_players):
+        self.world_borders_lower = self.world_borders[np.newaxis, :, 0].repeat(
+            number_players, axis=0
+        )
+        self.world_borders_upper = self.world_borders[np.newaxis, :, 1].repeat(
+            number_players, axis=0
+        )
+
+    def get_counter_collisions(self, player_positions):
+        counter_diff_vecs = (
+            player_positions[:, np.newaxis, :]
+            - self.counter_positions[np.newaxis, :, :]
+        )
+        counter_distances = np.max((np.abs(counter_diff_vecs)), axis=2)
+        closest_counter_positions = self.counter_positions[
+            np.argmin(counter_distances, axis=1)
+        ]
+        nearest_counter_to_player = player_positions - closest_counter_positions
+        relevant_axes = np.abs(nearest_counter_to_player).argmax(axis=1)
+
+        distances = np.linalg.norm(
+            np.max(
+                [
+                    np.abs(counter_diff_vecs) - 0.5,
+                    np.zeros(counter_diff_vecs.shape),
+                ],
+                axis=0,
+            ),
+            axis=2,
+        )
+
+        collided = np.any(distances < self.player_radius, axis=1)
+
+        return collided, relevant_axes, nearest_counter_to_player
+
+    def get_player_push(self, player_positions):
+        distances_players_after_scipy = distance_matrix(
+            player_positions, player_positions
+        )
+
+        player_diff_vecs = -(
+            player_positions[:, np.newaxis, :] - player_positions[np.newaxis, :, :]
+        )
+        collisions = distances_players_after_scipy < (2 * self.player_radius)
+        eye_idxs = np.eye(len(player_positions), len(player_positions), dtype=bool)
+        collisions[eye_idxs] = False
+        player_diff_vecs[collisions == False] = 0
+        push_vectors = np.sum(player_diff_vecs, axis=0)
+        collisions = np.any(collisions, axis=1)
+        return collisions, push_vectors
+
+    def perform_movement(
+        self,
+        duration: timedelta,
+        env_time: datetime,
+        players: dict[str, Player],
+        counters: list[Counter],
+    ):
+        """Moves a player in the direction specified in the action.action. If the player collides with a
+        counter or other player through this movement, then they are not moved.
+        (The extended code with the two ifs is for sliding movement at the counters, which feels a bit smoother.
+        This happens, when the player moves diagonally against the counters or world boundary.
+        This just checks if the single axis party of the movement could move the player and does so at a lower rate.)
+
+        The movement action is a unit 2d vector.
+
+        Detects collisions with other players and pushes them out of the way.
+
+        Args:
+            duration: The duration for how long the movement to perform.
+            env_time: The current time of the environment.
+            players:  The players in the environment.
+            counters:  The counters in the environment.
+        """
+        d_time = duration.total_seconds()
+
+        player_positions = np.array([p.pos for p in players.values()], dtype=float)
+        player_movement_vectors = np.array(
+            [
+                p.current_movement if env_time <= p.movement_until else [0, 0]
+                for p in players.values()
+            ],
+            dtype=float,
+        )
+
+        targeted_positions = player_positions + (
+            player_movement_vectors * (self.player_movement_speed * d_time)
+        )
+
+        # Collisions player between player
+        force_factor = 1.2
+        _, push_vectors = self.get_player_push(targeted_positions)
+        updated_movement = (force_factor * push_vectors) + player_movement_vectors
+        new_targeted_positions = player_positions + (
+            updated_movement * (self.player_movement_speed * d_time)
+        )
+        # same again to prevent squeezing into other players
+        _, push_vectors2 = self.get_player_push(new_targeted_positions)
+        updated_movement = (force_factor * push_vectors2) + updated_movement
+        new_targeted_positions = player_positions + (
+            updated_movement * (self.player_movement_speed * d_time)
+        )
+
+        # Check collisions with counters
+        (
+            collided,
+            relevant_axes,
+            nearest_counter_to_player,
+        ) = self.get_counter_collisions(new_targeted_positions)
+
+        # Check if sliding against counters is possible
+        for idx, player in enumerate(player_positions):
+            axis = relevant_axes[idx]
+            if collided[idx]:
+                # collide with counter left or top
+                if nearest_counter_to_player[idx][axis] > 0:
+                    updated_movement[idx, axis] = np.max(
+                        [updated_movement[idx, axis], 0]
+                    )
+                # collide with counter right or bottom
+                if nearest_counter_to_player[idx][axis] < 0:
+                    updated_movement[idx, axis] = np.min(
+                        [updated_movement[idx, axis], 0]
+                    )
+        new_positions = player_positions + (
+            updated_movement * (self.player_movement_speed * d_time)
+        )
+
+        # Check collisions with counters again, now absolute with no sliding possible
+        (
+            collided,
+            relevant_axes,
+            nearest_counter_to_player,
+        ) = self.get_counter_collisions(new_positions)
+        new_positions[collided] = player_positions[collided]
+
+        # Collisions player world borders
+        new_positions = np.clip(
+            new_positions,
+            self.world_borders_lower + self.player_radius,
+            self.world_borders_upper - self.player_radius,
+        )
+
+        for idx, p in enumerate(players.values()):
+            if not (new_positions[idx] == player_positions[idx]).all():
+                p.pos = new_positions[idx]
+
+            p.turn(player_movement_vectors[idx])
+
+            facing_distances = np.linalg.norm(
+                p.facing_point - self.counter_positions, axis=1
+            )
+            closest_counter = counters[facing_distances.argmin()]
+            p.current_nearest_counter = (
+                closest_counter
+                if facing_distances.min() <= self.player_interaction_range
+                else None
+            )
+            if p.last_interacted_counter != p.current_nearest_counter:
+                p.perform_interact_stop()
diff --git a/cooperative_cuisine/player.py b/cooperative_cuisine/player.py
index b85c76661433e2f899fba4ce93bde25df1883d16..82ed1ede3ee513f765d4cc79ca38da8f129dcca6 100644
--- a/cooperative_cuisine/player.py
+++ b/cooperative_cuisine/player.py
@@ -29,7 +29,7 @@ class PlayerConfig:
 
     radius: float = 0.4
     """The size of the player. The size of a counter is 1"""
-    player_speed_units_per_seconds: float | int = 8
+    speed_units_per_seconds: float | int = 8
     """The move distance/speed of the player per action call."""
     interaction_range: float = 1.6
     """How far player can interact with counters."""
diff --git a/cooperative_cuisine/reinforcement_learning/environment_config_rl.yaml b/cooperative_cuisine/reinforcement_learning/environment_config_rl.yaml
index 5e75d9bdaea71f52507fa31d3fb1ed47b268bf84..6b02ef60e3e86bf8d87de5187c00076ab51c377e 100644
--- a/cooperative_cuisine/reinforcement_learning/environment_config_rl.yaml
+++ b/cooperative_cuisine/reinforcement_learning/environment_config_rl.yaml
@@ -82,7 +82,7 @@ orders:
 
 player_config:
   radius: 0.4
-  player_speed_units_per_seconds: 1
+  speed_units_per_seconds: 1
   interaction_range: 1.6
   restricted_view: False
   view_angle: 95
diff --git a/cooperative_cuisine/reinforcement_learning/gym_env.py b/cooperative_cuisine/reinforcement_learning/gym_env.py
index 7bacc68c42bafd29257976a351307ea4c268cc72..d5f79214580709d2c409cf16db8f739a321679c8 100644
--- a/cooperative_cuisine/reinforcement_learning/gym_env.py
+++ b/cooperative_cuisine/reinforcement_learning/gym_env.py
@@ -105,14 +105,18 @@ with open(ROOT_DIR / "pygame_2d_vis" / "visualization.yaml", "r") as file:
 
 def shuffle_counters(env):
     sample_counter = []
+    other_counters = []
     for counter in env.counters:
         if counter.__class__ != Counter:
             sample_counter.append(counter)
+        else:
+            other_counters.append()
     new_counter_pos = [c.pos for c in sample_counter]
     random.shuffle(new_counter_pos)
     for counter, new_pos in zip(sample_counter, new_counter_pos):
         counter.pos = new_pos
-    env.counter_positions = np.array([c.pos for c in env.counters])
+    sample_counter.extend(other_counters)
+    env.overwrite_counters(sample_counter)
 
 
 class EnvGymWrapper(Env):
diff --git a/tests/test_start.py b/tests/test_start.py
index e69b1ac95c1590adff21abcaa9291f7352ddb8d6..a17bb73715d0882d444f3c19fbfe89c5e8b31a09 100644
--- a/tests/test_start.py
+++ b/tests/test_start.py
@@ -91,7 +91,7 @@ def test_movement(env_config, layout_empty_config, item_info):
     player_name = "1"
     start_pos = np.array([3, 4])
     env.add_player(player_name, start_pos)
-    env.player_movement_speed = 1
+    env.movement.player_movement_speed = 1
     move_direction = np.array([1, 0])
     move_action = Action(player_name, ActionType.MOVEMENT, move_direction, duration=0.1)
     do_moves_number = 3
@@ -100,7 +100,7 @@ def test_movement(env_config, layout_empty_config, item_info):
         env.step(timedelta(seconds=0.1))
 
     expected = start_pos + do_moves_number * (
-        move_direction * env.player_movement_speed * move_action.duration
+        move_direction * env.movement.player_movement_speed * move_action.duration
     )
     assert np.isclose(
         np.linalg.norm(expected - env.players[player_name].pos), 0
@@ -112,7 +112,7 @@ def test_player_movement_speed(env_config, layout_empty_config, item_info):
     player_name = "1"
     start_pos = np.array([3, 4])
     env.add_player(player_name, start_pos)
-    env.player_movement_speed = 2
+    env.movement.player_movement_speed = 2
     move_direction = np.array([1, 0])
     move_action = Action(player_name, ActionType.MOVEMENT, move_direction, duration=0.1)
     do_moves_number = 3
@@ -121,7 +121,7 @@ def test_player_movement_speed(env_config, layout_empty_config, item_info):
         env.step(timedelta(seconds=0.1))
 
     expected = start_pos + do_moves_number * (
-        move_direction * env.player_movement_speed * move_action.duration
+        move_direction * env.movement.player_movement_speed * move_action.duration
     )
 
     assert np.isclose(
@@ -140,7 +140,7 @@ def test_player_reach(env_config, layout_empty_config, item_info):
     counter = Counter(pos=counter_pos, hook=Hooks(env))
     env.overwrite_counters([counter])
     env.add_player("1", np.array([2, 4]))
-    env.player_movement_speed = 1
+    env.movement.player_movement_speed = 1
     player = env.players["1"]
     assert not player.can_reach(counter), "Player is too far away."
 
@@ -162,7 +162,7 @@ def test_pickup(env_config, layout_config, item_info):
 
     env.add_player("1", np.array([2, 3]))
     player = env.players["1"]
-    env.player_movement_speed = 1
+    env.movement.player_movement_speed = 1
 
     move_down = Action("1", ActionType.MOVEMENT, np.array([0, -1]), duration=1)
     move_up = Action("1", ActionType.MOVEMENT, np.array([0, 1]), duration=1)
@@ -224,7 +224,7 @@ def test_processing(env_config, layout_config, item_info):
     tomato = Item(name="Tomato", item_info=None)
     env.add_player("1", np.array([2, 3]))
     player = env.players["1"]
-    env.player_movement_speed = 1
+    env.movement.player_movement_speed = 1
     player.holding = tomato
 
     move = Action("1", ActionType.MOVEMENT, np.array([0, -1]), duration=1)