diff --git a/overcooked_simulator/counter_factory.py b/overcooked_simulator/counter_factory.py
index 9b1487c3706a70dde8b224283a1877c126c41b56..639459a56508c627eb5015aa5b3080bfcd2a1737 100644
--- a/overcooked_simulator/counter_factory.py
+++ b/overcooked_simulator/counter_factory.py
@@ -81,10 +81,10 @@ def convert_words_to_chars(layout_chars_config: dict[str, str]) -> dict[str, str
     """
     word_refs = {
         "hash": "#",
-        "space": " ",
+        # "space": " ",
         "dot": ".",
         "comma": ",",
-        "semicolon": ";",
+        # "semicolon": ";",
         "colon": ":",
         "minus": "-",
         "exclamation": "!",
@@ -98,6 +98,7 @@ def convert_words_to_chars(layout_chars_config: dict[str, str]) -> dict[str, str
         "left": "<",
         "pipe": "|",
         "at": "@",
+        "wave": "~",  # ~ is None / null in yaml
         "ocurlybracket": "{",
         "ccurlybracket": "}",
         "osquarebracket": "[",
@@ -163,8 +164,11 @@ class CounterFactory:
         """A set of characters that represent counters for agents or free spaces."""
 
         self.counter_classes: dict[str, Type] = dict(
-            inspect.getmembers(
-                sys.modules["overcooked_simulator.counters"], inspect.isclass
+            filter(
+                lambda k: issubclass(k[1], Counter),
+                inspect.getmembers(
+                    sys.modules["overcooked_simulator.counters"], inspect.isclass
+                ),
             )
         )
         """A dictionary of counter classes imported from the 'overcooked_simulator.counters' module."""
@@ -191,6 +195,8 @@ class CounterFactory:
 
         assert self.can_map(c), f"Can't map counter char {c}"
         counter_class = None
+        if c == "@":
+            print("-")
         if self.layout_chars_config[c] in self.item_info:
             item_info = self.item_info[self.layout_chars_config[c]]
             if item_info.type == ItemType.Equipment and item_info.equipment:
@@ -221,7 +227,20 @@ class CounterFactory:
                 )
 
         if counter_class is None:
-            counter_class = self.counter_classes[self.layout_chars_config[c]]
+            if self.layout_chars_config[c] in self.counter_classes:
+                counter_class = self.counter_classes[self.layout_chars_config[c]]
+            elif self.layout_chars_config[c] == "Plate":
+                return Counter(
+                    pos=pos,
+                    hook=self.hook,
+                    occupied_by=Plate(
+                        transitions=self.filter_item_info(
+                            by_item_type=ItemType.Meal, add_effects=True
+                        ),
+                        clean=True,
+                        item_info=self.item_info[Plate.__name__],
+                    ),
+                )
         kwargs = {
             "pos": pos,
             "hook": self.hook,
diff --git a/overcooked_simulator/counters.py b/overcooked_simulator/counters.py
index 3d930aa54e15a4c6ad9f187b3766eee34f96cc07..40779e5cb228c993a65b5264f684225ea1ff78bc 100644
--- a/overcooked_simulator/counters.py
+++ b/overcooked_simulator/counters.py
@@ -623,7 +623,7 @@ class Trashcan(Counter):
         return None
 
     def can_drop_off(self, item: Item) -> bool:
-        return True
+        return item.name != "Extinguisher"
 
 
 class CookingCounter(Counter):
diff --git a/overcooked_simulator/example_study_server.py b/overcooked_simulator/example_study_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..778135f47054e0cdc4a948f7f3a1098b95542426
--- /dev/null
+++ b/overcooked_simulator/example_study_server.py
@@ -0,0 +1,136 @@
+"""
+# Usage
+- Set `CONNECT_WITH_STUDY_SERVER` in overcooked_gui.py to True.
+- Run this script. Copy the manager id that is printed
+- Run the game_server.py script with the manager id copied from the terminal
+```
+python game_server.py --manager_ids COPIED_UUID
+```
+- Run 2 overcooked_gui.py scripts in different terminals. For more players change `NUMBER_PLAYER_PER_ENV` and start more guis.
+
+The environment starts when all players connected.
+"""
+
+import argparse
+import asyncio
+import logging
+from typing import Tuple
+
+import requests
+import uvicorn
+from fastapi import FastAPI
+
+from overcooked_simulator import ROOT_DIR
+from overcooked_simulator.game_server import CreateEnvironmentConfig
+from overcooked_simulator.server_results import PlayerInfo
+from overcooked_simulator.utils import (
+    url_and_port_arguments,
+    add_list_of_manager_ids_arguments,
+)
+
+NUMBER_PLAYER_PER_ENV = 2
+
+log = logging.getLogger(__name__)
+
+
+app = FastAPI()
+
+game_server_url = "localhost:8000"
+server_manager_id = None
+
+
+# @app.get("/")
+# async def root(response_class=HTMLResponse):
+#     return """
+#     <html>
+#         <head>
+#             <title>Overcooked Game</title>
+#         </head>
+#         <body>
+#             <h1>Start Game!</h1>
+#             <button type="button">Click Me!</button>
+#         </body>
+#     </html>
+#     """
+
+running_envs: dict[str, Tuple[int, dict[str, PlayerInfo], list[str]]] = {}
+current_free_envs = []
+
+
+@app.post("/connect_to_game/{request_id}")
+async def want_to_play(request_id: str):
+    global current_free_envs
+    # TODO based on study desing / internal state of request id current state (which level to play)
+    if current_free_envs:
+        current_free_env = current_free_envs.pop()
+
+        running_envs[current_free_env][2].append(request_id)
+        new_running_env = (
+            running_envs[current_free_env][0] + 1,
+            running_envs[current_free_env][1],
+            running_envs[current_free_env][2],
+        )
+        player_info = running_envs[current_free_env][1][str(new_running_env[0])]
+        running_envs[current_free_env] = new_running_env
+        if new_running_env[0] < NUMBER_PLAYER_PER_ENV - 1:
+            current_free_env.append(current_free_env)
+        return player_info
+    else:
+        environment_config_path = ROOT_DIR / "game_content" / "environment_config.yaml"
+        layout_path = ROOT_DIR / "game_content" / "layouts" / "basic.layout"
+        item_info_path = ROOT_DIR / "game_content" / "item_info.yaml"
+        with open(item_info_path, "r") as file:
+            item_info = file.read()
+        with open(layout_path, "r") as file:
+            layout = file.read()
+        with open(environment_config_path, "r") as file:
+            environment_config = file.read()
+        creation_json = CreateEnvironmentConfig(
+            manager_id=server_manager_id,
+            number_players=NUMBER_PLAYER_PER_ENV,
+            environment_settings={"all_player_can_pause_game": False},
+            item_info_config=item_info,
+            environment_config=environment_config,
+            layout_config=layout,
+            seed=1234567890,
+        ).model_dump(mode="json")
+        # todo async
+        env_info = requests.post(
+            game_server_url + "/manage/create_env/", json=creation_json
+        )
+
+        if env_info.status_code == 403:
+            raise ValueError(f"Forbidden Request: {env_info.json()['detail']}")
+        env_info = env_info.json()
+        print(env_info)
+        running_envs[env_info["env_id"]] = (0, env_info["player_info"], [request_id])
+        current_free_envs.append(env_info["env_id"])
+        return env_info["player_info"]["0"]
+
+
+def main(host, port, game_server_url_, manager_id):
+    global game_server_url, server_manager_id
+    game_server_url = "http://" + game_server_url_
+    server_manager_id = manager_id[0]
+    print(f"Use {server_manager_id=} for {game_server_url=}")
+    loop = asyncio.new_event_loop()
+    config = uvicorn.Config(app, host=host, port=port, loop=loop)
+    server = uvicorn.Server(config)
+    loop.run_until_complete(server.serve())
+
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        prog="Overcooked Simulator Study Server",
+        description="Study Server: Match Making, client pre and post managing.",
+        epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html",
+    )
+    url_and_port_arguments(parser=parser, server_name="Study Server", default_port=8080)
+    add_list_of_manager_ids_arguments(parser=parser)
+    args = parser.parse_args()
+    main(
+        args.url,
+        args.port,
+        game_server_url_="localhost:8000",
+        manager_id=args.manager_ids,
+    )
diff --git a/overcooked_simulator/game_content/environment_config.yaml b/overcooked_simulator/game_content/environment_config.yaml
index fc792099aa46b853e980e659e1fb2528328ad16c..4264c45679fdb9a66f6961c0ecd4bd8d8e0e9664 100644
--- a/overcooked_simulator/game_content/environment_config.yaml
+++ b/overcooked_simulator/game_content/environment_config.yaml
@@ -18,15 +18,16 @@ meals:
 
 layout_chars:
   _: Free
