From 61d3103f54017a4a956741fa5ad5de3008d06937 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Florian=20Schr=C3=B6der?=
 <fschroeder@techfak.uni-bielefeld.de>
Date: Mon, 10 Mar 2025 13:41:52 +0100
Subject: [PATCH] Add new visualization configs and hooks for environment
 control

Introduced multiple visualization configurations for diverse scenarios and added various hooks for enhanced game control (e.g., restricted movement, facing direction, and resetting player info). Updated supporting assets and layouts to align with these additions.
---
 .../configs/environment_config.yaml           |   4 +
 .../environment_config_moonlander.yaml        | 194 +++----
 .../{mazes => with_addons}/m-basic.layout     |   0
 .../moonlander.layout}                        |   0
 cooperative_cuisine/environment.py            |   2 +-
 .../hook_addons/add_player_movement_info.py   |  19 +
 .../hook_addons/reset_player_info.py          |  30 ++
 .../hook_addons/restrict_facing_direction.py  |  23 +
 .../hook_addons/restricted_movement.py        |   2 +
 cooperative_cuisine/pygame_2d_vis/drawing.py  |  62 ++-
 cooperative_cuisine/pygame_2d_vis/gui.py      |  45 +-
 .../images/moonlander_assets/coin.png         | Bin 0 -> 2059 bytes
 .../images/moonlander_assets/comet_master.png | Bin 0 -> 3504 bytes
 .../moonlander_assets/spaceship_master.png    | Bin 0 -> 1285 bytes
 .../pygame_2d_vis/visualization.yaml          |   8 +-
 .../visualization_moonlander.yaml             | 491 +++++++++++++++++
 .../visualization_no_caching.yaml             | 493 ++++++++++++++++++
 17 files changed, 1256 insertions(+), 117 deletions(-)
 rename cooperative_cuisine/configs/layouts/{mazes => with_addons}/m-basic.layout (100%)
 rename cooperative_cuisine/configs/layouts/{mazes/empty_large.layout => with_addons/moonlander.layout} (100%)
 create mode 100644 cooperative_cuisine/hook_addons/add_player_movement_info.py
 create mode 100644 cooperative_cuisine/hook_addons/reset_player_info.py
 create mode 100644 cooperative_cuisine/hook_addons/restrict_facing_direction.py
 create mode 100644 cooperative_cuisine/pygame_2d_vis/images/moonlander_assets/coin.png
 create mode 100644 cooperative_cuisine/pygame_2d_vis/images/moonlander_assets/comet_master.png
 create mode 100644 cooperative_cuisine/pygame_2d_vis/images/moonlander_assets/spaceship_master.png
 create mode 100644 cooperative_cuisine/pygame_2d_vis/visualization_moonlander.yaml
 create mode 100644 cooperative_cuisine/pygame_2d_vis/visualization_no_caching.yaml

diff --git a/cooperative_cuisine/configs/environment_config.yaml b/cooperative_cuisine/configs/environment_config.yaml
index 6722182b..5790dcc2 100644
--- a/cooperative_cuisine/configs/environment_config.yaml
+++ b/cooperative_cuisine/configs/environment_config.yaml
@@ -48,6 +48,10 @@ layout_chars:
   exclamation: Lava  # ! Lave
   dquote: Counter  # " wall/truck
   p: Counter # second plate return ??
+  exclamation: Lava  # ! Lave
+  star: Target  # * Target
+  dquote: Counter  # " wall/truck
+  p: Counter # second plate return ??
 
 
 orders:
diff --git a/cooperative_cuisine/configs/environment_config_moonlander.yaml b/cooperative_cuisine/configs/environment_config_moonlander.yaml
index e81e133e..cbaf40fd 100644
--- a/cooperative_cuisine/configs/environment_config_moonlander.yaml
+++ b/cooperative_cuisine/configs/environment_config_moonlander.yaml
@@ -1,7 +1,7 @@
 # you have to remove (put in comment) the assert in environment.py:         assert meals_to_be_ordered, "Need possible meals for order generation."
 plates:
-  clean_plates: 2
-  dirty_plates: 1
+  clean_plates: 0
+  dirty_plates: 0
   plate_delay: [ 5, 10 ]
   # range of seconds until the dirty plate arrives.
 
@@ -116,103 +116,119 @@ hook_callbacks:
     callback_class: !!python/name:cooperative_cuisine.hook_addons.restricted_movement.RestrictedMovementHook ''
     callback_class_kwargs:
       ignore_dims: [t, b]
-  # # ---------------  Scoring  ---------------
-  orders:
-    hooks: [ completed_order ]
-    callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks ''
-    callback_class_kwargs:
-      time_dependence_func: !!python/name:cooperative_cuisine.scores.constant_score ''
-      time_dependence_kwargs: { }
-      static_score: 100
-      score_on_specific_kwarg: meal_name
-      score_map: { }
-
-  not_ordered_meals:
-    hooks: [ serve_not_ordered_meal ]
-    callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks ''
-    callback_class_kwargs:
-      static_score: 2
-  trashcan_usages:
-    hooks: [ trashcan_usage ]
-    callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks ''
+  fixed_agent_direction:
+    hooks: [ post_step ]
+    callback_class: !!python/name:cooperative_cuisine.hook_addons.restrict_facing_direction.RestrictedFacingDirectionHook ''
     callback_class_kwargs:
-      static_score: -5
-  expired_orders:
-    hooks: [ order_expired ]
-    callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks ''
+      fixed_directions: [[0, 1]]
+  add_movement_info:
+    hooks: [ pre_perform_action ]
+    callback_class: !!python/name:cooperative_cuisine.hook_addons.add_player_movement_info.AddPlayerMovementInfoHook ''
+    callback_class_kwargs: {}
+  reset_movement_info:
+    hooks: [ state_dict ]
+    callback_class: !!python/name:cooperative_cuisine.hook_addons.reset_player_info.ResetPlayerInfoHook ''
     callback_class_kwargs:
-      static_score: -10
+      keys: [ last_movement ]
+      delay: 0.2
+      delay_key: 1
+  # # ---------------  Scoring  ---------------
+#  orders:
+#    hooks: [ completed_order ]
+#    callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks ''
+#    callback_class_kwargs:
+#      time_dependence_func: !!python/name:cooperative_cuisine.scores.constant_score ''
+#      time_dependence_kwargs: { }
+#      static_score: 100
+#      score_on_specific_kwarg: meal_name
+#      score_map: { }
+#
+#  not_ordered_meals:
+#    hooks: [ serve_not_ordered_meal ]
+#    callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks ''
+#    callback_class_kwargs:
+#      static_score: 2
+#  trashcan_usages:
+#    hooks: [ trashcan_usage ]
+#    callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks ''
+#    callback_class_kwargs:
+#      static_score: -5
+#  expired_orders:
+#    hooks: [ order_expired ]
+#    callback_class: !!python/name:cooperative_cuisine.scores.ScoreViaHooks ''
+#    callback_class_kwargs:
+#      static_score: -10
     # --------------- Recording ---------------
   #  json_states:
   #    hooks: [ json_state ]
   #    callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder ''
   #    callback_class_kwargs:
   #      record_path: USER_LOG_DIR/ENV_NAME/json_states.jsonl
-  actions:
-    hooks: [ pre_perform_action ]
-    callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder ''
-    callback_class_kwargs:
-      record_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl
-  random_env_events:
-    hooks: [ order_duration_sample, plate_out_of_kitchen_time ]
-    callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder ''
-    callback_class_kwargs:
-      record_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl
-      add_hook_ref: true
-  env_configs:
-    hooks: [ env_initialized, item_info_config ]
-    callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder ''
-    callback_class_kwargs:
-      record_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl
-      add_hook_ref: true
+#  actions:
+#    hooks: [ pre_perform_action ]
+#    callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder ''
+#    callback_class_kwargs:
+#      record_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl
+#  random_env_events:
+#    hooks: [ order_duration_sample, plate_out_of_kitchen_time ]
+#    callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder ''
+#    callback_class_kwargs:
+#      record_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl
+#      add_hook_ref: true
+#  env_configs:
+#    hooks: [ env_initialized, item_info_config ]
+#    callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder ''
+#    callback_class_kwargs:
+#      record_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl
+#      add_hook_ref: true
 
   # Game event recording
