From 6626507886c25c6c2d29df1fbb06cedfbf1d6da4 Mon Sep 17 00:00:00 2001 From: fheinrich <fheinrich@techfak.uni-bielefeld.de> Date: Thu, 21 Dec 2023 14:29:51 +0100 Subject: [PATCH] Draft: UI with buttons and window changing. --- .../game_content/layouts/empty.layout | 11 +- .../{pygame_gui => gui_2d_vis}/__init__.py | 0 .../{pygame_gui => gui_2d_vis}/game_colors.py | 0 .../gui_2d_vis/gui_theme.json | 49 +++ .../images/pixel_cook.png | Bin .../images/plate.png | Bin .../images/plate_clean.png | Bin .../images/plate_dirty.png | Bin .../{pygame_gui => gui_2d_vis}/images/pot.png | Bin .../images/tomato.png | Bin .../images/tomato_cut.png | Bin .../images/tomato_soup.png | Bin .../overcooked_gui.py} | 281 +++++++++++++++--- .../gui_2d_vis/pygame_gui_test.py | 38 +++ .../visualization.yaml | 0 overcooked_simulator/main.py | 7 +- overcooked_simulator/simulation_runner.py | 4 +- setup.py | 2 +- 18 files changed, 348 insertions(+), 44 deletions(-) rename overcooked_simulator/{pygame_gui => gui_2d_vis}/__init__.py (100%) rename overcooked_simulator/{pygame_gui => gui_2d_vis}/game_colors.py (100%) create mode 100644 overcooked_simulator/gui_2d_vis/gui_theme.json rename overcooked_simulator/{pygame_gui => gui_2d_vis}/images/pixel_cook.png (100%) rename overcooked_simulator/{pygame_gui => gui_2d_vis}/images/plate.png (100%) rename overcooked_simulator/{pygame_gui => gui_2d_vis}/images/plate_clean.png (100%) rename overcooked_simulator/{pygame_gui => gui_2d_vis}/images/plate_dirty.png (100%) rename overcooked_simulator/{pygame_gui => gui_2d_vis}/images/pot.png (100%) rename overcooked_simulator/{pygame_gui => gui_2d_vis}/images/tomato.png (100%) rename overcooked_simulator/{pygame_gui => gui_2d_vis}/images/tomato_cut.png (100%) rename overcooked_simulator/{pygame_gui => gui_2d_vis}/images/tomato_soup.png (100%) rename overcooked_simulator/{pygame_gui/pygame_gui.py => gui_2d_vis/overcooked_gui.py} (64%) create mode 100644 overcooked_simulator/gui_2d_vis/pygame_gui_test.py rename overcooked_simulator/{pygame_gui => gui_2d_vis}/visualization.yaml (100%) diff --git a/overcooked_simulator/game_content/layouts/empty.layout b/overcooked_simulator/game_content/layouts/empty.layout index de0f1b9e..68ddca6e 100644 --- a/overcooked_simulator/game_content/layouts/empty.layout +++ b/overcooked_simulator/game_content/layouts/empty.layout @@ -1,3 +1,10 @@ +_____######################### _____ -_____ -____P \ No newline at end of file +____P +## +# +# +# +# +# +# \ No newline at end of file diff --git a/overcooked_simulator/pygame_gui/__init__.py b/overcooked_simulator/gui_2d_vis/__init__.py similarity index 100% rename from overcooked_simulator/pygame_gui/__init__.py rename to overcooked_simulator/gui_2d_vis/__init__.py diff --git a/overcooked_simulator/pygame_gui/game_colors.py b/overcooked_simulator/gui_2d_vis/game_colors.py similarity index 100% rename from overcooked_simulator/pygame_gui/game_colors.py rename to overcooked_simulator/gui_2d_vis/game_colors.py diff --git a/overcooked_simulator/gui_2d_vis/gui_theme.json b/overcooked_simulator/gui_2d_vis/gui_theme.json new file mode 100644 index 00000000..3ca11b55 --- /dev/null +++ b/overcooked_simulator/gui_2d_vis/gui_theme.json @@ -0,0 +1,49 @@ +{ + "defaults": { + "colours": { + "normal_bg": "#45494e", + "hovered_bg": "#35393e", + "disabled_bg": "#25292e", + "selected_bg": "#193754", + "dark_bg": "#15191e", + "normal_text": "#c5cbd8", + "hovered_text": "#FFFFFF", + "selected_text": "#FFFFFF", + "disabled_text": "#6d736f", + "link_text": "#0000EE", + "link_hover": "#2020FF", + "link_selected": "#551A8B", + "text_shadow": "#777777", + "normal_border": "#DDDDDD", + "hovered_border": "#B0B0B0", + "disabled_border": "#808080", + "selected_border": "#8080B0", + "active_border": "#8080B0", + "filled_bar": "#f4251b", + "unfilled_bar": "#CCCCCC" + } + }, + "button": { + "colours": { + "normal_bg": "#45494e", + "hovered_bg": "#35393e", + "disabled_bg": "#25292e", + "selected_bg": "#193754", + "active_bg": "#193754", + "dark_bg": "#15191e", + "normal_text": "#c5cbd8", + "hovered_text": "#FFFFFF", + "selected_text": "#FFFFFF", + "disabled_text": "#6d736f", + "active_text": "#FFFFFF", + "normal_border": "#DDDDDD", + "hovered_border": "#B0B0B0", + "disabled_border": "#808080", + "selected_border": "#8080B0", + "active_border": "#8080B0" + }, + "misc": { + "tool_tip_delay": "1.5" + } + } +} \ No newline at end of file diff --git a/overcooked_simulator/pygame_gui/images/pixel_cook.png b/overcooked_simulator/gui_2d_vis/images/pixel_cook.png similarity index 100% rename from overcooked_simulator/pygame_gui/images/pixel_cook.png rename to overcooked_simulator/gui_2d_vis/images/pixel_cook.png diff --git a/overcooked_simulator/pygame_gui/images/plate.png b/overcooked_simulator/gui_2d_vis/images/plate.png similarity index 100% rename from overcooked_simulator/pygame_gui/images/plate.png rename to overcooked_simulator/gui_2d_vis/images/plate.png diff --git a/overcooked_simulator/pygame_gui/images/plate_clean.png b/overcooked_simulator/gui_2d_vis/images/plate_clean.png similarity index 100% rename from overcooked_simulator/pygame_gui/images/plate_clean.png rename to overcooked_simulator/gui_2d_vis/images/plate_clean.png diff --git a/overcooked_simulator/pygame_gui/images/plate_dirty.png b/overcooked_simulator/gui_2d_vis/images/plate_dirty.png similarity index 100% rename from overcooked_simulator/pygame_gui/images/plate_dirty.png rename to overcooked_simulator/gui_2d_vis/images/plate_dirty.png diff --git a/overcooked_simulator/pygame_gui/images/pot.png b/overcooked_simulator/gui_2d_vis/images/pot.png similarity index 100% rename from overcooked_simulator/pygame_gui/images/pot.png rename to overcooked_simulator/gui_2d_vis/images/pot.png diff --git a/overcooked_simulator/pygame_gui/images/tomato.png b/overcooked_simulator/gui_2d_vis/images/tomato.png similarity index 100% rename from overcooked_simulator/pygame_gui/images/tomato.png rename to overcooked_simulator/gui_2d_vis/images/tomato.png diff --git a/overcooked_simulator/pygame_gui/images/tomato_cut.png b/overcooked_simulator/gui_2d_vis/images/tomato_cut.png similarity index 100% rename from overcooked_simulator/pygame_gui/images/tomato_cut.png rename to overcooked_simulator/gui_2d_vis/images/tomato_cut.png diff --git a/overcooked_simulator/pygame_gui/images/tomato_soup.png b/overcooked_simulator/gui_2d_vis/images/tomato_soup.png similarity index 100% rename from overcooked_simulator/pygame_gui/images/tomato_soup.png rename to overcooked_simulator/gui_2d_vis/images/tomato_soup.png diff --git a/overcooked_simulator/pygame_gui/pygame_gui.py b/overcooked_simulator/gui_2d_vis/overcooked_gui.py similarity index 64% rename from overcooked_simulator/pygame_gui/pygame_gui.py rename to overcooked_simulator/gui_2d_vis/overcooked_gui.py index a723373f..d5e4ca3d 100644 --- a/overcooked_simulator/pygame_gui/pygame_gui.py +++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py @@ -3,10 +3,12 @@ import logging import math import sys from collections import deque +from enum import Enum import numpy as np import numpy.typing as npt import pygame +import pygame_gui import yaml from scipy.spatial import KDTree @@ -19,9 +21,9 @@ from overcooked_simulator.game_items import ( Meal, Plate, ) +from overcooked_simulator.gui_2d_vis.game_colors import BLUE +from overcooked_simulator.gui_2d_vis.game_colors import colors, Color from overcooked_simulator.overcooked_environment import Action -from overcooked_simulator.pygame_gui.game_colors import BLUE -from overcooked_simulator.pygame_gui.game_colors import colors, Color from overcooked_simulator.simulation_runner import Simulator USE_PLAYER_COOK_SPRITES = True @@ -29,6 +31,12 @@ SHOW_INTERACTION_RANGE = False SHOW_COUNTER_CENTERS = False +class MenuStates(Enum): + Start = "Start" + Game = "Game" + End = "End" + + def create_polygon(n, length): if n == 0: return np.array([0, 0]) @@ -86,11 +94,17 @@ class PyGameGUI: player_names: list[str], player_keys: list[pygame.key], ): - self.screen = None + self.game_screen = None self.FPS = 60 self.simulator = simulator self.counter_size = self.simulator.env.counter_side_length + + self.screen_margin = 100 self.window_width, self.window_height = ( + simulator.env.world_width + (2 * self.screen_margin), + simulator.env.world_height + (2 * self.screen_margin), + ) + self.game_width, self.game_height = ( simulator.env.world_width, simulator.env.world_height, ) @@ -106,7 +120,7 @@ class PyGameGUI: ] # TODO cache loaded images? - with open(ROOT_DIR / "pygame_gui" / "visualization.yaml", "r") as file: + with open(ROOT_DIR / "gui_2d_vis" / "visualization.yaml", "r") as file: self.visualization_config = yaml.safe_load(file) self.images_path = ROOT_DIR / "pygame_gui" / "images" @@ -115,6 +129,9 @@ class PyGameGUI: self.image_cache_dict = {} + self.menu_state = MenuStates.Start + self.manager: pygame_gui.UIManager + def create_player_colors(self) -> list[Color]: number_player = len(self.simulator.env.players) hue_values = np.linspace(0, 1, number_player + 1) @@ -183,12 +200,15 @@ class PyGameGUI: def draw_background(self): """Visualizes a game background.""" + self.game_screen.fill( + colors[self.visualization_config["Kitchen"]["ground_tiles_color"]] + ) block_size = self.counter_size // 2 # Set the size of the grid block for x in range(0, self.window_width, block_size): for y in range(0, self.window_height, block_size): rect = pygame.Rect(x, y, block_size, block_size) pygame.draw.rect( - self.screen, + self.game_screen, self.visualization_config["Kitchen"]["background_lines"], rect, 1, @@ -200,7 +220,7 @@ class PyGameGUI: image = self.image_cache_dict[cache_entry] else: image = pygame.image.load( - ROOT_DIR / "pygame_gui" / img_path + ROOT_DIR / "gui_2d_vis" / img_path ).convert_alpha() self.image_cache_dict[cache_entry] = image @@ -209,7 +229,7 @@ class PyGameGUI: image = pygame.transform.rotate(image, rot_angle) rect = image.get_rect() rect.center = pos - self.screen.blit(image, rect) + self.game_screen.blit(image, rect) def draw_players(self, state): """Visualizes the players as circles with a triangle for the facing direction. @@ -235,14 +255,14 @@ class PyGameGUI: color1 = self.player_colors[p_idx] color2 = colors["white"] - pygame.draw.circle(self.screen, color2, pos, size) - pygame.draw.circle(self.screen, BLUE, pos, size, width=1) - pygame.draw.circle(self.screen, colors[color1], pos, size // 2) + pygame.draw.circle(self.game_screen, color2, pos, size) + pygame.draw.circle(self.game_screen, BLUE, pos, size, width=1) + pygame.draw.circle(self.game_screen, colors[color1], pos, size // 2) pos = player.pos facing = player.facing_direction pygame.draw.polygon( - self.screen, + self.game_screen, BLUE, ( ( @@ -259,13 +279,15 @@ class PyGameGUI: if SHOW_INTERACTION_RANGE: pygame.draw.circle( - self.screen, + self.game_screen, BLUE, player.facing_point, player.interaction_range * self.counter_size, width=1, ) - pygame.draw.circle(self.screen, colors["red1"], player.facing_point, 4) + pygame.draw.circle( + self.game_screen, colors["red1"], player.facing_point, 4 + ) if player.holding is not None: holding_item_pos = player.pos + (20 * player.facing_direction) @@ -274,7 +296,7 @@ class PyGameGUI: if player.current_nearest_counter: counter: Counter = player.current_nearest_counter pygame.draw.rect( - self.screen, + self.game_screen, colors[self.player_colors[p_idx]], rect=pygame.Rect( counter.pos[0] - (self.counter_size // 2), @@ -308,7 +330,7 @@ class PyGameGUI: if "center_offset" in part: dx, dy = np.array(part["center_offset"]) * self.counter_size rect = pygame.Rect(pos[0] + dx, pos[1] + dy, height, width) - pygame.draw.rect(self.screen, color, rect) + pygame.draw.rect(self.game_screen, color, rect) else: rect = pygame.Rect( pos[0] - (height / 2), @@ -316,19 +338,19 @@ class PyGameGUI: height, width, ) - pygame.draw.rect(self.screen, color, rect) + pygame.draw.rect(self.game_screen, color, rect) elif part_type == "circle": radius = part["radius"] * self.counter_size color = colors[part["color"]] if "center_offset" in part: pygame.draw.circle( - self.screen, + self.game_screen, color, pos + np.array(part["center_offset"]), radius, ) else: - pygame.draw.circle(self.screen, color, pos, radius) + pygame.draw.circle(self.game_screen, color, pos, radius) def draw_item(self, pos: npt.NDArray[float], item: Item, scale: float = 1.0): """Visualization of an item at the specified position. On a counter or in the hands of the player. @@ -373,7 +395,7 @@ class PyGameGUI: progress_width, bar_height, ) - pygame.draw.rect(self.screen, colors["green1"], progress_bar) + pygame.draw.rect(self.game_screen, colors["green1"], progress_bar) def draw_counter(self, counter): """Visualization of a counter at its position. If it is occupied by an item, it is also shown. @@ -416,7 +438,7 @@ class PyGameGUI: for counter in state["counters"]: self.draw_counter(counter) if SHOW_COUNTER_CENTERS: - pygame.draw.circle(self.screen, colors["green1"], counter.pos, 3) + pygame.draw.circle(self.game_screen, colors["green1"], counter.pos, 3) def draw(self, state): """Main visualization function. @@ -424,15 +446,148 @@ class PyGameGUI: Args: state: The game state returned by the environment. """ - self.screen.fill( - colors[self.visualization_config["Kitchen"]["ground_tiles_color"]] - ) + self.draw_background() self.draw_counters(state) self.draw_players(state) + self.manager.draw_ui(self.game_screen) + + def init_ui_elements(self): + self.manager = pygame_gui.UIManager((self.window_width, self.window_height)) + self.manager.get_theme().load_theme(ROOT_DIR / "gui_2d_vis" / "gui_theme.json") + + button_width, button_height = 200, 60 + self.start_button = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect( + ( + (self.window_width // 2) - button_width // 2, + (self.window_height / 2) - button_height // 2, + ), + (button_width, button_height), + ), + text="Start Game", + manager=self.manager, + ) + self.start_button.can_hover() + + self.quit_button = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect( + ( + (self.window_width - button_width), + 0, + ), + (button_width, button_height), + ), + text="Quit Game", + manager=self.manager, + ) + self.quit_button.can_hover() + + self.finished_button = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect( + ( + (self.window_width - button_width), + (self.window_height - button_height), + ), + (button_width, button_height), + ), + text="End screen", + manager=self.manager, + ) + self.finished_button.can_hover() + + self.back_button = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect( + ( + (0), + (self.window_height - button_height), + ), + (button_width, button_height), + ), + text="Back to Start", + manager=self.manager, + ) + self.back_button.can_hover() + + self.score_rect = pygame.Rect( + ( + (self.window_width // 2) - button_width // 2, + (self.window_height / 2) - button_height // 2, + ), + (button_width, button_height), + ) + + self.score_label = pygame_gui.elements.UILabel( + text=f"Your score: _", + relative_rect=self.score_rect, + manager=self.manager, + object_id="#score_label", + ) + + layout_file_paths = [ + str(p.name) + for p in (ROOT_DIR / "game_content" / "layouts").glob("*.layout") + ] + assert len(layout_file_paths) != 0, "No layout files." + dropdown_width, dropdown_height = 200, 40 + self.layout_selection = pygame_gui.elements.UIDropDownMenu( + relative_rect=pygame.Rect( + ( + 0, + 0, + ), + (dropdown_width, dropdown_height), + ), + manager=self.manager, + options_list=layout_file_paths, + starting_option=layout_file_paths[-1], + ) - pygame.display.flip() + def setup_simulation(self, config_path, layout_path): + self.simulator = Simulator(config_path, layout_path, 600) + + number_player = len(self.player_names) + for i in range(number_player): + player_name = f"p{i}" + self.simulator.register_player(player_name) + self.simulator.start() + + def change_to_start_window(self): + self.menu_state = MenuStates.Start + self.back_button.hide() + self.quit_button.show() + self.start_button.show() + self.score_label.hide() + self.finished_button.hide() + self.layout_selection.show() + + def change_to_game_window(self): + self.menu_state = MenuStates.Game + self.start_button.hide() + self.back_button.show() + self.score_label.hide() + self.finished_button.show() + self.layout_selection.hide() + layout_path = ( + ROOT_DIR + / "game_content" + / "layouts" + / self.layout_selection.selected_option + ) + config_path = ROOT_DIR / "game_content" / "environment_config.yaml" + self.setup_simulation(config_path, layout_path) + + def change_to_end_window(self): + self.menu_state = MenuStates.End + self.start_button.hide() + self.back_button.show() + self.score_label.show() + self.score_label.set_text( + f"Your Score is {self.simulator.env.game_score.score}" + ) + self.finished_button.hide() + self.layout_selection.hide() def start_pygame(self): """Starts pygame and the gui loop. Each frame the game state is visualized and keyboard inputs are read.""" @@ -440,32 +595,90 @@ class PyGameGUI: 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( - colors[self.visualization_config["Kitchen"]["ground_tiles_color"]] + self.init_ui_elements() + + self.screen_margin = 100 + self.main_window = pygame.display.set_mode( + ( + self.window_width, + self.window_height, + ) + ) + self.game_screen = pygame.Surface( + ( + self.game_width, + self.game_height, + ), ) + pygame.display.set_caption("Simple Overcooked Simulator") clock = pygame.time.Clock() + self.change_to_start_window() # Game loop running = True while running: try: + time_delta = clock.tick(self.FPS) / 1000.0 + for event in pygame.event.get(): if event.type == pygame.QUIT: running = False - if event.type in [pygame.KEYDOWN, pygame.KEYUP]: + if event.type == pygame_gui.UI_BUTTON_PRESSED: + if event.ui_element == self.start_button: + self.change_to_game_window() + if event.ui_element == self.back_button: + self.change_to_start_window() + if event.ui_element == self.finished_button: + self.change_to_end_window() + if event.ui_element == self.quit_button: + running = False + log.debug("Quitting game") + + if ( + event.type in [pygame.KEYDOWN, pygame.KEYUP] + and self.menu_state == MenuStates.Game + ): self.handle_key_event(event) - self.handle_keys() - clock.tick(self.FPS) - state = self.simulator.get_state() - self.draw(state) + self.manager.process_events(event) + + # drawing: + + self.main_window.fill(colors["lemonchiffon1"]) + self.manager.draw_ui(self.main_window) + + match self.menu_state: + case MenuStates.Start: + pass + case MenuStates.Game: + self.draw_background() + + self.handle_keys() + + state = self.simulator.get_state() + self.draw(state) + + game_screen_rect = self.game_screen.get_rect() + game_screen_rect.center = [ + (self.window_width) // 2, + (self.window_height) // 2, + ] + + self.main_window.blit(self.game_screen, game_screen_rect) + + case MenuStates.End: + pygame.draw.rect( + self.game_screen, colors["cornsilk1"], self.score_rect + ) + self.manager.update(time_delta) + pygame.display.flip() except KeyboardInterrupt: - pygame.quit() self.simulator.stop() + pygame.quit() sys.exit() + self.simulator.stop() pygame.quit() + sys.exit() diff --git a/overcooked_simulator/gui_2d_vis/pygame_gui_test.py b/overcooked_simulator/gui_2d_vis/pygame_gui_test.py new file mode 100644 index 00000000..2a59f534 --- /dev/null +++ b/overcooked_simulator/gui_2d_vis/pygame_gui_test.py @@ -0,0 +1,38 @@ +import pygame +import pygame_gui + +pygame.init() + +pygame.display.set_caption("Quick Start") +window_surface = pygame.display.set_mode((800, 600)) + +background = pygame.Surface((800, 600)) +background.fill(pygame.Color("#000000")) + +manager = pygame_gui.UIManager((800, 600)) + +hello_button = pygame_gui.elements.UIButton( + relative_rect=pygame.Rect((350, 275), (100, 50)), text="Say Hello", manager=manager +) + +clock = pygame.time.Clock() +is_running = True + +while is_running: + time_delta = clock.tick(60) / 1000.0 + for event in pygame.event.get(): + if event.type == pygame.QUIT: + is_running = False + + if event.type == pygame_gui.UI_BUTTON_PRESSED: + if event.ui_element == hello_button: + print("Hello World!") + + manager.process_events(event) + + manager.update(time_delta) + + window_surface.blit(background, (0, 0)) + manager.draw_ui(window_surface) + + pygame.display.update() diff --git a/overcooked_simulator/pygame_gui/visualization.yaml b/overcooked_simulator/gui_2d_vis/visualization.yaml similarity index 100% rename from overcooked_simulator/pygame_gui/visualization.yaml rename to overcooked_simulator/gui_2d_vis/visualization.yaml diff --git a/overcooked_simulator/main.py b/overcooked_simulator/main.py index 239779f4..86caba35 100644 --- a/overcooked_simulator/main.py +++ b/overcooked_simulator/main.py @@ -7,7 +7,7 @@ from datetime import datetime import pygame from overcooked_simulator import ROOT_DIR -from overcooked_simulator.pygame_gui.pygame_gui import PyGameGUI +from overcooked_simulator.gui_2d_vis.overcooked_gui import PyGameGUI from overcooked_simulator.simulation_runner import Simulator log = logging.getLogger(__name__) @@ -50,12 +50,9 @@ def main(): pygame.K_i, ] keys2 = [pygame.K_a, pygame.K_d, pygame.K_w, pygame.K_s, pygame.K_f, pygame.K_e] - gui = PyGameGUI(simulator, [f"p{i}" for i in range(number_player)], [keys1, keys2]) - simulator.start() + gui = PyGameGUI(simulator, [f"p{i}" for i in range(number_player)], [keys1, keys2]) gui.start_pygame() - - simulator.stop() sys.exit() diff --git a/overcooked_simulator/simulation_runner.py b/overcooked_simulator/simulation_runner.py index 795b61e6..d479efb8 100644 --- a/overcooked_simulator/simulation_runner.py +++ b/overcooked_simulator/simulation_runner.py @@ -26,7 +26,7 @@ class Simulator(Thread): def __init__( self, - env_layout_path, + env_config_path, layout_path, frequency: int, item_info_path=ROOT_DIR / "game_content" / "item_info.yaml", @@ -39,7 +39,7 @@ class Simulator(Thread): self.step_frequency: int = frequency self.preferred_sleep_time_ns: float = 1e9 / self.step_frequency self.env: Environment = Environment( - env_layout_path, layout_path, item_info_path + env_config_path, layout_path, item_info_path ) super().__init__() diff --git a/setup.py b/setup.py index 69f30ff3..c1b16d40 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ with open("README.md") as readme_file: with open("CHANGELOG.md") as history_file: history = history_file.read() -requirements = ["numpy", "pygame", "scipy", "pytest>=3", "pyyaml"] +requirements = ["numpy", "pygame", "scipy", "pytest>=3", "pyyaml", "pygame_gui"] test_requirements = [ "pytest>=3", -- GitLab