-  hash: Counter
+  hash: Counter  # #
   A: Agent
   pipe: Extinguisher
   P: PlateDispenser
   C: CuttingBoard
   X: Trashcan
-  W: ServingWindow
+  $: ServingWindow
   S: Sink
   +: SinkAddon
+  at: Plate  # @ just a clean plate on a counter
   U: Pot  # with Stove
   Q: Pan  # with Stove
   O: Peel  # with Oven
@@ -41,6 +42,15 @@ layout_chars:
   G: Sausage  # sausaGe
   B: Bun
   M: Meat
+  question: Counter  # ? mushroom
+  ↓: Counter
+  ^: Counter
+  right: Counter
+  left: Counter
+  wave: Free  # ~ Water
+  minus: Free  # - Ice
+  dquote: Counter  # " wall/truck
+  p: Counter # second plate return ??
 
 
 orders:
@@ -88,7 +98,8 @@ player_config:
   player_speed_units_per_seconds: 6
   interaction_range: 1.6
   restricted_view: False
-  view_angle: 95
+  view_angle: 70
+  view_range: 5.5  # in grid units, can be "null"
 
 effect_manager:
   FireManager:
diff --git a/overcooked_simulator/game_content/layouts/basic.layout b/overcooked_simulator/game_content/layouts/basic.layout
index 94e5fb1dc055cefacfa6ef562ad1fe04424e726b..5238c21f86c487ed90ff8efc6a373c6213d1f6c9 100644
--- a/overcooked_simulator/game_content/layouts/basic.layout
+++ b/overcooked_simulator/game_content/layouts/basic.layout
@@ -1,7 +1,7 @@
 #QU#FO#TNLB#
 #__________M
 |__________K
-W__________I
+$__________I
 #__A_____A_D
 C__________E
 C__________G
diff --git a/overcooked_simulator/game_content/layouts/large.layout b/overcooked_simulator/game_content/layouts/large.layout
index 6933567897246f90838b6a7efd8f15e48ca5b9bf..460244ca3fd685c71cdab3cc2bc6d5d4ae1c9092 100644
--- a/overcooked_simulator/game_content/layouts/large.layout
+++ b/overcooked_simulator/game_content/layouts/large.layout
@@ -4,7 +4,7 @@
 #____________________________________#
 #____________________________________#
 #____________________________________K
-W____________________________________I
+$____________________________________I
 #____________________________________#
 #____________________________________#
 #__A_____A___________________________D
