From 681f629cd0070a2adf425c1782e95bb49f3b7062 Mon Sep 17 00:00:00 2001
From: fheinrich <fheinrich@techfak.uni-bielefeld.de>
Date: Mon, 4 Dec 2023 17:15:38 +0100
Subject: [PATCH] Added pygame GUI, fixed import

---
 overcooked_simulator/layouts/basic.layout |   6 +-
 overcooked_simulator/main.py              |   2 +-
 overcooked_simulator/pygame_gui.py        | 181 ++++++++++++++++++++++
 3 files changed, 185 insertions(+), 4 deletions(-)
 create mode 100644 overcooked_simulator/pygame_gui.py

diff --git a/overcooked_simulator/layouts/basic.layout b/overcooked_simulator/layouts/basic.layout
index 65a375bb..82524fc6 100644
--- a/overcooked_simulator/layouts/basic.layout
+++ b/overcooked_simulator/layouts/basic.layout
@@ -2,9 +2,9 @@ EEEEEEEEEEE
 ECCCCCCCCCE
 ECEEEEEEECE
 ECEEEEEEECE
-ECEEEEEEECE
-ECEEEEEEECE
-ECEEEEEEECE
+ECEEEEEEEEE
+ECEEEEEEEEE
+ECEEEEEEEEE
 ECEEEEEEECE
 ECEEEEEEECE
 ECCCCCCCCCE
diff --git a/overcooked_simulator/main.py b/overcooked_simulator/main.py
index 47897d24..168a240f 100644
--- a/overcooked_simulator/main.py
+++ b/overcooked_simulator/main.py
@@ -1,4 +1,4 @@
-from player import Player
+from overcooked_simulator.player import Player
 import sys
 from overcooked_simulator.simulation_runner import Simulator
 from overcooked_simulator.overcooked_environment import Environment