-  game_events:
-    hooks:
-      - init_orders
-      - post_counter_pick_up
-      - post_counter_drop_off
-      - post_dispenser_pick_up
-      - cutting_board_100
-      - player_start_interaction
-      - player_end_interact
-      - post_serving
-      - no_serving
-      - dirty_plate_arrives
-      - trashcan_usage
-      - plate_cleaned
-      - added_plate_to_sink
-      - drop_on_sink_addon
-      - pick_up_from_sink_addon
-      - serve_not_ordered_meal
-      - serve_without_plate
-      - completed_order
-      - new_orders
-      - order_expired
-      - action_on_not_reachable_counter
-      - new_fire
-      - fire_spreading
-      - drop_off_on_cooking_equipment
-      - players_collide
-      - post_plate_dispenser_pick_up
-      - post_plate_dispenser_drop_off
-      - drop_off_on_cooking_equipment_plate_dispenser
-      - on_item_transition
-      - progress_started
-      - progress_finished
-      - content_ready
-      - dispenser_item_returned
-      - additional_state_update
-      - game_ended_step
-      - score_changed
-      - fire_extinguished
-      - pick_up_on_cooking_equipment
-
-
-    callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder ''
-    callback_class_kwargs:
-      record_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl
-      add_hook_ref: true
+#  game_events:
+#    hooks:
+#      - init_orders
+#      - post_counter_pick_up
+#      - post_counter_drop_off
+#      - post_dispenser_pick_up
+#      - cutting_board_100
+#      - player_start_interaction
+#      - player_end_interact
+#      - post_serving
+#      - no_serving
+#      - dirty_plate_arrives
+#      - trashcan_usage
+#      - plate_cleaned
+#      - added_plate_to_sink
+#      - drop_on_sink_addon
+#      - pick_up_from_sink_addon
+#      - serve_not_ordered_meal
+#      - serve_without_plate
+#      - completed_order
+#      - new_orders
+#      - order_expired
+#      - action_on_not_reachable_counter
+#      - new_fire
+#      - fire_spreading
+#      - drop_off_on_cooking_equipment
+#      - players_collide
+#      - post_plate_dispenser_pick_up
+#      - post_plate_dispenser_drop_off
+#      - drop_off_on_cooking_equipment_plate_dispenser
+#      - on_item_transition
+#      - progress_started
+#      - progress_finished
+#      - content_ready
+#      - dispenser_item_returned
+#      - additional_state_update
+#      - game_ended_step
+#      - score_changed
+#      - fire_extinguished
+#      - pick_up_on_cooking_equipment
+#
+#
+#    callback_class: !!python/name:cooperative_cuisine.recording.FileRecorder ''
+#    callback_class_kwargs:
+#      record_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl
+#      add_hook_ref: true
 
 
 #  info_msg:
diff --git a/cooperative_cuisine/configs/layouts/mazes/m-basic.layout b/cooperative_cuisine/configs/layouts/with_addons/m-basic.layout
similarity index 100%
rename from cooperative_cuisine/configs/layouts/mazes/m-basic.layout
rename to cooperative_cuisine/configs/layouts/with_addons/m-basic.layout
diff --git a/cooperative_cuisine/configs/layouts/mazes/empty_large.layout b/cooperative_cuisine/configs/layouts/with_addons/moonlander.layout
similarity index 100%
rename from cooperative_cuisine/configs/layouts/mazes/empty_large.layout
rename to cooperative_cuisine/configs/layouts/with_addons/moonlander.layout
diff --git a/cooperative_cuisine/environment.py b/cooperative_cuisine/environment.py
index 6ad7bdd8..c754d995 100644
--- a/cooperative_cuisine/environment.py
+++ b/cooperative_cuisine/environment.py
@@ -312,7 +312,7 @@ class Environment:
         self.overwrite_counters(self.counters)
         
         meals_to_be_ordered = self.recipe_validation.validate_environment(self.counters)
-        assert meals_to_be_ordered, "Need possible meals for order generation."
+        # assert meals_to_be_ordered, "Need possible meals for order generation."
         
         available_meals = {meal: self.item_info[meal] for meal in meals_to_be_ordered}
         self.order_manager.set_available_meals(available_meals)
diff --git a/cooperative_cuisine/hook_addons/add_player_movement_info.py b/cooperative_cuisine/hook_addons/add_player_movement_info.py
new file mode 100644
index 00000000..2f38e966
--- /dev/null
+++ b/cooperative_cuisine/hook_addons/add_player_movement_info.py
@@ -0,0 +1,19 @@
+import numpy as np
+
+from datetime import datetime
+from cooperative_cuisine.action import Action, ActionType
+from cooperative_cuisine.environment import Environment
+from cooperative_cuisine.hooks import HookCallbackClass
+
+
+class AddPlayerMovementInfoHook(HookCallbackClass):
+
+    def __call__(self, hook_ref: str, env: Environment, **kwargs):
+        assert "action" in kwargs
+        action: Action = kwargs["action"]
+        for player in env.players.values():
+            if player.name == action.player:
+                player.player_info["last_movement"] = (action.action_data.tolist(), env.env_time.isoformat())
+                break
+            
+            
diff --git a/cooperative_cuisine/hook_addons/reset_player_info.py b/cooperative_cuisine/hook_addons/reset_player_info.py
new file mode 100644
index 00000000..8a919935
--- /dev/null
+++ b/cooperative_cuisine/hook_addons/reset_player_info.py
@@ -0,0 +1,30 @@
+import numpy as np
+from datetime import datetime, timedelta
+from cooperative_cuisine.action import Action, ActionType
+from cooperative_cuisine.environment import Environment
+from cooperative_cuisine.hooks import HookCallbackClass
+
+
+class ResetPlayerInfoHook(HookCallbackClass):
+    
+    def __init__(self, name: str, env: Environment, keys: list[str] = None, delay: float | int = None, delay_key: str | int = None, **kwargs):
+        super().__init__(name, env, **kwargs)
+        self.keys = keys
+        self.delay = delay
+        self.delay_key = delay_key
+
+    def __call__(self, hook_ref: str, env: Environment, **kwargs):
+        for player in env.players.values():
+            if self.keys is None:
+                player.player_info = {}
+            else:
+                for key in self.keys:
+                    if key in player.player_info:
+                        if self.delay is not None:
+                            t = datetime.fromisoformat(player.player_info[key][self.delay_key])
+                            if timedelta(seconds=self.delay) < env.env_time - t:
+                                player.player_info[key] = None
+                                del player.player_info[key]
+                        else:
+                            del player.player_info[key]
+            
diff --git a/cooperative_cuisine/hook_addons/restrict_facing_direction.py b/cooperative_cuisine/hook_addons/restrict_facing_direction.py
new file mode 100644
index 00000000..5b66d9fa
--- /dev/null
+++ b/cooperative_cuisine/hook_addons/restrict_facing_direction.py
@@ -0,0 +1,23 @@
+import numpy as np
+
+from cooperative_cuisine.action import Action, ActionType
+from cooperative_cuisine.environment import Environment
+from cooperative_cuisine.hooks import HookCallbackClass
+
+
+class RestrictedFacingDirectionHook(HookCallbackClass):
+    def __init__(self, name: str, env: Environment, fixed_directions: list[list[int]] = None, **kwargs):
+        super().__init__(name, env, **kwargs)
+        self.fixed_directions = [np.array(d) for d in fixed_directions] if fixed_directions is not None else []
+    
+    def _clip_to_closest_direction(self, direction: np.ndarray) -> np.ndarray:
+        if not self.fixed_directions:
+            return direction
+        direction = direction / np.linalg.norm(direction)
+        best_match = max(self.fixed_directions, key=lambda d: np.dot(direction, d / np.linalg.norm(d)))
+        return best_match
+    
+    def __call__(self, hook_ref: str, env: Environment, **kwargs):
+        for player in env.players.values():
+            if player.facing_direction is not None:
+                player.facing_direction = self._clip_to_closest_direction(player.facing_direction)
diff --git a/cooperative_cuisine/hook_addons/restricted_movement.py b/cooperative_cuisine/hook_addons/restricted_movement.py
index b3d07a36..cbf8e2cd 100644
--- a/cooperative_cuisine/hook_addons/restricted_movement.py
+++ b/cooperative_cuisine/hook_addons/restricted_movement.py
@@ -22,3 +22,5 @@ class RestrictedMovementHook(HookCallbackClass):
                 action.action_data[0] = 0
             if "r" in self.ignore_dims and action.action_data[0] > 0:
                 action.action_data[0] = 0
+            
+            
diff --git a/cooperative_cuisine/pygame_2d_vis/drawing.py b/cooperative_cuisine/pygame_2d_vis/drawing.py
index fb4807fa..1f463386 100644
--- a/cooperative_cuisine/pygame_2d_vis/drawing.py
+++ b/cooperative_cuisine/pygame_2d_vis/drawing.py
@@ -248,18 +248,20 @@ class Visualizer:
         
         if "items" in state:
             for (name, pos) in state["items"]:
-                pos[0] = pos[0] * grid_size
-                pos[1] = pos[1] * grid_size
-                
-                self.draw_item(pos=pos, item={"type": name}, screen=screen, grid_size=grid_size)
+                self.draw_item(pos=pos, item={"type": name}, screen=screen)
         