diff --git a/overcooked_simulator/game_content/layouts/large_t.layout b/overcooked_simulator/game_content/layouts/large_t.layout
new file mode 100644
index 0000000000000000000000000000000000000000..304e6f7746f4e0c7510f69395b55c7f691de84f6
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/large_t.layout
@@ -0,0 +1,45 @@
+#QU#F###O#T#################N###L###B#
+#____________________________________#
+#____________________________________M
+#____________________________________#
+#____________________________________#
+#____________________________________K
+W____________________________________I
+#____________________________________#
+#____________________________________#
+#__A_____A___________________________D
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+C____________________________________E
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+#____________________________________#
+C____________________________________G
+#____________________________________#
+#P#####S+####X#####S+#################
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-1-far-apart.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-1-far-apart.layout
new file mode 100644
index 0000000000000000000000000000000000000000..0fa1d52d170632fb5996b5baf907f502d0b1cc06
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-1-far-apart.layout
@@ -0,0 +1,14 @@
+###N####U####
+#___________|
+#___A___A___#
+#___________S
+##########__+
+P___________#
+$___________#
+$___________X
+###C#C###@@##
+
+; seconds=150
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/1-1_(Overcooked!)
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-2-pedestrians.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-2-pedestrians.layout
new file mode 100644
index 0000000000000000000000000000000000000000..e9ac2fb08cd9d6437eac0d30da17d6a6114f037e
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-2-pedestrians.layout
@@ -0,0 +1,12 @@
+_##U#U#__###|X_#
+______#____A___$
++_____@__@_____$
+S________#_____P
+____A____#______
+_##C#C#__#T#N##_
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/1-2_(Overcooked!)
+; pedestrians: down the middle road
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-3-moving-counters.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-3-moving-counters.layout
new file mode 100644
index 0000000000000000000000000000000000000000..3dfb4177c4332c3263818e86c6390d562f5bc1bb
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-3-moving-counters.layout
@@ -0,0 +1,15 @@
+_____________
+___U#U##$$P|_
+_#____#______
+_@__A_#___A__
+_@____#______
+_@____#______
+_X____#______
+_#C#C##NT?___
+_____________
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=false
+; link: https://overcooked.fandom.com/wiki/1-3_(Overcooked!)
+; moving counters
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-4-bottleneck.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-4-bottleneck.layout
new file mode 100644
index 0000000000000000000000000000000000000000..1eac27c1c4852d05ecec30d6b9ddf1942fceecde
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-4-bottleneck.layout
@@ -0,0 +1,14 @@
+##S+####QQQQ#
+T____###____|
+M_A__###__A_#
+B___________#
+L____###____$
+#____###____$
+#____###____P
+X____###____@
+##C#C###@@@@#
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/1-4_(Overcooked!)
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-5-circle.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-5-circle.layout
new file mode 100644
index 0000000000000000000000000000000000000000..a278d9ab1c5d4214d8194d0b2ef82822dff2ae32
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-5-circle.layout
@@ -0,0 +1,14 @@
+#####P$$|#####
+#?NT#A_A_#S+##
+#____________X
+#_##########_#
+#_##########_#
+#_##########_#
+#_#######@@@_#
+#____________#
+#C#C####U#U#U#
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/1-5_(Overcooked!)
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-6-raising-platforms.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-6-raising-platforms.layout
new file mode 100644
index 0000000000000000000000000000000000000000..5af323f1c86c874cb126fb25170fe46e67cf01e3
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-6-raising-platforms.layout
@@ -0,0 +1,14 @@
+##S+###@Q@Q@#
+M___________#
+T___________|
+L___________$
+#___________$
+#___________P
+X___________#
+##C#C##Q#Q#B#
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/1-6_(Overcooked!)
+; raising platforms based on earthquakes
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/2-1-moving-trucks.layout b/overcooked_simulator/game_content/layouts/overcooked-1/2-1-moving-trucks.layout
new file mode 100644
index 0000000000000000000000000000000000000000..b7a4745ab23db07ddaa344b83e36ffa2840a2a07
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/2-1-moving-trucks.layout
@@ -0,0 +1,17 @@
+_______________
+__#QQQ#@@@#____
+__#_______$____
+__B_______$____
+__#_______P____
+_______________
+__M__A____X____
+__L_______#____
+__T__A____C____
+__#|###C###____
+_______________
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=false
+; link: https://overcooked.fandom.com/wiki/2-1_(Overcooked!)
+; moving trucks: counters and ground are moving
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/2-2-rats.layout b/overcooked_simulator/game_content/layouts/overcooked-1/2-2-rats.layout
new file mode 100644
index 0000000000000000000000000000000000000000..4543c03b09012c4d8f86a88d3eb688f7230ba0cf
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/2-2-rats.layout
@@ -0,0 +1,17 @@
+#####P$$|#####
+#####____#####
+##S+#____#S+##
+X____________X
+#____________#
+U___@__A_@___#
+#___@____@___#
+#___#_A__#___#
+U___#____#___#
+#___#____#___#
+#?N##C##C##NT#
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/2-2_(Overcooked!)
+; rats: steal ingredients + meals
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/2-3-separated-conveyors.layout b/overcooked_simulator/game_content/layouts/overcooked-1/2-3-separated-conveyors.layout
new file mode 100644
index 0000000000000000000000000000000000000000..6980844cd618acaf578975d98b261d0b0c6d0147
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/2-3-separated-conveyors.layout
@@ -0,0 +1,15 @@
+>>>>>>>>>>>>>>>↓
+^#_____##@____#↓
+^+A____|#@_A__#↓
+^S_____Q#C____$↓
+^M_____###____$↓
+^L_____Q#C____P↓
+^B_____###____#↓
+^T_____X#@____X↓
+^<<<<<<<<<<<<<<<
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/2-3_(Overcooked!)
+; conveyors
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/2-4-separated-2.layout b/overcooked_simulator/game_content/layouts/overcooked-1/2-4-separated-2.layout
new file mode 100644
index 0000000000000000000000000000000000000000..d0416de526c511526c42cf430b7ae40ead2ff46b
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/2-4-separated-2.layout
@@ -0,0 +1,16 @@
+#@@@##C#C######
+#_____________$
+#___A_________$
+#_____________P
+####____###___#
+X<<<<<<X>>>>>>X
+#___###____####
+Q_____________#
+#________A____#
+Q_____________#
+##Q#+S#|##BTLM#
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/2-4_(Overcooked!)
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/3-1-ice.layout b/overcooked_simulator/game_content/layouts/overcooked-1/3-1-ice.layout
new file mode 100644
index 0000000000000000000000000000000000000000..4d47b65b2cd83bf38a0bdf9c08c6d235c4074cc9
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/3-1-ice.layout
@@ -0,0 +1,19 @@
+~~~~~~~~~~~~~~~~
+~~~~~~P$$~~~~~~~
+~~~---------~~~~
+~~~-----------~~
+~~--#C#C|##----~
+~---S#####IA---~
+~---+#####K---~~
+~---#FFF##F---~~
+~~-A----@@@--~~~
+~~-----------~~~
+~~~~--------~~~~
+~~~~~~~----~~~~~
+~~~~~~~~~~~~~~~~
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/3-1_(Overcooked!)
+; ice: accelerating
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/3-2-separated-moving-counters.layout b/overcooked_simulator/game_content/layouts/overcooked-1/3-2-separated-moving-counters.layout
new file mode 100644
index 0000000000000000000000000000000000000000..dc8aefbc3a7c3e149bbff07a600bd452453edee9
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/3-2-separated-moving-counters.layout
@@ -0,0 +1,16 @@
+##$$####$$###
+####P##______
+______?______
+______N______
+U_____T_____U
+X#####X______
+U_____#_____U
+______#______
+__A___#__A___
+@@@C#C#______
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=false
+; link: https://overcooked.fandom.com/wiki/3-2_(Overcooked!)
+; moving counters
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/3-3-moving-trucks-2.layout b/overcooked_simulator/game_content/layouts/overcooked-1/3-3-moving-trucks-2.layout
new file mode 100644
index 0000000000000000000000000000000000000000..f5385f60d38a706c609fc6f199a323f1bfeb36d1
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/3-3-moving-trucks-2.layout
@@ -0,0 +1,15 @@
+__________""#NIK?TX##
+__________""#__A____$
+__________""#_______$
+__________""#__A____P
+__________""_________
+C_________""________C
+#_________""________#
+C_________""________C
+#|U#U#U@@####F@F@F|##
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=false
+; link: https://overcooked.fandom.com/wiki/3-3_(Overcooked!)
+; moving trucks: counters and ground are moving
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/3-4-ice-moving-platforms.layout b/overcooked_simulator/game_content/layouts/overcooked-1/3-4-ice-moving-platforms.layout
new file mode 100644
index 0000000000000000000000000000000000000000..389caab9fc91d253aeba25b3c9129e416139b326
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/3-4-ice-moving-platforms.layout
@@ -0,0 +1,14 @@
+##F#F#~~~~@@F##
+X-----~~~-----#
+#-A---~~~-----#
+I-----~~~-----$
+#-----~~~-----$
+K-----~~~-----P
+|--A-----------
+#+S##~---#C#C##
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/3-4_(Overcooked!)
+; ice, moving platforms, water
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/4-1-moving-counters.layout b/overcooked_simulator/game_content/layouts/overcooked-1/4-1-moving-counters.layout
new file mode 100644
index 0000000000000000000000000000000000000000..4b6fcaec5c611c09428f82fd8d116c69bee5d47c
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/4-1-moving-counters.layout
@@ -0,0 +1,17 @@
+"""""#|#O#O#O#X#"""
+_____#_________#"""
+__#____________#"""
+__####____##@@@####
+__+_______#_______$
+__S___A___#___A___$
+__#_______#_______P
+__#####C#C#____####
+__#____________#"""
+____"#_________#"""
+"""""###DTE?G###"""
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/4-1_(Overcooked!)
+; moving counters
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/4-2-dark.layout b/overcooked_simulator/game_content/layouts/overcooked-1/4-2-dark.layout
new file mode 100644
index 0000000000000000000000000000000000000000..a591aa97c088a0edaa01287038770ff4df30d579
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/4-2-dark.layout
@@ -0,0 +1,19 @@
+#########|U#@@@#
+##S+##C##______$
+#______________$
+C______________P
+#______________#
+#______###X#####
+U______#######T#
+#__A___________#
+#___________A__#
+N______________#
+#########______?
+################
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/4-2_(Overcooked!)
+; link: https://www.trueachievements.com/game/Overcooked/walkthrough/6
+; dark: only flashlight fov
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/4-3-moving-counters.layout b/overcooked_simulator/game_content/layouts/overcooked-1/4-3-moving-counters.layout
new file mode 100644
index 0000000000000000000000000000000000000000..eb704056ac8135e4e8cc11ede3dbd135f4794340
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/4-3-moving-counters.layout
@@ -0,0 +1,18 @@
+###S+#####P$$##X#
+#_______|_______#
+C_______________Q
+#_______#_______#
+#_______#_______#
+###_######@@@_###
+B_______#_______#
+T__A____#___A___#
+M_______________Q
+L_______#_______#
+#_______#_______#
+#################
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/4-3_(Overcooked!)
+; moving counters
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/4-4-moving-counters-separated.layout b/overcooked_simulator/game_content/layouts/overcooked-1/4-4-moving-counters-separated.layout
new file mode 100644
index 0000000000000000000000000000000000000000..88bc660ac2bc1ddcbd2600b715a38613dc4d16b7
--- /dev/null
+++ b/overcooked_simulator/game_content/layouts/overcooked-1/4-4-moving-counters-separated.layout
@@ -0,0 +1,16 @@
+#________↓C#O##O###
++________↓________S
+S________↓________+
+#________↓________#
+#________↓#MBLT#@@#
+#|#Q##Q#C↓________p
+P________↓________$
+$_____A__↓________$
+$________↓________#
+#@@#G?DE#X_________
+
+; seconds=240
+; plates={c:0, d:0}
+; dirty_plates=true
+; link: https://overcooked.fandom.com/wiki/4-4_(Overcooked!)
+; moving counters
\ No newline at end of file
diff --git a/overcooked_simulator/game_content/layouts/split.layout b/overcooked_simulator/game_content/layouts/split.layout
index 39bace3e0a94b0594d8ef9f294daeb40aae4492f..3f29e313f63f566ad9deb846f465412949d82e0c 100644
--- a/overcooked_simulator/game_content/layouts/split.layout
+++ b/overcooked_simulator/game_content/layouts/split.layout
@@ -1,7 +1,7 @@
 #QU#T###NLB#
 #__________M
 #____A_____#