diff --git a/overcooked_simulator/pygame_gui.py b/overcooked_simulator/pygame_gui.py
new file mode 100644
index 00000000..a4410c33
--- /dev/null
+++ b/overcooked_simulator/pygame_gui.py
@@ -0,0 +1,181 @@
+import pygame
+import numpy as np
+from overcooked_simulator.overcooked_environment import Action
+from overcooked_simulator.simulation_runner import Simulator
+
+FPS = 60
+
+WHITE = (255, 255, 255)
+GREY = (190, 190, 190)
+BLACK = (0, 0, 0)
+COUNTERCOLOR = (240, 240, 240)
+LIGHTGREY = (220, 220, 220)
+GREEN = (0, 255, 0)
+RED = (255, 0, 0)
+BLUE = (0, 0, 255)
+YELLOW = (255, 255, 0)
+
+BACKGROUND_COLOR = GREY
+
+GET_CONTINOUS_INTERACT_AND_PICKUP = False
+
+
+class PlayerKeyset:
+    """Set of keyboard keys for controlling a player. First four keys are for movement, 5th key is for interacting
+    with counters. 6th key ist for picking up things or dropping them.
+
+    """
+    def __init__(self, keys: list[pygame.key]):
+        self.player_keys = keys
+        self.move_vectors = [[-1, 0], [1, 0], [0, -1], [0, 1]]
+        self.key_to_movement = {key: vec for (key, vec) in zip(self.player_keys[:-2], self.move_vectors)}
+        self.interact_key = self.player_keys[-2]
+        self.pickup_key = self.player_keys[-1]
+
+
+class PyGameGUI:
+    """Visualisation of the overcooked environmnent and reading keyboard inputs using pygame.
+    """
+    def __init__(self, simulator: Simulator):
+        self.simulator = simulator
+        self.counter_size = self.simulator.env.counter_side_length
+        self.WINDOW_WIDTH, self.WINDOW_HEIGHT = simulator.env.world_width, simulator.env.world_height
+
+        keys1 = [pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN, pygame.K_SPACE, pygame.K_i]
+        keys2 = [pygame.K_a, pygame.K_d, pygame.K_w, pygame.K_s, pygame.K_f, pygame.K_e]
+        self.player_keysets: list[PlayerKeyset] = [PlayerKeyset(keys1), PlayerKeyset(keys2)]
+
+    def send_action(self, action: Action):
+        """Sends an action to the game environment.
+
+        Args:
+            action: The action to be sent.
+        """
+        self.simulator.enter_action(action)
+
+    def handle_keys(self):
+        """Handles keyboard inputs. Sends action for the respective players. When a key is held down, every frame
+        an action is sent in this function.
+        """
+        keys = pygame.key.get_pressed()
+        for player_idx, keyset in enumerate(self.player_keysets):
+            relevant_keys = [keys[k] for k in keyset.player_keys]
+            if any(relevant_keys[:-2]):
+                move_vec = np.zeros(2)
+                for idx, pressed in enumerate(relevant_keys[:-2]):
+                    if pressed:
+                        move_vec += keyset.move_vectors[idx]
+                if np.linalg.norm(move_vec) != 0:
+                    move_vec = move_vec / np.linalg.norm(move_vec)
+
+                action = Action(f"p{player_idx+1}", "movement", move_vec)
+                self.send_action(action)
+            if GET_CONTINOUS_INTERACT_AND_PICKUP:
+                if relevant_keys[-2]:
+                    action = Action(f"p{player_idx+1}", "interact", "interact")
+                    self.send_action(action)
+                if relevant_keys[-1]:
+                    action = Action(f"p{player_idx+1}", "pickup", "pickup")
+                    self.send_action(action)
+
+    def handle_interact_single_send(self, event):
+        """Handles key events. Here when a key is held down, only one action is sent.
+
+        Args:
+            event: Pygame event for extracting the key.
+        """
+        for player_idx, keyset in enumerate(self.player_keysets):
+            if event.key == keyset.pickup_key:
+                action = Action(f"p{player_idx + 1}", "pickup", "pickup")
+                self.send_action(action)
+            elif event.key == keyset.interact_key:
+                action = Action(f"p{player_idx + 1}", "interact", "interact")
+                self.send_action(action)
+
+    def draw_background(self):
+        """Visualizes a game background.
+        """
+        GREY = (200, 200, 200)
+        blockSize = 20  # Set the size of the grid block
+        for x in range(0, self.WINDOW_WIDTH, blockSize):
+            for y in range(0, self.WINDOW_HEIGHT, blockSize):
+                rect = pygame.Rect(x, y, blockSize, blockSize)
+                pygame.draw.rect(self.screen, GREY, rect, 1)
+
+    def draw_players(self, state):
+        """Visualizes the players as circles with an triangle for the facing diretion.
+
+        Args:
+            state: The game state returned by the environment.
+        """
+        for player in state["players"].values():
+            pos = player.pos
+            size = player.radius
+            color1 = RED if player.name == "p1" else GREEN
+            color2 = WHITE
+
+            rect = pygame.Rect(pos[0] - (size / 2), pos[1] - (size / 2), size, size)
+            pygame.draw.circle(self.screen, color2, pos, size)
+            pygame.draw.rect(self.screen, color1, rect)
+
+            facing = player.facing_direction
+
+            pygame.draw.polygon(self.screen, BLUE,
+                            ((pos[0]+(facing[1]*5), pos[1]-(facing[0]*5)), (pos[0]-(facing[1]*5), pos[1]+(facing[0]*5)), player.pos + (facing * 20)))
+
+    def draw_counters(self, state):
+        """Visualizes the counters in the environment.
+
+        Args:
+            state: The game state returned by the environment.
+        """
+        for idx, counter in enumerate(state["counters"]):
+            counter_rect_outline = pygame.Rect(counter.pos[0] - (self.counter_size / 2),
+                                       counter.pos[1] - (self.counter_size / 2), self.counter_size,
+                                       self.counter_size)
+
+            pygame.draw.rect(self.screen, COUNTERCOLOR, counter_rect_outline)
+
+    def draw(self, state):
+        """Main visualization function.
+
+        Args:
+            state: The game state returned by the environment.
+        """
+        self.screen.fill(BACKGROUND_COLOR)
+        self.draw_background()
+
+        self.draw_counters(state)
+        self.draw_players(state)
+
+        pygame.display.flip()
+
+    def start_pygame(self):
+        """Starts pygame and the gui loop. Each frame the gamestate is visualized and keyboard inputs are read.
+        """
+        pygame.init()
+        pygame.font.init()
+
+        self.screen = pygame.display.set_mode((self.WINDOW_WIDTH, self.WINDOW_HEIGHT))
+        pygame.display.set_caption("Simple Overcooked Simulator")
+        self.screen.fill(BACKGROUND_COLOR)
+
+        clock = pygame.time.Clock()
+
+        # Game loop
+        running = True
+        while running:
+
+            for event in pygame.event.get():
+                if event.type == pygame.QUIT:
+                    running = False
+                if not GET_CONTINOUS_INTERACT_AND_PICKUP:
+                    if event.type == pygame.KEYDOWN:
+                        self.handle_interact_single_send(event)
+
+            self.handle_keys()
+            clock.tick(FPS)
+            state = self.simulator.get_state()
+            self.draw(state)
+
+        pygame.quit()
-- 
GitLab