-        for idx, col in zip(controlled_player_idxs, [colors["red"], colors["blue"]]):
-            pygame.draw.circle(
-                screen,
-                col,
-                (np.array(state["players"][int(idx)]["pos"]) + 0.5) * self.grid_size,
-                (self.grid_size / 2),
-            )
+        if "PlayerHighlighting" in self.config and "controlled_player_colors" in self.config["PlayerHighlighting"]:
+            controlled_player_colors = list(map(lambda c: colors[c] if c in colors else c, self.config["PlayerHighlighting"]["controlled_player_colors"]))
+        else:
+            controlled_player_colors = [colors["red"], colors["blue"]]
+        if controlled_player_colors:
+            for idx, color in zip(controlled_player_idxs, controlled_player_colors):
+                pygame.draw.circle(
+                    screen,
+                    color,
+                    (np.array(state["players"][int(idx)]["pos"]) + 0.5) * self.grid_size,
+                    (self.grid_size / 2),
+                )
         
         self.draw_players(
             screen,
@@ -406,8 +408,7 @@ class Visualizer:
         if "ground" in state:
             for pos, ground in state["ground"]:
                 if ground in self.config:
-                    self.draw_thing(surface, [(pos[0] * grid_size) + block_size, (pos[1] * grid_size) + block_size],
-                                    grid_size, self.config[ground]["parts"])
+                    self.draw_thing(surface, pos, self.config[ground]["parts"])
     
     def draw_image(
         self,
@@ -461,15 +462,31 @@ class Visualizer:
         color: RGB,
         facing: npt.NDArray[float] | list[float],
         is_robot: bool,
+        player_info: dict = None,
     ):
-        pygame.draw.circle(
-            screen,
-            color,
-            self.model_to_world_coords(pos - facing * 0.25),
-            self.grid_size * 0.18 if is_robot else self.grid_size * 0.2,
-        )
-        sprite = self.config["Cook"]["parts"] if not is_robot else self.config["RobotChef"]["parts"]
-        self.draw_thing(screen, pos, sprite, scale=1.0, orientation=facing.tolist())
+        sprite_config =  self.config["Cook"] if not is_robot else self.config["RobotChef"]
+        if "add_circle_color" in sprite_config:
+            pygame.draw.circle(
+                screen,
+                color,
+                self.model_to_world_coords(pos - facing * 0.25),
+                self.grid_size * 0.18 if is_robot else self.grid_size * 0.2,
+            )
+        parts = sprite_config["parts"]
+        if "based_on_movement_data" in sprite_config and player_info and "last_movement" in player_info:
+            direction = None
+            match player_info["last_movement"][0]:
+                case [0.0, 1.0]:
+                    direction = "down"
+                case [1.0, 0.0]:
+                    direction = "right"
+                case [0.0, -1.0]:
+                    direction = "up"
+                case [-1.0, 0.0]:
+                    direction = "left"
+            if direction is not None and direction in sprite_config["based_on_movement_data"]:
+                parts = sprite_config["based_on_movement_data"][direction]
+        self.draw_thing(screen, pos, parts, scale=1.0, orientation=facing.tolist())
     
     def draw_players(
         self,
@@ -489,6 +506,7 @@ class Visualizer:
             
             pos = player_dict["pos"]
             facing = np.array(player_dict["facing_direction"], dtype=float)
+            player_info = player_dict["player_info"]
             
             if self.USE_PLAYER_COOK_SPRITES:
                 if "player_type" in player_dict["player_info"]:
@@ -497,7 +515,7 @@ class Visualizer:
                     robot = False
                 
                 self.draw_cook(
-                    screen, pos, colors[self.player_colors[p_idx]], facing, is_robot=robot
+                    screen, pos, colors[self.player_colors[p_idx]], facing, is_robot=robot, player_info=player_info
                 )
             else:
                 player_radius = 0.4
diff --git a/cooperative_cuisine/pygame_2d_vis/gui.py b/cooperative_cuisine/pygame_2d_vis/gui.py
index af1afb34..6f8c2580 100644
--- a/cooperative_cuisine/pygame_2d_vis/gui.py
+++ b/cooperative_cuisine/pygame_2d_vis/gui.py
@@ -45,6 +45,7 @@ from cooperative_cuisine.utils import (
 )
 from cooperative_cuisine.pygame_2d_vis.generate_layout_thumbnail import layout_thumbnail
 
+DEFAULT_VIS_CONFIG = ROOT_DIR / "pygame_2d_vis" / "visualization.yaml"
 
 class MenuStates(Enum):
     """Enumeration of "Page" types in the 2D pygame vis."""
@@ -172,8 +173,10 @@ class PyGameGUI:
         
         self.manager_id = random.choice(manager_ids)
         
-        with open(ROOT_DIR / "pygame_2d_vis" / "visualization.yaml", "r") as file:
-            self.visualization_config = yaml.safe_load(file)
+        self.current_visualisation_config_path = None
+        self.visualization_config = None
+        self.vis = None
+        self.set_vis_config(DEFAULT_VIS_CONFIG)
         
         self.language = self.visualization_config["Gui"]["language"]
         self.skip_tutorial = self.visualization_config["Gui"]["skip_tutorial"]
@@ -211,7 +214,7 @@ class PyGameGUI:
         self.kitchen_height = 1
         self.kitchen_aspect_ratio = 1
         self.images_path = ROOT_DIR / "pygame_gui" / "images"
-        self.vis = Visualizer(self.visualization_config)
+        
         
         self.last_score: float = 0
         self.switch_score_color: bool = False
@@ -238,6 +241,14 @@ class PyGameGUI:
         }
         self.item_info_file_paths = sorted(list(self.item_info_file_paths_dict.keys()))
         
+        self.visuaization_config_file_paths_dict = {
+            p.name.replace("visualization", "vis"): p for p in (ROOT_DIR / "pygame_2d_vis").rglob("vis*.yaml")
+        }
+        self.visuaization_config_file_paths_dict.update(
+            {p.name.replace("visualization", "vis"): p for p in (ROOT_DIR / "configs").glob("vis*.yaml")}
+        )
+        self.visuaization_config_file_paths = sorted(list(self.visuaization_config_file_paths_dict.keys()))
+        
         self.current_layout_idx = 0
         
         self.selected_layout = "basic.layout"
@@ -449,6 +460,13 @@ class PyGameGUI:
                     if event.type == pygame.JOYBUTTONDOWN:
                         key_set.next_player()
     
+    def set_vis_config(self, vis_config_path):
+        # GUI Elements etc. will not be updated
+        self.current_visualisation_config_path = vis_config_path
+        with open(vis_config_path, "r") as file:
+            self.visualization_config = yaml.safe_load(file)
+        self.vis = Visualizer(self.visualization_config)
+    
     def set_window_size(self):
         """Sets the window size based on fullscreen or not."""
         if self.fullscreen:
@@ -642,6 +660,16 @@ class PyGameGUI:
                 "right": "right",
             },
         )
+        self.visualization_config_selection = pygame_gui.elements.UIDropDownMenu(
+            relative_rect=r,
+            manager=self.manager,
+            options_list=self.visuaization_config_file_paths,
+            starting_option=[c for c in self.visuaization_config_file_paths if "vis.yaml" == c][0],
+            anchors={
+                "top_target": self.item_info_selection,
+                "right": "right",
+            },
+        )
         smooth_options = ["Smooth Paths", "Don't Smooth Paths"]
         self.smooth_paths_selection = pygame_gui.elements.UIDropDownMenu(
             relative_rect=r,
@@ -649,10 +677,13 @@ class PyGameGUI:
             options_list=smooth_options,
             starting_option=smooth_options[1],
             anchors={
-                "top_target": self.item_info_selection,
+                "top_target": self.visualization_config_selection,
                 "right": "right",
             },
         )
+        
+        
+        
         self.press_a_image.set_dimensions(new_dims)
         if not self.CONNECT_WITH_STUDY_SERVER:
             assert len(self.layout_file_paths) != 0, "No layout files."
@@ -1151,6 +1182,7 @@ class PyGameGUI:
             self.env_config_selection,
             self.item_info_selection,
             self.smooth_paths_selection,
+            self.visualization_config_selection,
             self.agent_selection_scroll_space,
             self.number_bots_label,
             self.tutorial_image,
@@ -1219,6 +1251,7 @@ class PyGameGUI:
                         # config_elements
                         self.env_config_selection,
                         self.item_info_selection,
+                        self.visualization_config_selection,
                 ]
                 
                 # only show fullscreen and quit button in debug and not arcade
@@ -1909,6 +1942,10 @@ class PyGameGUI:
         item_info, layout, environment_config, _ = load_config_files(item_info_path, layout_path,
                                                                      environment_config_path)
         num_players = 1 if tutorial else self.number_players