-W__________#
+$__________#
 ############
 C__________#
 C_____A____#
diff --git a/overcooked_simulator/game_server.py b/overcooked_simulator/game_server.py
index d54341a53d2a8100a0a2b9aa12c4bf3879e27f8b..84879915dce1ff120d6f7a99e405466aa9f7adce 100644
--- a/overcooked_simulator/game_server.py
+++ b/overcooked_simulator/game_server.py
@@ -593,17 +593,6 @@ def manage_websocket_message(message: str, client_id: str) -> PlayerRequestResul
             "player_hash" in message_dict
         ), "'player_hash' key not in message dictionary'"
         match request_type:
-            case PlayerRequestType.READY:
-                accepted = environment_handler.set_player_ready(
-                    message_dict["player_hash"]
-                )
-                return {
-                    "request_type": request_type.value,
-                    "msg": f"ready{' ' if accepted else ' not '}accepted",
-                    "status": 200 if accepted else 400,
-                    "player_hash": message_dict["player_hash"],
-                }
-
             case PlayerRequestType.GET_STATE:
                 state = environment_handler.get_state(message_dict["player_hash"])
                 if isinstance(state, int):
@@ -616,7 +605,16 @@ def manage_websocket_message(message: str, client_id: str) -> PlayerRequestResul
                         "player_hash": None,
                     }
                 return state
