diff --git a/overcooked_simulator/layouts/basic.layout b/overcooked_simulator/layouts/basic.layout
new file mode 100644
index 0000000000000000000000000000000000000000..65a375bb3b13216a9737eaf9be24f8926cbb5a33
--- /dev/null
+++ b/overcooked_simulator/layouts/basic.layout
@@ -0,0 +1,11 @@
+EEEEEEEEEEE
+ECCCCCCCCCE
+ECEEEEEEECE
+ECEEEEEEECE
+ECEEEEEEECE
+ECEEEEEEECE
+ECEEEEEEECE
+ECEEEEEEECE
+ECEEEEEEECE
+ECCCCCCCCCE
+EEEEEEEEEEE
diff --git a/overcooked_simulator/main.py b/overcooked_simulator/main.py
new file mode 100644
index 0000000000000000000000000000000000000000..47897d248160f83aef69f958c1da01b2623c1725
--- /dev/null
+++ b/overcooked_simulator/main.py
@@ -0,0 +1,21 @@
+from player import Player
+import sys
+from overcooked_simulator.simulation_runner import Simulator
+from overcooked_simulator.overcooked_environment import Environment
+
+def main():
+    simulator = Simulator(Environment, 300)
+
+    simulator.register_player(Player("p1", [100, 200]))
+    simulator.register_player(Player("p2", [200, 100]))
+
+    simulator.start()
+
+    print(simulator.get_state())
+
+    simulator.stop()
+    sys.exit()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py
new file mode 100644
index 0000000000000000000000000000000000000000..71060740332ed8a2c041cfac8a6dd841c1edeb64
--- /dev/null
+++ b/overcooked_simulator/overcooked_environment.py
@@ -0,0 +1,164 @@
+from __future__ import annotations
+from typing import TYPE_CHECKING
+
+if TYPE_CHECKING:
+    from overcooked_simulator.player import Player
+from pathlib import Path
+import numpy as np
+
+
+class Counter:
+    """Simple class for a counter at a specified position (center of counter). Can hold things on top.
+    """
+    def __init__(self, pos: np.array):
+        self.pos = pos
+        self.occupied_by = None
+
+    def __repr__(self):
+        return f"Counter(pos:{str(self.pos)},holds:{self.occupied_by})"
+
+
+class Action:
+    """Action class, specifies player, action type and action itself.
+    """
+    def __init__(self, player, act_type, action):
+        self.player = player
+        self.act_type = act_type
+        assert self.act_type in ["movement", "pickup", "interact"], "Unknown action type"
+        self.action = action
+
+
+class Environment:
+    """Environment class which handles the game logic for the overcooked-inspired environment.
+
+        Handles player movement, collision-detection, counters, cooking processes, recipes, incoming orders, time.
+        """
+    def __init__(self):
+        self.players = {}
+        self.counter_side_length = 40
+        self.layout_path = Path("overcooked_simulator/layouts/basic.layout")
+        self.counters = self.create_counters(self.layout_path)
+        self.score = 0
+
+    def create_counters(self, layout_file: Path):
+        """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_file: Path to the layout file.
+        """
+        current_y = self.counter_side_length / 2
+        counters = []
+
+        with open(layout_file, "r") as layout_file:
+            lines = layout_file.readlines()
+        for line in lines:
+            line = line.replace("\n", "").replace(" ", "")  # remove newline char
+            current_x = self.counter_side_length / 2
+            for character in line:
+                character = character.capitalize()
+                if character == "C":
+                    counter = Counter(np.array([current_x, current_y]))
+                    counters.append(counter)
+                    current_x += self.counter_side_length
+                elif character == "E":
+                    pass
+
+            current_y += self.counter_side_length
+        return 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.
+        Possible action types are movement, pickup and interact actions.
+
+        Args:
+            action: The action to be performed
+        """
+        assert action.player in self.players.keys(), "Unknown player."
+
+        player = self.players[action.player]
+        if action.act_type == "movement":
+            self.perform_movement(player, action.action)
+        elif action.act_type == "pickup":
+            self.perform_pickup(player)
+        elif action.act_type == "interact":
+            self.perform_interact(player)
+
+    def get_closest_counter(self, player: Player):
+        """Determines the closest counter in the environment of a player.
+
+        Args:
+            player: The player for which to find the closest counter
+
+        Returns: The closest counter for the given player.
+        """
+        pass
+
+    def perform_pickup(self, player: Player):
+        """Performs the game action corresponding to picking up an item
+
+        Args:
+            player: The player which performs the pickup action.
+
+        Returns: TODO?
+
+        """
+        pass
+
+    def perform_interact(self, player: Player):
+        """Performs the game action corresponding to interacting with a counter or other object.
+
+        Args:
+            player: The player which performs the interaction.
+
+        Returns: TODO?
+        """
+        pass
+
+    def perform_movement(self, player: Player, action):
+        """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.
+
+        Args:
+            player: The player to move.
+            action: The Action which now contains a unit-2d-vector of the movement direction
+        """
+        pass
+
+    def detect_collision(self, player: Player):
+        """Detect collisions between the player and other players or counters.
+
+        Args:
+            player: The player for which to check collisions.
+
+        Returns: True if the player is intersecting with any object in the environment.
+
+        """
+        pass
+
+    def step(self):
+        """Performs a step of the environment. Affects time based events such as cooking or cutting things, orders
+        and timelimits.
+        """
+        pass
+
+    def get_state(self):
+        """Get the current state of the game environment. The state here is accessible by the current python objects.
+
+        Returns: Dict of lists of the current relevant game objects.
+
+        """
+        return {"players": self.players,
+                "counters": self.counters,
+                "score": self.score}
+
+    def get_state_json(self):
+        """Get the current state of the game environment as a json-like nested dictionary.
+
+        Returns: Json-like string of the current game state.
+
+        """
+        pass
diff --git a/overcooked_simulator/player.py b/overcooked_simulator/player.py
new file mode 100644
index 0000000000000000000000000000000000000000..42f3d26fff900cbbd059de18c68657da34fbe558
--- /dev/null
+++ b/overcooked_simulator/player.py
@@ -0,0 +1,66 @@
+import numpy as np
+from overcooked_simulator.overcooked_environment import Counter
+
+
+class Player:
+    """Class representing a player in the game environment. A player consists of a name, their position and what
+    the player is currently holding in the hands.
+    This class handles interactions with counters and objects.
+
+    """
+    def __init__(self, name, pos):
+        self.name = name
+        self.pos = np.array(pos, dtype=float)
+        self.holding = None
+
+        self.move_dist = 5
+        self.facing_direction = np.array([0, 1])
+
+    def move(self, movement: np.array):
+        """Moves the player position by the given movement vector.
+        A unit direction vector multiplied by move_dist is added to the player position.
+
+        Args:
+            movement: 2D-Vector of length 1
+        """
+        pass
+
+    def move_abs(self, new_pos: np.array):
+        """Overwrites the player location by the new_pos 2d-vector. Absolute movement.
+        Mostly needed for resetting the player after a collision.
+
+        Args:
+            new_pos: 2D-Vector of the new player position.
+        """
+        pass
+
+    def turn(self, direction: np.array):
+        """Turns the player in the given direction. Overwrites the facing_direction by a given 2d-vector.
+        facing_direction is normalized to length 1.
+
+        Args:
+            direction: 2D-Vector of the direction for the player to face.
+        """
+        pass
+
+    def pick_action(self, counter: Counter):
+        """Performs the pickup-action with the counter. Handles the logic of what the player is currently holding,
+        what is currently on the counter and what can be picked up or combined in hand.
+
+        Args:
+            counter: The counter to pick things up from or put things down.
+        """
+        pass
+
+    def interact(self, counter: Counter):
+        """Performs the interact-action with the counter. Handles logic of starting processes
+        like for e.g. cutting onions. TODO holding the button vs pressing once?
+
+        Args:
+            counter: The counter to interact with.
+        """
+
+        pass
+
+    def __repr__(self):
+        return f"Player(name:{self.name},pos:{str(self.pos)},holds:{self.holding})"
diff --git a/overcooked_simulator/simulation_runner.py b/overcooked_simulator/simulation_runner.py
new file mode 100644
index 0000000000000000000000000000000000000000..846a56bcbb2e243c1df54ae287104c8eedacb90a
--- /dev/null
+++ b/overcooked_simulator/simulation_runner.py
@@ -0,0 +1,103 @@
+from threading import Thread
+import time
+from overcooked_simulator.overcooked_environment import Environment, Action
+from overcooked_simulator.player import Player
+
+
+class Simulator(Thread):
+    """Simulator main class which runs manages the environment and player inputs and gamestate outputs.
+
+    Main Simulator class which runs the game environment. Players can be registered in the game.
+    The simulator is run as its own thread.
+
+    Typical usage example:
+
+      sim = Simulator()
+      sim.register_player(Player("p1", [x,y]))
+      sim.start()
+    """
+
+    def __init__(self, env_class: type, frequency: int):
+        self.finished: bool = False
+
+        self.step_frequency: int = frequency
+        self.prefered_sleeptime_ns: float = 1e9 / self.step_frequency
+
+        self.env = env_class()
+
+        super().__init__()
+
+    def step(self):
+        """One simulation step of the environment.
+            """
+        self.env.step()
+
+    def enter_action(self, action: Action):
+        """Takes an action and executes it in the environment.
+
+            Args:
+                action (Action): The action object to be executed.
+            """
+        self.env.perform_action(action)
+
+    def get_state(self):
+        """Get the current gamestate as python objects.
+
+            Returns:
+                The current state of the game. Currently as dict with lists of environment objects.
+            """
+
+        return self.env.get_state()
+
+    def get_state_json(self):
+        """Get the current gamestate in json-like dict.
+
+            Returns:
+                The gamestate encoded in a json style nested dict.
+            """
+
+        return self.env.get_state_json()
+
+    def register_player(self, player: Player):
+        print(f"Added player {player.name} to the game.")
+        """Adds a player to the environment.
+
+            Args:
+                player: The player to be added.
+            """
+
+        self.env.players[player.name] = player
+
+    def register_players(self, players: list[Player]):
+        """Registers multiple players from a list
+
+            Args:
+                players: List of players to be added.
+            """
+
+        for p in players:
+            self.register_player(p)
+
+    def run(self):
+        """Starts the simulator thread. Runs in a loop until stopped.
+            """
+        overslept_in_ns = 0
+
+        while not self.finished:
+
+            step_start = time.time_ns()
+            self.step()
+            step_duration = time.time_ns() - step_start
+
+            time_to_sleep_ns = self.prefered_sleeptime_ns - (step_duration + overslept_in_ns)
+
+            sleep_start = time.time_ns()
+            time.sleep(max(time_to_sleep_ns / 1e9, 0))
+            sleep_function_duration = time.time_ns() - sleep_start
+            overslept_in_ns = sleep_function_duration - time_to_sleep_ns
+
+    def stop(self):
+        """Stops the simulator
+        """
+        print("Stopping the simulation.")
+        self.finished = True
diff --git a/setup.py b/setup.py
index 06e9d0e8050d626df2c660bf9f1a1f6f955426e3..a267d074c93d777f787a8fd47cf4bc7c28e1f4df 100644
--- a/setup.py
+++ b/setup.py
@@ -10,7 +10,7 @@ with open('README.md') as readme_file:
 with open('CHANGELOG.md') as history_file:
     history = history_file.read()
 
-requirements = []
+requirements = ["numpy", "pygame", "scipy"]
 
 test_requirements = ['pytest>=3', ]
 
diff --git a/tests/test_start.py b/tests/test_start.py
index fbd6dd5c68c7b9d29a4cb13b0849128c2dd25ce2..0cc343f7846358e7cffd35f6b4da72a5ba60e15f 100644
--- a/tests/test_start.py
+++ b/tests/test_start.py
@@ -1,4 +1,49 @@
+from overcooked_simulator.simulation_runner import Simulator
+from overcooked_simulator.player import Player
+from overcooked_simulator.overcooked_environment import Environment, Action
+import numpy as np
+import time
 
-def test_dummy():
-    assert "overcooked".startswith("over"), "overcooked does not start with 'over'"
+def test_player_registration():
 
+    sim = Simulator(Environment, 200)
+    p1 = Player("player1", np.array([0, 0]))
+    sim.register_player(p1)
+
+    assert len(sim.env.players) != 0, "Wrong number of players"
+    assert len(sim.env.players) == 1, "Wrong number of players"
+
+    p2 = Player("player2", np.array([100, 100]))
+    sim.register_player(p2)
+
+    assert len(sim.env.players) == 2, "Wrong number of players"
+
+    p3 = Player("player2", np.array([100, 100]))
+    sim.register_player(p2) # same player name
+    assert len(sim.env.players) == 2, "Wrong number of players"
+
+    sim.start()
+    sim.stop()
+
+def test_simulator_frequency():
+    class TestEnv:
+        def __init__(self):
+            self.c = 0
+
+        def step(self):
+            self.c += 1
+
+    frequency = 1000
+    running_time_seconds = 8
+
+    sim = Simulator(TestEnv, frequency)
+
+    sim.start()
+    time.sleep(running_time_seconds)
+    sim.stop()
+
+    print(sim.env.c)
+    accepted_tolerance = 0.02
+    lower = frequency * running_time_seconds * (1-accepted_tolerance)
+    upper = frequency * running_time_seconds * (1+accepted_tolerance)
+    assert sim.env.c > lower and sim.env.c < upper, "Timing error in the environment at 1000hz"