+        vis_config_path = self.visuaization_config_file_paths_dict[self.visualization_config_selection.selected_option]
+        if vis_config_path != self.current_visualisation_config_path:
+            print(f"Loading new visualization config: {vis_config_path} (old: {self.current_visualisation_config_path})")
+            self.set_vis_config(vis_config_path)
         
         seed = int(random.random() * 100000)
         creation_json = CreateEnvironmentConfig(
diff --git a/cooperative_cuisine/pygame_2d_vis/images/moonlander_assets/coin.png b/cooperative_cuisine/pygame_2d_vis/images/moonlander_assets/coin.png
new file mode 100644
index 0000000000000000000000000000000000000000..8a2af4b47f01ef070ef913727fdc85de7ea2a194
GIT binary patch
literal 2059
zcmeAS@N?(olHy`uVBq!ia0y~yU@!t<4mJh`208nVjSLLzmpok@Lo%G#MxD<O36-ej
zU#5G);H=1`u#DcLP0R<SlBdp);AC5@ZD?4k$ekR1Y-wkGgLJHf@Z)2>2X{;mm~zBT
zKw4PXDMn+_MX8J}i-I&tFJAk%ZSJkYbK2=u_sqZVtGxWlZ0`4W>)+SdpZk8dTAHoY
zj+^=JTnVGVIrZgfc^^M2ocYc(SANg4S<4n03X4piwtChl_9Auu{dT1?yw1yaTXXCG
z`Qtt}DWsJDy79pqI}fcrnD2gnj`Yk*|KsV~SRI{D$6j3CI`OAha9zFGj6GJ;Z)|SZ
z?A@*W`<(yt{Cx`_Z_7LPw(?i=O5Vnm(kIStj{Q6R+@F6stc?@?pIgUrx?Z34=I?DT
z^A)#jdo24mZQc61`*r?i5pvO-M!B<-{?7lsA!NHv%<^@O1*;o_v-7rWP4@lG!s30v
z=ydVbZTIHIzvX9OX#aS$^C!m)tpwYKAm*KR8@_kS`|-v%7@U4ux;-Vl^`Ub>$!Zk?
z<H?2Fo0l>yTWc%tTmE(R#07B%D;sLQ6-69k>bJQs`sdT#jDq|`_bn1F?W`g!jgFW3
zZ7++)SQ*DWO}iGR(7Nt$IeW6XT5`e#ecc7K95|TPEtc9Ww&A^iUozX{pDR=o6OVD-
z+IVcf^W%w|UTySK5@@(lUUJ@hyIz0om3y0&FG(=%zBXC2TIQFt!Y1*U-gnKF3~gd|
zKVGa$_;D)2M7Lo@>ZPWSOpF|t)VA>QIvQMOJ}7A(%YI8M_CZ^X!khkhK6e#|S<9~F
z*VxN#=91*~a@Lu|G3m}3&K89gwLZ@_v$QN^Q@n8Kk`%Y528Z+7=nVlayTlJ(f1DPl
zuz{(_dD&LO2TojVvzh{~*le-0-Ydbl`B0FgRUcEb&Hl&IIzoT<%7$eyYY3d#D6BpE
zu)&&7@3j=JNzD*&_#iUxjwmN@<JXO|u9g356Eg6->w9;q0|OI-M0!kn*!RF<9uCI@
z12Hv*t9*6Q3TAl;Q*+j>Op^U&^?%ozrNtT7j!Q84bxr4*5VUg3%KiD5>%AstrA4)H
z@A`8$T~hQ?QxRvDqibB?IyMWjo^<B@Of1)K{10*ONm}`H;d*!F?M(~J8g^=|P565^
zs{8#k8xuDjfn8EF1>cJKusoT+ng7ZDpEVgxH%0H8bZqS4XAqCoXXq<86mOQRopJob
z?8fzBoee8YL!`qhGGzJx#LMprne?qzr0>1R3=tpkB31WTXJ&hysa4Xyf7``w+32|X
zN6Y5>iGr&`eKTIHmr~n0DIlhjC0S%jtTEq{?#Da-`TO|^c3gGSo!(@+z*6OK*X$|G
zlTEztt8)h>ER7Vsz}~SSg7<+f+mlDpXMcW-U#`Amz61LwTj4pSs^JORslV1_=q~yq
z7wIFU@Vr6k<)u!si!3WVJB5EASTJ|vLZ+fQsftliuQgZ~eK~a_K;Vq`AMb!IUK|P<
z?m8S-1<quyJ=E!N_FLerD|-Zs7jCtUe7d>suzQ|#Nv`j<S8;*@&-hnImbm;|@GaI=
zVV|bNF7~CttbwmHHs`gPalI?@onCF-n6aTq$>$w+nVS2b(v1^Wb3EBIZDR(fnkU<q
z)7&|Gj>L5Dwr6cVnd|G?l`4Hdc3o&-+ws<zj|+QT1ZM4C6K$z`sFkrTa*E^esuR<m
z&ybj{EB|}Pfem*)H2QC1J9ec<@b2G4BZf=Ayf&Mk7T4VxyKd#WBe5#`GR=<f*5Z4A
z%ExZf!lg;;S`(f#ue~kBu5k4%L)UL^mR<g**DVO0b<|3lG4S%%H4G^t8SOJ;Opi7h
zu4F0PIY}<)#k84W7Y(;gN(#N0ahmti`d`cS7A)P{e=sNEw&{tabw3h(3Tk7m!xk-Q
z^qgpIc#Pv#%x{6YGPO05a=A;wN|nR+IoUHQu8dI4Y--{0Q|J+9+naT<X`{oHH)bzP
z9e)*dm&R^dk~ck*Q-JrbwIB!EkCwu>S3M6#O}@FQwY@ZSqxx}&T~DvhUAbOr+idOU
zVbkq=%r03J8Vc^8c($fp%;s9)s_To@Uf+tjFSk-Fy6~-pz&wpjf#(xqeI~JZtxbBZ
z5r6LDCDvq<X>Vk{MXY#b!LiY)w{ZPAi48OFGF^OdL?V3Z0i%1{H!3;qDC_gQ&gZ8v
zgU9J{;><($&#3CC#a6nmX3#tLQ^_&^e%Q_%-KQL5B3%R49eJ$acVN@J_gW0hlk(DT
zdh4d}zpTld!Q!Sl^KtN8lPCkOrrcfC#j^8v9k>xJ_U?XZb~a<?OQ-PTqOI>;?p1f3
z-Loix<B;5ahl=a_Ty>8b9h>(d+px1TV%rUCA@Sgqm*zO=?YaBs<CCc`#YK7ge@t$=
za6BhVFx>IguIj{3KGo|ZJ|FWszk5b}*4@+%3seL(=DxluK9l=x)Vv3=<=vTj?!k8z
zjrLzS`{pA5)|~J9SJd*Cyy3KGyyeVZDk<)~vtzNE0M8}vz^fwP^>)c`{9Q9!LRZ4}
za;Ve8zmGSDzq>Cf_vy}0&P5>(lWv^(wI}QJ)PuR-w%t-)_@;Kl*R9ibp4qwV&+qGp
zl-*bTUwD*f4R=S>@poFGvwy7p-j(xNw7+Ui*S@>$w|;**tt&peO#iRww-~)$F(>O*
zOLs)E@2}jyPF-P}+vn5_Hq&=nALCv6zlz1>W<<!GIX}IQyGZ@m<%p(FYot3^kCf}$
zylrIr-O&1_@<+z=eK(iJY}~N6)au3AsolRXAN|yt{^h<{=6qh}jocF@Cb4+@&X>$|
lTyS5OJ?Q+S@=fpm7rZs@-ErO~oPmLX!PC{xWt~$(6967$$+Q3f

literal 0
HcmV?d00001

diff --git a/cooperative_cuisine/pygame_2d_vis/images/moonlander_assets/comet_master.png b/cooperative_cuisine/pygame_2d_vis/images/moonlander_assets/comet_master.png
new file mode 100644
index 0000000000000000000000000000000000000000..4fa35db6c1a9a0d5cfbb04f17f924d30c81fed63
GIT binary patch
literal 3504
zcmeAS@N?(olHy`uVBq!ia0y~yU@!t<4mJh`208nVjSLLD<(@8%AsNnFXGQjehKjX?
zH}<qT7>a03o?^r;*4)6vyG6gDBXEj?@S6q}o;?yOjB){nrzX!;c*yA6VB%@6!X}&}
z>7yLL!*xm|YUWxdmYFHbL^K|J`zQ8w{+sI8VgY7*Px#)gK6kTvef{6;TfZkHhfewX
z`}_996Z!l9ntlK9@bKStvAeGoe|vNDsCnL<4Ov%LMXnB8yQb>vtFV}u9Orhvt)kjt
zHzxbrWw!IlZi?QX_cKer=EFgIb>CSgZSTFO>12NX^?LpEkicH9{^x%ta+>AbC@8wT
z%(r-3?rk%E*6MF>GH09TZxfHNxhQ3tm11dW85R_jWSVv5!^`TguTt;V|KD5s_SV-=
z%KbK<w(l%{er{dx^1j^r`|f&qd%t};Jznp}o6YC{sT+i{1%-rcQHm(3_>ryb*0W(>
z?eAOb_y5aEKR2iF^!<Icx65+wPrQ--U_s-yy1!M@CK(g{emKlu_qS7h-iG=0|8CAQ
z&wr<*8?oVlq|efz&};78p_vMKqIZ_=NIu?oZB3+c^_Pq8Ys1z?ZOeaer~M`@W4c~k
zk=m?h=36(XpNs09WxD2#+svhh_RsmCzdv%{`r9jm)o*X8Yu=iB{o1+t_W8NDx0P<M
z`TzI(+c!5i+XSiZU1gH+^wiXPDa)dii%(8YUhCM*HZ9ux`o7=qtiyR+xo?T6=p0F4
zaWZEte)eVNlbdmSt8e<-|4o@|U0$|WA$Ymp)^2hAFwQfzws&?EJ{IAr`Et=+?9n0S
zs<->EM>#q+?sl2oB=CP_!Z88qgSIg<S1%2J+bSNn;b^zGwe*RGM&>mc&Vj8P&SXzX
zKX3E7XQF{f_~qsP>xHbpvj|P`=9RO#!Nwzz@bl--ysi5Gzb@UH`S|2%RyLNl`$tue
zPP?vf{#JR;`}6raw=$al*__ae*^yBC`pVY)eLvMCxt%3g9^RZ~ntkiz<Kt^xyTwA2
zp09KG^yyPjW9$BRcXxk(A5j1KnXh$F(UU%*o}P#kYo})~4fkFf_BJZ4YU!0-UtgrL
z^UIYS^Em$X)z!_*=T)8B|Nr0b_uEoGi&a<Oy1lLHYu4*aOS@&Q%g!_&;NWa{e&oZC
zkB_$|v6_`G3{q(L@!>H4+uQm3zvf)o6`CEc<9q60%>Kf=m-SA4dw2JCuvVeSqf_$7
zPig$V8XkZ1>FMczceZ_%j!OLQw7-A1&<CMwFE1=?{`={)zSyO$%(E;CAH^O_VQsQL
zJKz4k<}ssQY13)@!x%2DjW$1T^Y!)h^41M%k(>wLpFVZ!&Z*`8^S2#XldgW}%cZ5>
zoDs*ed&G7WJ?%2hxpBd7j>W_sH>LCU1ZvJ$<G_)pBVg2i+;zpHdB6K*ZKpA0-dwdb
z-d$72e*(X>*_l2W%SQ)xmA`+dFroDIwVQ|e?Q=BPrCCf%SszNSs+TEFl2e;GeQRRt
zmzpmxF1~vB`>l`sY6g`bnOW!NSjx@FvR-oG#l^*MKR=y+TU$A<(B6H;g^=7O8#Y`x
z*nafQtE;Qc3LZG{=Ol4YZ`wQ8y8M<izwHzGm~Y4B>%Y9;nti=&Ljwatw(xX@i|huM
zm-(KaHS2N%i@8@|>PC&OB*)H6O5905eGV>-5D@43JAF6vn+MO&xc*XGz<fI|vE{)s
zF~RsAMuE1#9s_yFiO&lSpB-3nW#$KS-|tV{B|>t4e0aG1#NQw5H?ehj9P5#M{EkIp
zql3(Y?*;`A4lw2i%GLiVWM>t4cIdililIo-ygPSd%*7hdZEgsWP4YW7&!+NH`?mc1
z_YRt5UOJL?c2;QPRF)H${q5&IVvJ(>r6Bh5;^Ow`gvZBv?+H8-`}^yww=(kqjpnBb
ziYAlq?PB0q`p9HeV~S~nQ)CRE+0h^y%g;<M>)KumZtki6eAfIj)BVPlqo1eg#a79^
zHj;Sy@xH8eS;Yj?+*?;xTZ%Q;O_}`Qw1z6fMkNNufIl`Ln`b`?V^|fveck4hHg9ij
z^={j&$K!n>>&(+z?bX6+J}Wp>L{?pi&foi#A%KgOBVg9y0RKDnkuyrp*v2qE{ZaSo
ziYE7Oq5b_!Hy!1BcXLy!_x5*RUtgb_du`&x4p|npjaKJvzuidAGkG*sBFVNyNZ{YG
zRwH$dVB<I9>-wckwLbduzPh~JpNUI*(F!{aMhBLq98pGLIiVtp7HA4Ss1tg`b5W*Y
z!lWe`oNY`YXI8yi${4vdEA;P7<McSCt&h4joDGsBn|ik?g~>7ed8R+P-FKS4%O4L0
z=Vy(+Yd9~b7~hz1sNsl(*IOy?0PP2*zK#A(3Veo4Y#z);$;W#3Fg*KnV`H-Ihmgwa
zYa$=Ja&K4QUbrJd@w333?Ca~k>Ks~<GUdjq9jziqRaZR?+5A|&Pd%Z~wc%)!qU7N=
z??<gW?tC@6YsTvlVPKkn&*ponh-2f1#-gJ$XDm8qtD|TZFlSoI>uYP*`TR`Nh-5eW
znPAy8!?IFszvsTGGP<6-Ses&+xF=<FsLp89e5;b!k+i$|d!D})!|9ZxxA%R&SAD*A
zCMSbpU?UUDySA-6+%_DNIp3Z$JxxO_RnSPLje#M#P?x2wf~{d=g37a_qJmtDwd{D8
z7+srZTP<W@qaG%6;!rF1--8k?!paX54v0Tn&@iP(TQkjL=ilGo{h6CTZD`%PQA;e%
zCak6K+xI_ej2@PCo7E38oT^!K-X_ccQC!c?!uMPbV!BaJZ25)We~Q|k_jXE>Y~;r~
zJB!~P{t_T^eEt4^w_^Plz13>$3+f3jz4phbg=y|jM$YOx!48k--RZVwp44F!%c?NH
z?$^r89<zHlSnQ9lerx*QcbkIXg=rt=vX-PB&F>ZL-mzef=jWL-TW3U@I+k_@OWbfy
zXyfwRIAwavVwJ;d6WUDT6y2JfR))vdnr_rMbD^|J%KHJAh-wp0_r$)ZLIFMui$cyU
zDcTpsEqi7ohp7@@Q7^Mi*W-O04k|AggV%6N%oR;3)m>$F?Wk#iCWGz&KcBbd-?!tg
zoe`eTB*I-Zzo)fH#MkBb2Z;$)x)KRG3MYzo$hrvzZ;_X9mfdC~{P_^eB3`NcGrfJ5
zvRWG|B|7~H=)PdV^yp^#{IA&&Y+?+()w<E!&KQeyo!n|7zW-;3<GRQHkMM*&oifRc
zqdA04iS59x3ip_BEmoo9ogz()-yX2+>~ktKJSiSu^Ds~Fy6_{8?=BN(s2=+eJ(oFe
z&08I}7F$Ed7@;=}DY373HZra@D&c7<)Qq_3aw_4@g3^f(=07Q^iO(!LJx%wV*v#%i
zmbFt-10S`{5Rf=#EH?dXEAwdy&bF(|1=S_kJ}zD+@Ia#F{Y|53>%-Tjbp18h^4iBU
zA+GABYWdRV^QzB@Cnx%`eT;2XjLSI5c=+#)qbK6NB>gbi*r2qEv9tA%_`KlI<B#6_
zJUQuB)Pr|f|1~$fnR%>NdU}FKgN7eN(**;sgw_PNN+z>3w(Q9_e{`=;era)ck-}X=
z&z5(ND`Ts99S*bTxh`9@J=lB66lwEmTXYSWFK6zZCeg`kSEl>!vDXi+>QYv{SR=2Z
zLw+BGe(WxLTXa2oUrptsI1N9WjSHI`Bc|RKU@lUB)*#5kDZN9rz5L@ghqln_DZC!n
zECOX27!J<VWRVwZRaJW~%gONb#NHXq2L+Ba`FUNLa!6&ZwU5TzPvXg|ocj)H>`t9$
zcT8Q-bB;#D$J0Mp1XeAa$sZQl)1fNsB%r_LhfqY9x!cU1*Om>Z)@DXNy2fVE5O8US
z^n@1CqnzuSqc!p;UtJ*UnAsF0_Q+haN8EnZfmcFPcnukjO`McuGdYt%)2(va{Vv^k
zXUoLqR60D*l(_h9ih>KHT3V2iG~?OVCNAuGg<^+~3Os6LIClTB@g@l$)_UFj4z-=U
zjR&uoPF8h3KJRag_ACR&!&@zqH!Q!v*6o~W#P2F(7UA*6pzjPD$1AUAWz&8$G#9R_
zj`^9KATjYr=h97+f1dJtEbC*&%8^qu{aWky4wWVg;iKJ5Z6%x!EL4*PWRnitls4WH
z>5fZga9j89ob~$;9^ab3d#&*OxUBPNvO&b0ICn=6hNQ@{GONmPrG&UOXJ<+{$zJlC
zxXWqlvbYINfz!6mU~gzhkXv{B=-0gWo|_i^elpqrpWwab|E?bw9pgQ^#r3RD=MGM<
zmC;x2THmz!PxfMTcqCx0;j_m;ywvS2PeaLsg_BQJ?|rlLwe;!_c1lY(>#pf)<v5Vz
z+V#ufs4L@FuMGn815XNkoH!v!Z{zY<4V{kCu9Rmhlg#vYJ-0q$a3bFL#e!bX7qgQL
zs^%M}oDf+5rQ!JjK7kaSzBtaP7|!b_-?UxrH#Bct|De71!+xz7eeYL3x0Tm$f4@G<
z{@;(}@D_%pTf<{JUNc@(6I8HqpRL$=@P*WGLCy7l`SLmnGo%jJ`(+5lJ)gT;W!mJ^
z-??3wFKv=!6}&&~!Dk`Ei7OkdBDSiX5!`Ov)4a35!opOBcP)=$sLa0mmS6UEhlp|n
zFU=6R`TVr&iIkaF&2QghTd*WZqCxkqoMjRd1G}h7XZiiw@|uFxzKLrWKb&d({{26G
z2ca?s7n>7TOO)&U&uu*u#wvd4-NaZ`*U8DtG=rD<@U4ITPx#xH_Cp(r8IRb`yfCZ2
zWunjRB^FN?JQg+B`zBoRG)vSxugQ~-On7&@_1yxUJ%T%A)Sk<1)oIxAIbP#kupG+-
xt^-E;M;vyF^PQFdv5UXv&~K9m%=K^fJD=aN`m9RXHU<U;22WQ%mvv4FO#ro{m%0D|

literal 0
HcmV?d00001

diff --git a/cooperative_cuisine/pygame_2d_vis/images/moonlander_assets/spaceship_master.png b/cooperative_cuisine/pygame_2d_vis/images/moonlander_assets/spaceship_master.png
new file mode 100644
index 0000000000000000000000000000000000000000..587d8edd7c898051b9302331b1bebfb09dcbeacf
GIT binary patch
literal 1285
zcmeAS@N?(olHy`uVBq!ia0y~yU@!t<4mJh`208nVjSLJdXFOdTLo%G-&hpF<aTIC0
zuc4y!&dt?EMNrhW*szIn(V~eBzquGcPU)WXF-@sT$lq&`x%vxLbwQTzqDM5el2{(A
zb}dRe<LJ`6yMr^sy8i1PFW;qE%e3#x@<vRb_i|m;``WMjqjseUi8xBuecJs!`;qn{
z(a%EXo~^V0)K;&tFy3_Wo!eUHoJ^;cSxE36v-XlZtNEt*`;vAc>lNphFP&SQzC_xh
zdCUG4U%r+4EL`fk_sWJJU8XCRS+BN<c33o*mFaC>srZF|o3@A8J&t?ugDL#U0*0c;
zXDgbF4=hmFaOqio-19YhLYb2SkFhLvo?@rH?A)%4oa;`PP51w6Vxhp0u$))cn<YR~
z^{A+WZ|m;0l9w)A_Fxbw)l^;Qo4S3oReFG9h{VDkrj+JH4pE2g&F?ohAK+XrUcPfy
znwCISq(0ATyUVwCI~{2MERiS>V;GiWU^RhLflKv9y_iQ)b<i1;mB}mjH0;t^w%~?m
z@o~>p+N)nK?_1|`VT#ewnGa?gO!E2F{PolHs+T6&U00n955Bm_#Ppa^xRKLs?R@Q}
zS0oF*-f&(NsrA%fM1yHbO`>I}&WZ`<p%Z@HUt4!{sqNp8E}<iA5ASM;L>6U4Ozr7+
zELi;3QF`nB{P)ZUecJ*ZcWmX=5a(Ha(5z_XqUIS4H~5+jT3%$VPurz%&oWPSqmh~b
z=a(N{>jTnOGfw!F{W>-OrSa8kci+E!+#ff~U{iyiiQlBJ6(1hERe!DA`J*$JS7gR+
zuWMKB8Cn$kYWBT4ewg>=vl7nFiO)1w3knN7U!R#~)cE470beu2hWDN4w|A5X37n4m
zrSK;};?#jDIj=>yCt2-%rs^Hu(0ptD`y*OskFs4dN-|=7X>e8l_?K4|D_b6%IU#l6
zj&{~9mPSSfadCk>VHOWr=c+l8T{o(1?w>K)Up_tK*43-3>((h8J^F9n>%@bJt6w;V
z{o2DVzvg+<O1a{OUAuQXH;RagUfjCAx>vzbfl2ewwOoV3CYII$rj44+0wQ5YRkuzE
z3t)B)Kf-!Q@(7DCdx!Rs28WUvD}u8X1en<_CN7#)$Id0lb!19fqSz4wfrXAG5;?Xp
z-=bp7bzD>w44U>%<8*p6WA^fki<d9Xy!R&GrF8w~ZIAD$UAOKpoq9!%B}(A4RJPrV
zm+v2lt1mGxdC6?~=6=dSHvQU!?cW}s+P7FmCgRFlPFeR1W{#q>H+k<ZH5WBn`+-Z_
z@k?BQ!-5mDR_2yuKHhnuu*K}cty`}W=a;7aVKq9Gp|FO{Ip^1wz6}Opp`oo^sv*fo
zPAL3Xv;I}hmy<d*SGwLa91N3tI-k+eKrOUjeqss75|P!7RrRrgVXE>yHtOekx-}G)
zX4hR6xqQ6K?%HBG=^Xnn5`oOQnVEv@>}C?8PCYAc-8%8dJ<a2N<?Tink?#5Lv@cyT
zYTUq7aV+@r1m=ZbvifIw>Na#HiOQR}Zn!ji&d0qPVMTZAbdp_c3)Y>V<FiV=y>LnR
z8U?1N`m4VA3X;$4J^E^TeFaytZTqAVptktbgg}<`gwN~me`Yk(|FnMj`>xZ6MW_1N
zTR)#=awUDEhLWR1lkKv^HW?1S{jOD-T?rf9Ki1i0yylSk8m#JOnsj&3hsce)`i?Ah
zTcfas>;FC>^&|BY76dD;Zd!dhep<|%;=A_g3hNZ#|MtveU|?YIboFyt=akR{0C~hr
ArT_o{

literal 0
HcmV?d00001

diff --git a/cooperative_cuisine/pygame_2d_vis/visualization.yaml b/cooperative_cuisine/pygame_2d_vis/visualization.yaml
index cb74d082..05aec531 100644
--- a/cooperative_cuisine/pygame_2d_vis/visualization.yaml
+++ b/cooperative_cuisine/pygame_2d_vis/visualization.yaml
@@ -28,7 +28,7 @@ GameWindow:
   game_border_color: black
   background_color: lemonchiffon1
   # optimization
-  cache_flags: [ Counters, Background ]  # [None]
+  cache_flags: [ Counters, Background ]  # [None] #
   reduced_background: true  # false if moveable counter or updated background (coins etc.)
 
 Orders:
@@ -39,6 +39,10 @@ Kitchen:
   ground_tiles_color: sgigray76
   background_lines: gray79
 
+PlayerHighlighting:
+  controlled_player_colors: [ red, blue ]
+
+
 Counter:
   parts:
     #    - type: rect
@@ -304,12 +308,14 @@ OnionSoupPlate:
       size: 0.3
 
 Cook:
+  add_circle_color: true
   parts:
     - type: image
       path: images/pixel_cook_masked.png
       size: 1
 
 RobotChef:
+  add_circle_color: true
   parts:
     - type: image
       path: images/robot_chef.png
diff --git a/cooperative_cuisine/pygame_2d_vis/visualization_moonlander.yaml b/cooperative_cuisine/pygame_2d_vis/visualization_moonlander.yaml
new file mode 100644
index 00000000..87249014
--- /dev/null
+++ b/cooperative_cuisine/pygame_2d_vis/visualization_moonlander.yaml
@@ -0,0 +1,491 @@
+# colors: https://www.webucator.com/article/python-color-constants-module/
+
+Gui:
+  language: "de"
+  use_player_cook_sprites: True
+  show_interaction_range: False
+  show_counter_centers: False
+  press_button_to_continue: True
+  start_in_fullscreen: False
+  force_debug_elements: False
+  force_always_on_top: False
+  skip_tutorial: False
+  audio_level: 0.1
+  default_agent_script: IPyHopAgent
+  hide_agents: [ ]
+
+GameWindow:
+  screen_margin_proportion: 0.15
+  min_width: 900
+  min_height: 600
+  buttons_width: 180
+  buttons_height: 60
+  FPS: 60
+
+  order_bar_height: 100
+
+  game_border_size: 1
+  game_border_color: black
+  background_color: lemonchiffon1
+  # optimization
+  cache_flags: [None] # [ Counters, Background ]  # [None] #
+  reduced_background: false  # false if moveable counter or updated background (coins etc.)
+
+Orders:
+  show_ingredients: False
+  show_source_ingredients: False
+
+Kitchen:
+  ground_tiles_color: black
+  background_lines: black
+
+PlayerHighlighting:
+  controlled_player_colors: [ ]
+
+Counter:
+  parts: []
+    #    - type: rect
+    #      height: 1
+    #      width: 1
+    #      color: whitesmoke
+#    - type: image
+#      path: images/counter5.png
+#      size: 1
+
+EdgeCounter:
+  parts: []
+    #    - type: rect
+    #      height: 1
+    #      width: 1
+    #      color: whitesmoke
+#    - type: image
+#      path: images/wall.png
+#      size: 1
+
+CuttingBoard:
+  parts: []
+#    - type: image
+#      path: images/cutting_board_large.png
+#      size: 0.75
+#      center_offset: [ 0, -0.05 ]
+
+
+PlateDispenser:
+  parts: []
+
+Trashcan:
+  parts: []
+#    - type: image
+#      path: images/trash6.png
+#      size: 0.88
+
+#TomatoDispenser:
+#  parts:
+#    - color: orangered1
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+#
+#LettuceDispenser:
+#  parts:
+#    - color: palegreen3
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+#
+#OnionDispenser:
+#  parts:
+#    - color: deeppink3
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+#
+#MeatDispenser:
+#  parts:
+#    - color: indianred1
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+#
+#BunDispenser:
+#  parts:
+#    - color: sandybrown
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+
+Dispenser:
+  parts: []
+#    - type: circle
+#      color: black
+#      radius: 0.35
+#      center_offset: [ 0, -0.05 ]
+#    - type: circle
+#      color: gray83
+#      radius: 0.33
+#      center_offset: [ 0, -0.05 ]
+
+
+#  item_offset: [ 0, -0.05 ]
+#  item_scale: 0.9
+
+ServingWindow:
+  parts: []
+#    - type: image
+#      path: images/star.png
+#      size: 0.8
+#      center_offset: [ 0, -0.02 ]
+#      rotate_image: False
+#    - type: image
+#      path: images/bell_gold.png
+#      size: 0.5
+#      center_offset: [ -0.2, -0.05 ]
+#      rotate_image: False
+
+Stove:
+  parts: []
+#    - type: image
+#      path: images/stove3.png
+#      size: 0.8
+#      center_offset: [ 0, -0.05 ]
+
+Sink:
+  parts: []
+#    - type: image
+#      path: images/sink1.png
+#      size: 0.85
+#      center_offset: [ 0, -0.12 ]
+
+SinkAddon:
+  parts: []
+#    - type: image
+#      path: images/drip2.png
+#      size: 0.75
+#      center_offset: [ 0, -0.05 ]
+
+# Tools
+Extinguisher:
+  parts: []
+#    - type: image
+#      path: images/fire_extinguisher.png
+#      size: 0.85
+#      center_offset: [ 0, -0.05 ]
+
+# Effects
+Fire:
+  parts:
+    - type: image
+      path: images/fire.png
+      size: 1
+
+Fire1:
+  parts:
+    - type: image
+      path: images/fire.png
+      size: 1.0
+
+Fire2:
+  parts:
+    - type: image
+      path: images/fire2.png
+      size: 1.0
+
+Fire3:
+  parts:
+    - type: image
+      path: images/fire3.png
+      size: 1.0
+
+
+# Items
+Tomato:
+  parts: []
+#    - type: image
+#      path: images/tomato.png
+#      size: 0.8
+
+Onion:
+  parts:
+    - type: image
+      path: images/onion.png
+      size: 0.8
+
+Bun:
+  parts:
+    - type: image
+      path: images/bun.png
+      size: 0.9
+
+Lettuce:
+  parts: []
+#    - type: image
+#      path: images/lettuce.png
+#      size: 0.9
+
+Meat:
+  parts:
+    - type: image
+      path: images/meat.png
+      size: 1
+
+ChoppedLettuce:
+  parts:
+    - type: image
+      path: images/lettuce_cut4.png
+      size: 0.85
+
+ChoppedTomato:
+  parts:
+    - type: image
+      path: images/tomato_cut.png
+      size: 0.9
+
+ChoppedOnion:
+  parts:
+    - type: image
+      path: images/onion_cut.png
+      size: 0.95
+
+RawPatty:
+  parts:
+    - type: image
+      path: images/raw_patty.png
+      size: 0.9
+
+CookedPatty:
+  parts:
+    - type: image
+      path: images/cooked_patty.png
+      size: 0.9
+
+Burger:
+  parts:
+    - type: image
+      path: images/burger.png
+      size: 0.9
+
+Salad:
+  parts:
+    - type: image
+      path: images/salad.png
+      size: 0.8
+
+TomatoSoup:
+  parts:
+    - type: image
+      path: images/tomato_soup_pot.png
+      size: 1.05
+      center_offset: [ -0.02, -0.1 ]
+
+TomatoSoupPlate:
+  parts:
+    - type: image
+      path: images/tomato_soup_plate.png
+      size: 0.6
+
+OnionSoup:
+  parts:
+    - type: image
+      path: images/onion_soup_pot.png
+      size: 1.05
+      center_offset: [ -0.02, -0.1 ]
+    - type: image
+      path: images/onion_cut.png
+      size: 0.2
+      center_offset: [ -0.02, -0.1 ]
+
+OnionSoupPlate:
+  parts:
+    - type: image
+      path: images/onion_soup_plate.png
+      size: 0.6
+    - type: image
+      path: images/onion_cut.png
+      size: 0.3
+
+Cook:
+  add_circle_color: false
+  based_on_movement_data:
+    left:
+      - type: image
+        path: images/moonlander_assets/spaceship_master_left_turn.png
+        size: 1
+    right:
+      - type: image
+        path: images/moonlander_assets/spaceship_master_right_turn.png
+        size: 1
+  parts:
+    - type: image
+      path: images/moonlander_assets/spaceship_master.png
+      size: 1
+
+RobotChef:
+  parts:
+    - type: image
+      path: images/robot_chef.png
+      size: 1
+
+Plate:
+  parts: []
+
+DirtyPlate:
+  parts: []
+
+Pot:
+  parts:
+    - type: image
+      path: images/pot_smaller.png
+      size: 1.05
+      center_offset: [ -0.02, -0.1 ]
+
+Pan:
+  parts:
+    - type: image
+      path: images/pan.png
+      size: 1.1
+
+DeepFryer:
+  parts:
+    - type: image
+      path: images/deepfryer1.png
+      size: 0.8
+
+Oven:
+  parts:
+    - type: image
+      path: images/pizza_oven.png
+      size: 1
+
+Basket:
+  parts:
+    - type: image
+      path: images/basket1.png
+      size: 0.8
+
+Peel:
+  parts:
+    - type: image
+      path: images/pizza_wood.png
+      size: 1.2
+      center_offset: [ 0, 0.2 ]
+
+Potato:
+  parts:
+    - type: image
+      path: images/potato2.png
+      size: 0.7
+
+RawChips:
+  parts:
+    - type: image
+      path: images/raw_fries.png
+      size: 0.8
+
+Chips:
+  parts:
+    - type: image
+      path: images/fries2.png
+      size: 0.8
+
+Fish:
+  parts:
+    - type: image
+      path: images/fish3.png
+      size: 0.9
+
+ChoppedFish:
+  parts:
+    - type: image
+      path: images/cut_fish.png
+      size: 0.8
+
+FriedFish:
+  parts:
+    - type: image
+      path: images/fried_fish.png
+      size: 0.8
+
+FishAndChips:
+  parts:
+    - type: image
+      path: images/fries2.png
+      size: 0.8
+      center_offset: [ -0.1, 0 ]
+    - type: image
+      path: images/fried_fish.png
+      size: 0.8
+      center_offset: [ +0.2, 0 ]
+
+BurgerWithChips:
+  parts:
+    - type: image
+      path: images/fries2.png
+      size: 0.8
+      center_offset: [ -0.1, 0 ]
+    - type: image
+      path: images/burger.png
+      size: 0.8
+      center_offset: [ +0.2, 0 ]
+
+
+Dough:
+  parts:
+    - type: image
+      path: images/pizza_dough.png
+      size: 0.7
+
+PizzaBase:
+  parts:
+    - type: image
+      path: images/pizza_base.png
+      size: 0.9
+
+Sausage:
+  parts:
+    - type: image
+      path: images/sausage.png
+      size: 0.8
+
+ChoppedSausage:
+  parts:
+    - type: image
+      path: images/sausage_chopped.png
+      size: 0.8
+
+Cheese:
+  parts:
+    - type: image
+      path: images/cheese3.png
+      size: 0.7
+
+GratedCheese:
+  parts:
+    - type: image
+      path: images/grated_cheese.png
+      size: 1.1
+
+Pizza:
+  parts:
+    - type: image
+      path: images/pizza.png
+      size: 0.9
+
+Water:
+  parts:
+    - type: rect
+      height: 1
+      width: 1
+      color: blue
+
+Lava:
+  parts:
+    - type: rect
+      height: 1
+      width: 1
+      color: red
+
+Target:
+  parts:
+    - type: image
+      path: images/moonlander_assets/coin.png
+      size: 1
\ No newline at end of file
diff --git a/cooperative_cuisine/pygame_2d_vis/visualization_no_caching.yaml b/cooperative_cuisine/pygame_2d_vis/visualization_no_caching.yaml
new file mode 100644
index 00000000..28dcb2c5
--- /dev/null
+++ b/cooperative_cuisine/pygame_2d_vis/visualization_no_caching.yaml
@@ -0,0 +1,493 @@
+# colors: https://www.webucator.com/article/python-color-constants-module/
+
+Gui:
+  language: "de"
+  use_player_cook_sprites: True
+  show_interaction_range: False
+  show_counter_centers: False
+  press_button_to_continue: True
+  start_in_fullscreen: False
+  force_debug_elements: False
+  force_always_on_top: False
+  skip_tutorial: False
+  audio_level: 0.1
+  default_agent_script: IPyHopAgent
+  hide_agents: [ ]
+
+GameWindow:
+  screen_margin_proportion: 0.15
+  min_width: 900
+  min_height: 600
+  buttons_width: 180
+  buttons_height: 60
+  FPS: 60
+
+  order_bar_height: 100
+
+  game_border_size: 1
+  game_border_color: black
+  background_color: lemonchiffon1
+  # optimization
+  cache_flags: [None] # [ Counters, Background ]  # [None] #
+  reduced_background: false  # false if moveable counter or updated background (coins etc.)
+
+Orders:
+  show_ingredients: False
+  show_source_ingredients: False
+
+Kitchen:
+  ground_tiles_color: sgigray76
+  background_lines: gray79
+
+PlayerHighlighting:
+  controlled_player_colors: [ red, blue ]
+
+Counter:
+  parts:
+    #    - type: rect
+    #      height: 1
+    #      width: 1
+    #      color: whitesmoke
+    - type: image
+      path: images/counter5.png
+      size: 1
+
+EdgeCounter:
+  parts:
+    #    - type: rect
+    #      height: 1
+    #      width: 1
+    #      color: whitesmoke
+    - type: image
+      path: images/wall.png
+      size: 1
+
+CuttingBoard:
+  parts:
+    - type: image
+      path: images/cutting_board_large.png
+      size: 0.75
+      center_offset: [ 0, -0.05 ]
+
+
+PlateDispenser:
+  parts:
+    - type: image
+      path: images/plate_dispenser.png
+      size: 1
+
+Trashcan:
+  parts:
+    - type: image
+      path: images/trash6.png
+      size: 0.88
+
+#TomatoDispenser:
+#  parts:
+#    - color: orangered1
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+#
+#LettuceDispenser:
+#  parts:
+#    - color: palegreen3
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+#
+#OnionDispenser:
+#  parts:
+#    - color: deeppink3
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+#
+#MeatDispenser:
+#  parts:
+#    - color: indianred1
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+#
+#BunDispenser:
+#  parts:
+#    - color: sandybrown
+#      type: rect
+#      height: 0.8
+#      width: 0.8
+
+Dispenser:
+  parts:
+    - type: circle
+      color: black
+      radius: 0.35
+      center_offset: [ 0, -0.05 ]
+    - type: circle
+      color: gray83
+      radius: 0.33
+      center_offset: [ 0, -0.05 ]
+
+
+  item_offset: [ 0, -0.05 ]
+  item_scale: 0.9
+
+ServingWindow:
+  parts:
+    - type: image
+      path: images/star.png
+      size: 0.8
+      center_offset: [ 0, -0.02 ]
+      rotate_image: False
+#    - type: image
+#      path: images/bell_gold.png
+#      size: 0.5
+#      center_offset: [ -0.2, -0.05 ]
+#      rotate_image: False
+
+Stove:
+  parts:
+    - type: image
+      path: images/stove3.png
+      size: 0.8
+      center_offset: [ 0, -0.05 ]
+
+Sink:
+  parts:
+    - type: image
+      path: images/sink1.png
+      size: 0.85
+      center_offset: [ 0, -0.12 ]
+
+SinkAddon:
+  parts:
+    - type: image
+      path: images/drip2.png
+      size: 0.75
+      center_offset: [ 0, -0.05 ]
+
+# Tools
+Extinguisher:
+  parts:
+    - type: image
+      path: images/fire_extinguisher.png
+      size: 0.85
+      center_offset: [ 0, -0.05 ]
+
+# Effects
+Fire:
+  parts:
+    - type: image
+      path: images/fire.png
+      size: 1
+
+Fire1:
+  parts:
+    - type: image
+      path: images/fire.png
+      size: 1.0
+
+Fire2:
+  parts:
+    - type: image
+      path: images/fire2.png
+      size: 1.0
+
+Fire3:
+  parts:
+    - type: image
+      path: images/fire3.png
+      size: 1.0
+
+
+# Items
+Tomato:
+  parts:
+    - type: image
+      path: images/tomato.png
+      size: 0.8
+
+Onion:
+  parts:
+    - type: image
+      path: images/onion.png
+      size: 0.8
+
+Bun:
+  parts:
+    - type: image
+      path: images/bun.png
+      size: 0.9
+
+Lettuce:
+  parts:
+    - type: image
+      path: images/lettuce.png
+      size: 0.9
+
+Meat:
+  parts:
+    - type: image
+      path: images/meat.png
+      size: 1
+
+ChoppedLettuce:
+  parts:
+    - type: image
+      path: images/lettuce_cut4.png
+      size: 0.85
+
+ChoppedTomato:
+  parts:
+    - type: image
+      path: images/tomato_cut.png
+      size: 0.9
+
+ChoppedOnion:
+  parts:
+    - type: image
+      path: images/onion_cut.png
+      size: 0.95
+
+RawPatty:
+  parts:
+    - type: image
+      path: images/raw_patty.png
+      size: 0.9
+
+CookedPatty:
+  parts:
+    - type: image
+      path: images/cooked_patty.png
+      size: 0.9
+
+Burger:
+  parts:
+    - type: image
+      path: images/burger.png
+      size: 0.9
+
+Salad:
+  parts:
+    - type: image
+      path: images/salad.png
+      size: 0.8
+
+TomatoSoup:
+  parts:
+    - type: image
+      path: images/tomato_soup_pot.png
+      size: 1.05
+      center_offset: [ -0.02, -0.1 ]
+
+TomatoSoupPlate:
+  parts:
+    - type: image
+      path: images/tomato_soup_plate.png
+      size: 0.6
+
+OnionSoup:
+  parts:
+    - type: image
+      path: images/onion_soup_pot.png
+      size: 1.05
+      center_offset: [ -0.02, -0.1 ]
+    - type: image
+      path: images/onion_cut.png
+      size: 0.2
+      center_offset: [ -0.02, -0.1 ]
+
+OnionSoupPlate:
+  parts:
+    - type: image
+      path: images/onion_soup_plate.png
+      size: 0.6
+    - type: image
+      path: images/onion_cut.png
+      size: 0.3
+
+Cook:
+  add_circle_color: true
+  parts:
+    - type: image
+      path: images/pixel_cook_masked.png
+      size: 1
+
+RobotChef:
+  add_circle_color: true
+  parts:
+    - type: image
+      path: images/robot_chef.png
+      size: 1
+
+Plate:
+  parts:
+    - type: image
+      path: images/plate_clean.png
+      size: 0.8
+
+DirtyPlate:
+  parts:
+    - type: image
+      path: images/plate_dirty.png
+      size: 0.8
+
+Pot:
+  parts:
+    - type: image
+      path: images/pot_smaller.png
+      size: 1.05
+      center_offset: [ -0.02, -0.1 ]
+
+Pan:
+  parts:
+    - type: image
+      path: images/pan.png
+      size: 1.1
+
+DeepFryer:
+  parts:
+    - type: image
+      path: images/deepfryer1.png
+      size: 0.8
+
+Oven:
+  parts:
+    - type: image
+      path: images/pizza_oven.png
+      size: 1
+
+Basket:
+  parts:
+    - type: image
+      path: images/basket1.png
+      size: 0.8
+
+Peel:
+  parts:
+    - type: image
+      path: images/pizza_wood.png
+      size: 1.2
+      center_offset: [ 0, 0.2 ]
+
+Potato:
+  parts:
+    - type: image
+      path: images/potato2.png
+      size: 0.7
+
+RawChips:
+  parts:
+    - type: image
+      path: images/raw_fries.png
+      size: 0.8
+
+Chips:
+  parts:
+    - type: image
+      path: images/fries2.png
+      size: 0.8
+
+Fish:
+  parts:
+    - type: image
+      path: images/fish3.png
+      size: 0.9
+
+ChoppedFish:
+  parts:
+    - type: image
+      path: images/cut_fish.png
+      size: 0.8
+
+FriedFish:
+  parts:
+    - type: image
+      path: images/fried_fish.png
+      size: 0.8
+
+FishAndChips:
+  parts:
+    - type: image
+      path: images/fries2.png
+      size: 0.8
+      center_offset: [ -0.1, 0 ]
+    - type: image
+      path: images/fried_fish.png
+      size: 0.8
+      center_offset: [ +0.2, 0 ]
+
+BurgerWithChips:
+  parts:
+    - type: image
+      path: images/fries2.png
+      size: 0.8
+      center_offset: [ -0.1, 0 ]
+    - type: image
+      path: images/burger.png
+      size: 0.8
+      center_offset: [ +0.2, 0 ]
+
+
+Dough:
+  parts:
+    - type: image
+      path: images/pizza_dough.png
+      size: 0.7
+
+PizzaBase:
+  parts:
+    - type: image
+      path: images/pizza_base.png
+      size: 0.9
+
+Sausage:
+  parts:
+    - type: image
+      path: images/sausage.png
+      size: 0.8
+
+ChoppedSausage:
+  parts:
+    - type: image
+      path: images/sausage_chopped.png
+      size: 0.8
+
+Cheese:
+  parts:
+    - type: image
+      path: images/cheese3.png
+      size: 0.7
+
+GratedCheese:
+  parts:
+    - type: image
+      path: images/grated_cheese.png
+      size: 1.1
+
+Pizza:
+  parts:
+    - type: image
+      path: images/pizza.png
+      size: 0.9
+
+Water:
+  parts:
+    - type: rect
+      height: 1
+      width: 1
+      color: blue
+
+Lava:
+  parts:
+    - type: rect
+      height: 1
+      width: 1
+      color: red
+
+Target:
+  parts:
+    - type: rect
+      height: 1
+      width: 1
+      color: gold1
\ No newline at end of file
-- 
GitLab