-
+            case PlayerRequestType.READY:
+                accepted = environment_handler.set_player_ready(
+                    message_dict["player_hash"]
+                )
+                return {
+                    "request_type": request_type.value,
+                    "msg": f"ready{' ' if accepted else ' not '}accepted",
+                    "status": 200 if accepted else 400,
+                    "player_hash": message_dict["player_hash"],
+                }
             case PlayerRequestType.ACTION:
                 assert (
                     "action" in message_dict
diff --git a/overcooked_simulator/gui_2d_vis/drawing.py b/overcooked_simulator/gui_2d_vis/drawing.py
index d9e5e8e22b0c3ad16df665e496c759178cd26262..fdf3d59050ec788d95e7520b34194dafcce4b3d0 100644
--- a/overcooked_simulator/gui_2d_vis/drawing.py
+++ b/overcooked_simulator/gui_2d_vis/drawing.py
@@ -45,11 +45,12 @@ def grayscale(img):
     return surface
 
 
-def create_polygon(n, length):
+def create_polygon(n, start_vec):
     if n == 1:
         return np.array([0, 0])
 
-    vector = np.array([length, 0])
+    vector = start_vec.copy()
+
     angle = (2 * np.pi) / n
 
     rot_matrix = np.array(
@@ -153,31 +154,66 @@ class Visualizer:
             direction = pygame.math.Vector2(state["view_restriction"]["direction"])
             pos = pygame.math.Vector2(state["view_restriction"]["position"])
             angle = state["view_restriction"]["angle"] / 2
+            range = state["view_restriction"]["range"]
+
+            angle = min(angle, 180)
 
             pos = pos * grid_size + pygame.math.Vector2([grid_size / 2, grid_size / 2])
 
-            rect_scale = max(width, height)
+            rect_scale = max(width, height) * 2
+            # rect_scale = 2 * grid_size
 
             left_beam = pos + (direction.rotate(angle) * rect_scale * 2)
             right_beam = pos + (direction.rotate(-angle) * rect_scale * 2)
 
-            pygame.draw.polygon(
-                screen,
-                colors["black"],
-                (
-                    pos - (direction * grid_size * 0.6),
-                    left_beam - (direction * grid_size * 0.6),
-                    left_beam + (direction.rotate(90) * rect_scale),
+            offset_front = direction * grid_size * 0.7
+            if angle != 180:
+                pygame.draw.polygon(
+                    screen,
+                    colors["black"],
+                    (
+                        pos - offset_front,
+                        left_beam - offset_front,
+                        left_beam + (direction.rotate(90) * rect_scale),
+                        pos
+                        - (direction * rect_scale * 2)
+                        + (direction.rotate(90) * rect_scale),
+                        pos
+                        - (direction * rect_scale * 2)
+                        + (direction.rotate(-90) * rect_scale),
+                        right_beam + (direction.rotate(-90) * rect_scale),
+                        right_beam - offset_front,
+                    ),
+                )
+            if range:
+                n_circle_points = 40
+
+                start_vec = np.array(-direction * range)
+                points = (
+                    np.array(create_polygon(n_circle_points, start_vec)) * grid_size
+                ) + pos
+
+                circle_closed = np.concatenate([points, points[0:1]], axis=0)
+
+                corners = [
+                    pos - (direction * rect_scale),
+                    *circle_closed,
+                    pos - (direction * rect_scale),
                     pos
-                    - (direction * rect_scale * 2)
+                    - (direction * rect_scale)
                     + (direction.rotate(90) * rect_scale),
                     pos
-                    - (direction * rect_scale * 2)
+                    + (direction * rect_scale)
+                    + (direction.rotate(90) * rect_scale),
+                    pos
+                    + (direction * rect_scale)
                     + (direction.rotate(-90) * rect_scale),
-                    right_beam + (direction.rotate(-90) * rect_scale),
-                    right_beam - (direction * grid_size * 0.6),
-                ),
-            )
+                    pos
+                    - (direction * rect_scale)
+                    + (direction.rotate(-90) * rect_scale),
+                ]
+
+                pygame.draw.polygon(screen, colors["black"], [*corners])
 
     def draw_background(
         self, surface: pygame.Surface, width: int, height: int, grid_size: int
@@ -503,7 +539,9 @@ class Visualizer:
                 burnt=item["type"].startswith("Burnt"),
             )
         elif "content_list" in item and item["content_list"]:
-            triangle_offsets = create_polygon(len(item["content_list"]), length=10)
+            triangle_offsets = create_polygon(
+                len(item["content_list"]), np.array([0.10])
+            )
             scale = 1 if len(item["content_list"]) == 1 else 0.6
             for idx, o in enumerate(item["content_list"]):
                 self.draw_item(
diff --git a/overcooked_simulator/gui_2d_vis/overcooked_gui.py b/overcooked_simulator/gui_2d_vis/overcooked_gui.py
index b6a62b5b5f693675560b02fb283e3e7d616277da..a537861748079b3b6aaefc828b4b7942d41483ba 100644
--- a/overcooked_simulator/gui_2d_vis/overcooked_gui.py
+++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py
@@ -1,10 +1,13 @@
 import argparse
 import dataclasses
+import glob
 import json
 import logging
 import random
 import sys
+import uuid
 from enum import Enum
+from pathlib import Path
 from subprocess import Popen
 
 import numpy as np
@@ -28,8 +31,11 @@ from overcooked_simulator.utils import (
     url_and_port_arguments,
     disable_websocket_logging_arguments,
     add_list_of_manager_ids_arguments,
+    setup_logging,
 )
 
+CONNECT_WITH_STUDY_SERVER = False
+
 
 class MenuStates(Enum):
     Start = "Start"
@@ -58,8 +64,11 @@ class PlayerKeySet:
         Movement keys in the following order: Down, Up, Left, Right
 
         Args:
-            player_name: The name of the player to control.
-            keys: The keys which control this player in the following order: Down, Up, Left, Right, Interact, Pickup.
+            move_keys: The keys which control this players movement in the following order: Down, Up, Left, Right.
+            interact_key: The key to interact with objects in the game.
+            pickup_key: The key to pick items up or put them down.
+            switch_key: The key for switching through controllable players.
+            players: The player indices which this keyset can control.
         """
         self.move_vectors: list[list[int]] = [[-1, 0], [1, 0], [0, -1], [0, 1]]
         self.key_to_movement: dict[pygame.key, list[int]] = {
@@ -98,6 +107,11 @@ class PyGameGUI:
         port: int,
         manager_ids: list[str],
     ):
+        pygame.init()
+        pygame.display.set_icon(
+            pygame.image.load(ROOT_DIR / "gui_2d_vis" / "images" / "fish3.png")
+        )
+
         self.game_screen: pygame.Surface = None
         self.FPS = 60
         self.running = True
@@ -117,72 +131,73 @@ class PyGameGUI:
         self.screen_margin = self.visualization_config["GameWindow"]["screen_margin"]
         self.min_width = self.visualization_config["GameWindow"]["min_width"]
         self.min_height = self.visualization_config["GameWindow"]["min_height"]
-
         self.buttons_width = self.visualization_config["GameWindow"]["buttons_width"]
         self.buttons_height = self.visualization_config["GameWindow"]["buttons_height"]
-
         self.order_bar_height = self.visualization_config["GameWindow"][
             "order_bar_height"
         ]
-
-        self.window_width = self.min_width
-        self.window_height = self.min_height
-
-        self.main_window = pygame.display.set_mode(
-            (self.window_width, self.window_height)
-        )
-
-        # self.game_width, self.game_height = 0, 0
-
+        (
+            self.window_width_fullscreen,
+            self.window_height_fullscreen,
+        ) = pygame.display.get_desktop_sizes()[0]
+        self.window_width_windowed = self.min_width
+        self.window_height_windowed = self.min_height
+        self.kitchen_width = 1
+        self.kitchen_height = 1
+        self.kitchen_aspect_ratio = 1
         self.images_path = ROOT_DIR / "pygame_gui" / "images"
+        self.vis = Visualizer(self.visualization_config)
+
+        self.fullscreen = False
 
         self.menu_state = MenuStates.Start
         self.manager: pygame_gui.UIManager
 
-        self.vis = Visualizer(self.visualization_config)
-
         self.sub_processes = []
 
-    def get_window_sizes(self, state: dict):
-        kitchen_width = state["kitchen"]["width"]
-        kitchen_height = state["kitchen"]["height"]
-        if self.visualization_config["GameWindow"]["WhatIsFixed"] == "window_width":
-            game_width = self.visualization_config["GameWindow"]["size"]
-            kitchen_aspect_ratio = kitchen_height / kitchen_width
-            game_height = int(game_width * kitchen_aspect_ratio)
-            grid_size = int(game_width / (kitchen_width - 0.1))
-
-        elif self.visualization_config["GameWindow"]["WhatIsFixed"] == "window_height":
-            game_height = self.visualization_config["GameWindow"]["size"]
-            kitchen_aspect_ratio = kitchen_width / kitchen_height
-            game_width = int(game_height * kitchen_aspect_ratio)
-            grid_size = int(game_width / (kitchen_width - 0.1))
-
-        elif self.visualization_config["GameWindow"]["WhatIsFixed"] == "grid":
-            grid_size = self.visualization_config["GameWindow"]["size"]
-            game_width, game_height = (
-                kitchen_width * grid_size,
-                kitchen_height * grid_size,
-            )
+    def get_window_sizes_from_state(self, state: dict):
+        self.kitchen_width = state["kitchen"]["width"]
+        self.kitchen_height = state["kitchen"]["height"]
+        self.kitchen_aspect_ratio = self.kitchen_height / self.kitchen_width
+        game_width = self.visualization_config["GameWindow"]["min_width"] - (
+            2 * self.screen_margin
+        )
+        game_height = self.visualization_config["GameWindow"]["min_height"] - (
+            2 * self.screen_margin
+        )
 
+        if self.kitchen_width > game_width:
+            self.game_height = game_width * self.kitchen_aspect_ratio
+            self.grid_size = game_width / self.kitchen_width
         else:
-            game_width, game_height = 0, 0
-            grid_size = 0
+            self.game_width = game_height / self.kitchen_aspect_ratio
+            self.grid_size = game_width / self.kitchen_width
 
-        window_width, window_height = (
-            game_width + (2 * self.screen_margin),
-            game_height + (2 * self.screen_margin),  # bar with orders
-        )
+        self.window_width_windowed = self.min_width
+        self.window_height_windowed = self.min_height
 
-        window_width = max(window_width, self.min_width)
-        window_height = max(window_height, self.min_height)
-        return (
-            int(window_width),
-            int(window_height),
-            int(game_width),
-            int(game_height),
-            grid_size,
-        )
+    def recalc_game_size(self):
+        log.debug("Resizing game screen")
+        max_width = self.window_width - (2 * self.screen_margin)
+        max_height = self.window_height - (2 * self.screen_margin)
+        if max_width < max_height:
+            self.game_width = max_width
+            self.game_height = max_width * self.kitchen_aspect_ratio
+            self.grid_size = int(self.game_height / self.kitchen_height)
+
+        else:
+            self.game_height = max_height
+            self.game_width = max_height / self.kitchen_aspect_ratio
+            self.grid_size = int(self.game_width / self.kitchen_width)
+
+        self.game_width = max(self.game_width, 100)
+        self.game_height = max(self.game_height, 100)
+        self.grid_size = max(self.grid_size, 1)
+
+        residual_x = self.game_width - (self.kitchen_width * self.grid_size)
+        residual_y = self.game_height - (self.kitchen_height * self.grid_size)
+        self.game_width -= residual_x
+        self.game_height -= residual_y
 
     def setup_player_keys(self, n=1, disjunct=False):
         if n:
@@ -237,7 +252,7 @@ class PyGameGUI:
                     current_player_name,
                     ActionType.MOVEMENT,
                     move_vec,
-                    duration=1 / self.FPS,
+                    duration=self.time_delta,
                 )
                 self.send_action(action)
 
@@ -267,7 +282,7 @@ class PyGameGUI:
                         current_player_name, ActionType.INTERACT, InterActionData.STOP
                     )
                     self.send_action(action)
-            if event.key == key_set.switch_key:
+            if event.key == key_set.switch_key and not CONNECT_WITH_STUDY_SERVER:
                 if event.type == pygame.KEYDOWN:
                     key_set.next_player()
 
@@ -302,17 +317,27 @@ class PyGameGUI:
         )
         self.quit_button.can_hover()
 
+        fullscreen_button_rect = pygame.Rect(
+            (0, 0), (self.buttons_width * 0.7, self.buttons_height)
+        )
+        fullscreen_button_rect.topright = (-self.buttons_width, 0)
+        self.fullscreen_button = pygame_gui.elements.UIButton(
+            relative_rect=fullscreen_button_rect,
+            text="Fullscreen",
+            manager=self.manager,
+            object_id="#fullscreen_button",
+            anchors={"right": "right", "top": "top"},
+        )
+        self.fullscreen_button.can_hover()
+
+        reset_button_rect = pygame.Rect((0, 0), (self.screen_margin * 0.75, 50))
+        reset_button_rect.topright = (0, 2 * self.buttons_height)
         self.reset_button = pygame_gui.elements.UIButton(
-            relative_rect=pygame.Rect(
-                (
-                    self.window_width - (self.screen_margin * 3 // 4),
-                    self.screen_margin,
-                ),
-                (self.screen_margin - (self.screen_margin // 4), 50),
-            ),
+            relative_rect=reset_button_rect,
             text="RESET",
             manager=self.manager,
             object_id="#reset_button",
+            anchors={"right": "right", "top": "top"},
         )
         self.reset_button.can_hover()
 
@@ -357,8 +382,9 @@ class PyGameGUI:
 
         self.layout_file_paths = {
             str(p.name): p
-            for p in (ROOT_DIR / "game_content" / "layouts").glob("*.layout")
+            for p in [Path(f) for f in sorted((ROOT_DIR / "game_content" / "layouts").rglob("*.layout"))]
         }
+
         assert len(self.layout_file_paths) != 0, "No layout files."
         dropdown_width, dropdown_height = 200, 40
         self.layout_selection = pygame_gui.elements.UIDropDownMenu(
@@ -581,8 +607,8 @@ class PyGameGUI:
         self.vis.draw_orders(
             screen=self.main_window,
             state=state,
-            grid_size=self.grid_size,
-            width=self.game_width,
+            grid_size=self.buttons_height,
+            width=self.window_width - self.buttons_width - (self.buttons_width * 0.7),
             height=self.game_height,
             screen_margin=self.screen_margin,
             config=self.visualization_config,
@@ -627,18 +653,28 @@ class PyGameGUI:
             (
                 self.game_width,
                 self.game_height,
-            ),
+            )
         )
+
+        if self.fullscreen:
+            flags = pygame.FULLSCREEN
+            self.window_width = self.window_width_fullscreen
+            self.window_height = self.window_height_fullscreen
+        else:
+            flags = 0
+            self.window_width = self.window_width_windowed
+            self.window_height = self.window_height_windowed
+
         self.main_window = pygame.display.set_mode(
             (
                 self.window_width,
                 self.window_height,
-            )
+            ),
+            flags=flags,
+            display=0,
         )
 
     def reset_window_size(self):
-        self.window_width = self.min_width
-        self.window_height = self.min_height
         self.game_width = 0
         self.game_height = 0
         self.set_window_size()
@@ -701,41 +737,50 @@ class PyGameGUI:
         self.timer_label.set_text(f"Time remaining: {display_time}")
 
     def setup_environment(self):
-        environment_config_path = ROOT_DIR / "game_content" / "environment_config.yaml"
-        layout_path = self.layout_file_paths[self.layout_selection.selected_option]
-        item_info_path = ROOT_DIR / "game_content" / "item_info.yaml"
-        with open(item_info_path, "r") as file:
-            item_info = file.read()
-        with open(layout_path, "r") as file:
-            layout = file.read()
-        with open(environment_config_path, "r") as file:
-            environment_config = file.read()
-
-        seed = 161616161616
-        creation_json = CreateEnvironmentConfig(
-            manager_id=self.manager_id,
-            number_players=self.number_players,
-            environment_settings={"all_player_can_pause_game": False},
-            item_info_config=item_info,
-            environment_config=environment_config,
-            layout_config=layout,
-            seed=seed,
-        ).model_dump(mode="json")
-
-        # print(CreateEnvironmentConfig.model_validate_json(json_data=creation_json))
-        env_info = requests.post(
-            f"{self.request_url}/manage/create_env/",
-            json=creation_json,
-        )
-        if env_info.status_code == 403:
-            raise ValueError(f"Forbidden Request: {env_info.json()['detail']}")
-        env_info = env_info.json()
-        assert isinstance(env_info, dict), "Env info must be a dictionary"
-        self.current_env_id = env_info["env_id"]
-        self.player_info = env_info["player_info"]
+        if CONNECT_WITH_STUDY_SERVER:
+            self.player_info = requests.post(
+                f"http://localhost:8080/connect_to_game/{uuid.uuid4().hex}"
+            ).json()
+            self.key_sets[0].current_player = int(self.player_info["player_id"])
+            self.player_info = {self.player_info["player_id"]: self.player_info}
+        else:
+            environment_config_path = (
+                ROOT_DIR / "game_content" / "environment_config.yaml"
+            )
+            layout_path = self.layout_file_paths[self.layout_selection.selected_option]
+            item_info_path = ROOT_DIR / "game_content" / "item_info.yaml"
+            with open(item_info_path, "r") as file:
+                item_info = file.read()
+            with open(layout_path, "r") as file:
+                layout = file.read()
+            with open(environment_config_path, "r") as file:
+                environment_config = file.read()
+
+            seed = 161616161616
+            creation_json = CreateEnvironmentConfig(
+                manager_id=self.manager_id,
+                number_players=self.number_players,
+                environment_settings={"all_player_can_pause_game": False},
+                item_info_config=item_info,
+                environment_config=environment_config,
+                layout_config=layout,
+                seed=seed,
+            ).model_dump(mode="json")
+
+            # print(CreateEnvironmentConfig.model_validate_json(json_data=creation_json))
+            env_info = requests.post(
+                f"{self.request_url}/manage/create_env/",
+                json=creation_json,
+            )
+            if env_info.status_code == 403:
+                raise ValueError(f"Forbidden Request: {env_info.json()['detail']}")
+            env_info = env_info.json()
+            assert isinstance(env_info, dict), "Env info must be a dictionary"
+            self.current_env_id = env_info["env_id"]
+            self.player_info = env_info["player_info"]
 
         state = None
-        for p, (player_id, player_info) in enumerate(env_info["player_info"].items()):
+        for p, (player_id, player_info) in enumerate(self.player_info.items()):
             if p < self.number_humans_to_be_added:
                 websocket = connect(self.websocket_url + player_info["client_id"])
                 websocket.send(
@@ -799,7 +844,7 @@ class PyGameGUI:
 
         if not self.number_humans_to_be_added:
             player_id = "0"
-            player_info = env_info["player_info"][player_id]
+            player_info = self.player_info[player_id]
             websocket = connect(self.websocket_url + player_info["client_id"])
             websocket.send(
                 json.dumps({"type": "ready", "player_hash": player_info["player_hash"]})
@@ -814,13 +859,7 @@ class PyGameGUI:
             )
             state = json.loads(websocket.recv())
 
-        (
-            self.window_width,
-            self.window_height,
-            self.game_width,
-            self.game_height,
-            self.grid_size,
-        ) = self.get_window_sizes(state)
+        self.get_window_sizes_from_state(state)
 
     def start_button_press(self):
         self.menu_state = MenuStates.Game
@@ -841,13 +880,11 @@ class PyGameGUI:
 
         self.setup_environment()
 
+        self.recalc_game_size()
         self.set_window_size()
-
         self.init_ui_elements()
         log.debug("Pressed start button")
 
-        # self.api.set_sim(self.simulator)
-
     def back_button_press(self):
         self.menu_state = MenuStates.Start
         self.reset_window_size()
@@ -863,28 +900,29 @@ class PyGameGUI:
 
     def reset_button_press(self):
         # self.reset_gui_values()
-
-        requests.post(
-            f"{self.request_url}/manage/stop_env",
-            json={
-                "manager_id": self.manager_id,
-                "env_id": self.current_env_id,
-                "reason": "reset button pressed",
-            },
-        )
+        if not CONNECT_WITH_STUDY_SERVER:
+            requests.post(
+                f"{self.request_url}/manage/stop_env",
+                json={
+                    "manager_id": self.manager_id,
+                    "env_id": self.current_env_id,
+                    "reason": "reset button pressed",
+                },
+            )
 
         # self.websocket.send(json.dumps("reset_game"))
         # answer = self.websocket.recv()        log.debug("Pressed reset button")
 
     def finished_button_press(self):
-        requests.post(
-            f"{self.request_url}/manage/stop_env/",
-            json={
-                "manager_id": self.manager_id,
-                "env_id": self.current_env_id,
-                "reason": "finish button pressed",
-            },
-        )
+        if not CONNECT_WITH_STUDY_SERVER:
+            requests.post(
+                f"{self.request_url}/manage/stop_env/",
+                json={
+                    "manager_id": self.manager_id,
+                    "env_id": self.current_env_id,
+                    "reason": "finish button pressed",
+                },
+            )
         self.menu_state = MenuStates.End
         self.reset_window_size()
         log.debug("Pressed finished button")
@@ -953,9 +991,9 @@ class PyGameGUI:
             json.dumps(
                 {
                     "type": "get_state",
-                    "player_hash": self.player_info[str(self.key_sets[0].current_idx)][
-                        "player_hash"
-                    ],
+                    "player_hash": self.player_info[
+                        str(self.key_sets[0].current_player)
+                    ]["player_hash"],
                 }
             )
         )
@@ -981,7 +1019,6 @@ class PyGameGUI:
     def start_pygame(self):
         """Starts pygame and the gui loop. Each frame the game state is visualized and keyboard inputs are read."""
         log.debug(f"Starting pygame gui at {self.FPS} fps")
-        pygame.init()
         pygame.font.init()
         self.comic_sans = pygame.font.SysFont("Comic Sans MS", 30)
 
@@ -990,6 +1027,7 @@ class PyGameGUI:
         clock = pygame.time.Clock()
 
         self.reset_window_size()
+
         self.init_ui_elements()
         self.manage_button_visibility()
 
@@ -999,13 +1037,22 @@ class PyGameGUI:
         self.running = True
         while self.running:
             try:
-                time_delta = clock.tick(self.FPS) / 1000.0
+                self.time_delta = clock.tick(self.FPS) / 1000
 
+                # print(clock.get_time())
                 for event in pygame.event.get():
                     if event.type == pygame.QUIT:
                         self.running = False
 
-                        # UI Buttons:
+                    # elif event.type == pygame.VIDEORESIZE:
+                    #     # scrsize = event.size
+                    #     self.window_width_windowed = event.w
+                    #     self.window_height_windowed = event.h
+                    #     self.recalc_game_size()
+                    #     self.set_window_size()
+                    #     self.init_ui_elements()
+                    #     self.manage_button_visibility()
+
                     if event.type == pygame_gui.UI_BUTTON_PRESSED:
                         match event.ui_element:
                             case self.start_button:
@@ -1015,6 +1062,7 @@ class PyGameGUI:
                                 ):
                                     continue
                                 self.start_button_press()
+
                             case self.back_button:
                                 self.back_button_press()
                                 self.disconnect_websockets()
@@ -1022,9 +1070,11 @@ class PyGameGUI:
                             case self.finished_button:
                                 self.finished_button_press()
                                 self.disconnect_websockets()
+
                             case self.quit_button:
                                 self.quit_button_press()
                                 self.disconnect_websockets()
+
                             case self.reset_button:
                                 self.reset_button_press()
                                 self.disconnect_websockets()
@@ -1055,6 +1105,18 @@ class PyGameGUI:
                             case self.xbox_controller_button:
                                 print("xbox_controller_button pressed.")
 
+                            case self.fullscreen_button:
+                                self.fullscreen = not self.fullscreen
+                                if self.fullscreen:
+                                    self.window_width = self.window_width_fullscreen
+                                    self.window_height = self.window_height_fullscreen
+                                else:
+                                    self.window_width = self.window_width_windowed
+                                    self.window_height = self.window_height_windowed
+                                self.recalc_game_size()
+                                self.set_window_size()
+                                self.init_ui_elements()
+
                         self.update_selection_elements()
 
                         self.manage_button_visibility()
@@ -1102,7 +1164,7 @@ class PyGameGUI:
                     case MenuStates.End:
                         self.update_conclusion_label(state)
 
-                self.manager.update(time_delta)
+                self.manager.update(self.time_delta)
                 pygame.display.flip()
 
             except (KeyboardInterrupt, SystemExit):
@@ -1114,6 +1176,7 @@ class PyGameGUI:
 
 
 def main(url: str, port: int, manager_ids: list[str]):
+    setup_logging()
     gui = PyGameGUI(
         url=url,
         port=port,
@@ -1133,4 +1196,4 @@ if __name__ == "__main__":
     disable_websocket_logging_arguments(parser)
     add_list_of_manager_ids_arguments(parser)
     args = parser.parse_args()
-    main(args.url, args.port, args.manager_ids, args.enable_websocket_logging)
+    main(args.url, args.port, args.manager_ids)
diff --git a/overcooked_simulator/gui_2d_vis/visualization.yaml b/overcooked_simulator/gui_2d_vis/visualization.yaml
index 428c76c01bd5567c2b420679671282bda7d74a4c..a0d7989c1c1bf9c2a96fcc28e1baf09bcd96e55e 100644
--- a/overcooked_simulator/gui_2d_vis/visualization.yaml
+++ b/overcooked_simulator/gui_2d_vis/visualization.yaml
@@ -1,10 +1,8 @@
 # colors: https://www.webucator.com/article/python-color-constants-module/
 
 GameWindow:
-  WhatIsFixed: grid  # grid or window_width or window_height
-  size: 50
   screen_margin: 100
-  min_width: 700
+  min_width: 900
   min_height: 600
   buttons_width: 180
   buttons_height: 60
diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py
index a119d34c7f3d17060b91552b94ca07f5bbcb58d6..d157ce604b478f28afe1c827f4a7e71b30aa3a49 100644
--- a/overcooked_simulator/overcooked_environment.py
+++ b/overcooked_simulator/overcooked_environment.py
@@ -168,6 +168,9 @@ class Environment:
             self.player_view_angle = self.environment_config["player_config"][
                 "view_angle"
             ]
+            self.player_view_range = self.environment_config["player_config"][
+                "view_range"
+            ]
 
         self.extra_setup_functions()
 
@@ -237,8 +240,6 @@ class Environment:
         ) = self.parse_layout_file()
         self.hook(LAYOUT_FILE_PARSED)
 
-        self.counter_positions = np.array([c.pos for c in self.counters])
-
         self.world_borders = np.array(
             [[-0.5, self.kitchen_width - 0.5], [-0.5, self.kitchen_height - 0.5]],
             dtype=float,
@@ -248,6 +249,9 @@ class Environment:
             "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(
@@ -267,6 +271,8 @@ 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_and_score.create_init_orders(self.env_time)
         self.start_time = self.env_time
         """The relative env time when it started."""
@@ -290,6 +296,27 @@ class Environment:
             env_start_time_worldtime=datetime.now(),
         )
 
+    def overwrite_counters(self, counters):
+        self.counters = counters
+        self.counter_positions = np.array([c.pos for c in self.counters])
+
+        progress_counter_classes = list(
+            filter(
+                lambda cl: hasattr(cl, "progress"),
+                dict(
+                    inspect.getmembers(
+                        sys.modules["overcooked_simulator.counters"], inspect.isclass
+                    )
+                ).values(),
+            )
+        )
+        self.progressing_counters = list(
+            filter(
+                lambda c: c.__class__ in progress_counter_classes,
+                self.counters,
+            )
+        )
+
     @property
     def game_ended(self) -> bool:
         """Whether the game is over or not based on the calculated `Environment.env_time_end`"""
@@ -406,11 +433,13 @@ class Environment:
 
         lines = list(filter(lambda l: l != "", lines))
         for line in lines:
-            line = line.replace("\n", "").replace(" ", "")  # remove newline char
+            line = line.replace(" ", "")
+            if not line or line.startswith(";"):
+                break
             current_x: float = starting_at
             grid_line = []
             for character in line:
-                character = character.capitalize()
+                # character = character.capitalize()
                 pos = np.array([current_x, current_y])
 
                 assert self.counter_factory.can_map(
@@ -435,7 +464,7 @@ class Environment:
             current_y += 1
 
         self.kitchen_width: float = len(lines[0]) + starting_at
-        self.kitchen_height = len(lines) + starting_at
+        self.kitchen_height = current_y
 
         self.determine_counter_orientations(
             counters, grid, np.array([self.kitchen_width / 2, self.kitchen_height / 2])
@@ -481,7 +510,7 @@ class Environment:
                 c.set_orientation(nearest_vec)
 
             elif grid_idx[0] == 0:
-                if grid_idx[1] == 0:
+                if grid_idx[1] == 0 or fst_counter_in_row is None:
                     # counter top left
                     c.set_orientation(np.array([1, 0]))
                 else:
@@ -666,8 +695,20 @@ class Environment:
 
         for idx, p in enumerate(self.players.values()):
             if not (new_positions[idx] == player_positions[idx]).all():
-                p.turn(player_movement_vectors[idx])
-                p.move_abs(new_positions[idx])
+                p.pos = new_positions[idx]
+                p.perform_interact_stop()
+
+            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
+            )
 
     def add_player(self, player_name: str, pos: npt.NDArray = None):
         """Add a player to the environment.
@@ -783,6 +824,7 @@ class Environment:
                     "position": self.players[player_id].pos.tolist(),
                     "angle": self.player_view_angle,
                     "counter_mask": None,
+                    "range": self.player_view_range,
                 }
                 if self.player_view_restricted
                 else None,
diff --git a/overcooked_simulator/player.py b/overcooked_simulator/player.py
index c367561e98862425ecc1590087ef7deb982e608e..638bc4cd5c0d537aa5823af96543a008b43c3300 100644
--- a/overcooked_simulator/player.py
+++ b/overcooked_simulator/player.py
@@ -37,6 +37,9 @@ class PlayerConfig:
     """Whether or not the player can see the entire map at once or just a view frustrum."""
     view_angle: int | None = None
     """Angle of the players view if restricted."""
+    view_range: float | None = None
+    """Range of the players view if restricted. In grid units."""
+
 
 class Player:
     """Class representing a player in the game environment. A player consists of a name, their position and what
@@ -88,19 +91,7 @@ class Player:
         function of the environment"""
         self.current_movement = move_vector
         self.movement_until = move_until
-
-    def move(self, movement: npt.NDArray[float]):
-        """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
-        """
-        if self.interacting and np.any(movement):
-            self.perform_interact_stop()
-        self.pos += movement
-        if np.linalg.norm(movement) != 0:
-            self.turn(movement)
+        self.perform_interact_stop()
 
     def move_abs(self, new_pos: npt.NDArray[float]):
         """Overwrites the player location by the new_pos 2d-vector. Absolute movement.
@@ -165,7 +156,7 @@ class Player:
             self.holding.combine(returned_by_counter)
 
         log.debug(
-            f"Self: {self.holding}, {counter.__class__.__name__}: {counter.occupied_by}"
+            f"Self: {self.holding.__class__.__name__}: {self.holding}, {counter.__class__.__name__}: {counter.occupied_by}"
         )
         # if isinstance(self.holding, Plate):
         #     log.debug(self.holding.clean)
diff --git a/overcooked_simulator/state_representation.py b/overcooked_simulator/state_representation.py
index c989aecca869846a993ce007bdff68072205ca76..fe587ca6d41c40514d0fe3d8e862c09575a6c09b 100644
--- a/overcooked_simulator/state_representation.py
+++ b/overcooked_simulator/state_representation.py
@@ -72,6 +72,7 @@ class ViewRestriction(BaseModel):
     position: list[float]
     angle: int  # degrees
     counter_mask: None | list[bool]
+    range: float | None
 
 
 class InfoMsgLevel(Enum):
diff --git a/overcooked_simulator/utils.py b/overcooked_simulator/utils.py
index b78d44af869d9a53ec17f5f2cd8eda191e0b4e17..aa91a0a99190c177f28dec0fc036e2f001b83b8a 100644
--- a/overcooked_simulator/utils.py
+++ b/overcooked_simulator/utils.py
@@ -113,21 +113,21 @@ def setup_logging(enable_websocket_logging=False):
         logging.getLogger("websockets.client").setLevel(logging.ERROR)
 
 
-def url_and_port_arguments(parser):
+def url_and_port_arguments(parser, server_name="game server", default_port=8000):
     parser.add_argument(
         "-url",
         "--url",
         "--host",
         type=str,
         default="localhost",
-        help="Overcooked game server host url.",
+        help=f"Overcooked {server_name} host url.",
     )
     parser.add_argument(
         "-p",
         "--port",
         type=int,
-        default=8000,
-        help="Port number for the game engine server",
+        default=default_port,
+        help=f"Port number for the {server_name}",
     )
 
 
@@ -168,3 +168,13 @@ class NumpyAndDataclassEncoder(json.JSONEncoder):
         #     return getattr(obj, "__name__", "Unknown")
 
         return json.JSONEncoder.default(self, obj)
+
+
+def create_layout(w, h):
+    for y in range(h):
+        for x in range(w):
+            if x == 0 or y == 0 or x == w - 1 or y == h - 1:
+                print("#", end="")
+            else:
+                print("_", end="")
+        print("")
diff --git a/tests/test_start.py b/tests/test_start.py
index 8efe8da9ecf1c67974f88a18b1bcdb0b1cd3786a..8531f9d81dc52cb4327c98e641406ad2d45bdec2 100644
--- a/tests/test_start.py
+++ b/tests/test_start.py
@@ -124,7 +124,7 @@ def test_player_reach(env_config, layout_empty_config, item_info):
 
     counter_pos = np.array([2, 2])
     counter = Counter(pos=counter_pos, hook=Hooks(env))
-    env.counters = [counter]
+    env.overwrite_counters([counter])
     env.add_player("1", np.array([2, 4]))
     env.player_movement_speed = 1
     player = env.players["1"]
@@ -144,7 +144,7 @@ def test_pickup(env_config, layout_config, item_info):
     counter_pos = np.array([2, 2])
     counter = Counter(pos=counter_pos, hook=Hooks(env))
     counter.occupied_by = Item(name="Tomato", item_info=None)
-    env.counters = [counter]
+    env.overwrite_counters([counter])
 
     env.add_player("1", np.array([2, 3]))
     player = env.players["1"]