diff --git a/cooperative_cuisine/pygame_2d_vis/gui.py b/cooperative_cuisine/pygame_2d_vis/gui.py index 28f154fd2dac1eb4bce8d603868eddc32f42ed13..8c6ad90a0c6f3be86cfd927a170a609a2bf6a418 100644 --- a/cooperative_cuisine/pygame_2d_vis/gui.py +++ b/cooperative_cuisine/pygame_2d_vis/gui.py @@ -28,6 +28,7 @@ from cooperative_cuisine.game_server import ( ) from cooperative_cuisine.pygame_2d_vis.drawing import Visualizer from cooperative_cuisine.pygame_2d_vis.game_colors import colors +from cooperative_cuisine.server_results import PlayerInfo from cooperative_cuisine.state_representation import StateRepresentation from cooperative_cuisine.utils import ( url_and_port_arguments, @@ -42,7 +43,7 @@ class MenuStates(Enum): """Enumeration of "Page" types in the 2D pygame vis.""" Start = "Start" - ControllerTutorial = "ControllerTutorial" + Tutorial = "Tutorial" PreGame = "PreGame" Game = "Game" PostGame = "PostGame" @@ -96,11 +97,16 @@ class PlayerKeySet: self.joystick = joystick def set_controlled_players(self, controlled_players: list[str]) -> None: + """Sets the controlled players for this keyset. + Args: + controlled_players: The players controlled by this keyset. + """ self.controlled_players = controlled_players self.current_player = self.controlled_players[0] self.current_idx = 0 def next_player(self) -> None: + """Switches to the next player in the list of controlled players.""" self.current_idx = (self.current_idx + 1) % len(self.controlled_players) if self.other_keyset: for ok in self.other_keyset: @@ -112,6 +118,7 @@ class PlayerKeySet: def __repr__(self) -> str: return f"Keyset(current={self.current_player}, players={self.controlled_players}, joy={self.joystick})" + class PyGameGUI: """Visualisation of the overcooked environment and reading keyboard inputs using pygame.""" @@ -186,6 +193,8 @@ class PyGameGUI: self.window_width_fullscreen /= 2 self.window_height_fullscreen /= 2 + self.game_width = 0 + self.game_height = 0 self.window_width_windowed = self.min_width self.window_height_windowed = self.min_height self.kitchen_width = 1 @@ -290,7 +299,7 @@ class PyGameGUI: ) self.send_action(action) - def handle_joy_stick_input(self, joysticks): + def handle_joy_stick_input(self, joysticks: dict[int, pygame.joystick.Joystick]): """Handles joystick inputs for movement every frame Args: joysticks: list of joysticks @@ -331,7 +340,7 @@ class PyGameGUI: ) self.send_action(action) - def handle_key_event(self, event): + def handle_key_event(self, event: pygame.event.Event): """Handles key events for the pickup and interaction keys. Pickup is a single action, for interaction keydown and keyup is necessary, because the player has to be able to hold the key down. @@ -361,7 +370,9 @@ class PyGameGUI: if event.type == pygame.KEYDOWN: key_set.next_player() - def handle_joy_stick_event(self, event, joysticks): + def handle_joy_stick_event( + self, event: pygame.event.Event, joysticks: dict[int, pygame.joystick.Joystick] + ): """Handles joy stick events for the pickup and interaction keys. Pickup is a single action, for interaction buttondown and buttonup is necessary, because the player has to be able to hold the button down. @@ -403,6 +414,7 @@ class PyGameGUI: key_set.next_player() def set_window_size(self): + """Sets the window size based on fullscreen or not.""" if self.fullscreen: flags = pygame.FULLSCREEN self.window_width = self.window_width_fullscreen @@ -420,12 +432,13 @@ class PyGameGUI: flags=flags, ) - def reset_window_size(self): - self.game_width = 0 - self.game_height = 0 - self.set_window_size() + def set_game_size(self, max_width: float = None, max_height: float = None): + """Sets the game size based on the kitchen size and the current window size. - def set_game_size(self, max_width=None, max_height=None): + Args: + max_width: Maximum width of the game screen. + max_height: Maximum height of the game screen. + """ if max_width is None: max_width = self.window_width - (2 * self.screen_margin) if max_height is None: @@ -466,6 +479,8 @@ class PyGameGUI: ) def init_ui_elements(self): + """Creates all UI elements. Creates lists of which elements belong on which screen.""" + self.manager = pygame_gui.UIManager( (self.window_width, self.window_height), starting_language=self.language, @@ -640,9 +655,8 @@ class PyGameGUI: ) size = 50 - add_player_button_rect = pygame.Rect((0, 0), (size, size)) self.add_human_player_button = pygame_gui.elements.UIButton( - relative_rect=add_player_button_rect, + relative_rect=pygame.Rect((0, 0), (size, size)), text="+", manager=self.manager, object_id="#quantity_button", @@ -650,10 +664,10 @@ class PyGameGUI: anchors={"left_target": self.added_bots_label, "centery": "centery"}, ) - remove_player_button_rect = pygame.Rect((0, 0), (size, size)) - remove_player_button_rect.right = 0 + rect = pygame.Rect((0, 0), (size, size)) + rect.right = 0 self.remove_human_button = pygame_gui.elements.UIButton( - relative_rect=remove_player_button_rect, + relative_rect=rect, text="-", manager=self.manager, object_id="#quantity_button", @@ -665,9 +679,8 @@ class PyGameGUI: }, ) - add_bot_button_rect = pygame.Rect((0, 0), (size, size)) self.add_bot_button = pygame_gui.elements.UIButton( - relative_rect=add_bot_button_rect, + relative_rect=pygame.Rect((0, 0), (size, size)), text="+", manager=self.manager, object_id="#quantity_button", @@ -675,10 +688,10 @@ class PyGameGUI: anchors={"left_target": self.added_bots_label, "centery": "centery"}, ) - remove_bot_button_rect = pygame.Rect((0, 0), (size, size)) - remove_bot_button_rect.right = 0 + rect = pygame.Rect((0, 0), (size, size)) + rect.right = 0 self.remove_bot_button = pygame_gui.elements.UIButton( - relative_rect=remove_bot_button_rect, + relative_rect=rect, text="-", manager=self.manager, object_id="#quantity_button", @@ -838,22 +851,6 @@ class PyGameGUI: self.orders_container_width = ( self.window_width - (2 * self.buttons_width) - (self.buttons_width * 0.7) ) - # rect = pygame.Rect( - # 0, - # 0, - # self.orders_container_width, - # self.screen_margin, - # ) - # self.orders_container = pygame_gui.elements.UIPanel( - # relative_rect=rect, - # manager=self.manager, - # object_id="#graph_container", - # anchors={ - # "top": "top", - # "left": "left", - # "left_target": self.orders_label, - # }, - # ) self.orders_image = pygame_gui.elements.UIImage( relative_rect=pygame.Rect( @@ -1075,7 +1072,12 @@ class PyGameGUI: final_text_container, ] - def show_screen_elements(self, elements: list): + def show_screen_elements(self, elements: list[pygame_gui.core.UIElement]): + """Hides all UI elements and shows the elements in the list and elements in self.on_all_screens. + + Args: + elements: List of UI elements to show. + """ all_elements = ( self.start_screen_elements + self.tutorial_screen_elements @@ -1091,7 +1093,8 @@ class PyGameGUI: for element in elements + self.on_all_screens: element.show() - def update_tutorial_screen(self): + def setup_tutorial_screen(self): + """Updates the tutorial screen with the current tutorial image and the continue button.""" self.show_screen_elements(self.tutorial_screen_elements) self.set_game_size( @@ -1115,19 +1118,17 @@ class PyGameGUI: grid_size=self.window_height / 18, ) self.tutorial_graph_image.set_image(tutorial_graph_surface) - # self.tutorial_graph_image.set_dimensions((self.game_width, self.game_height)) def update_screen_elements(self): + """Shows and hides the UI elements based on the current menu state.""" match self.menu_state: case MenuStates.Start: self.show_screen_elements(self.start_screen_elements) - if self.CONNECT_WITH_STUDY_SERVER: self.bot_number_container.hide() - self.update_selection_elements() - case MenuStates.ControllerTutorial: - self.update_tutorial_screen() + case MenuStates.Tutorial: + self.setup_tutorial_screen() case MenuStates.PreGame: self.init_ui_elements() self.show_screen_elements(self.pregame_screen_elements) @@ -1137,7 +1138,7 @@ class PyGameGUI: case MenuStates.PostGame: self.init_ui_elements() self.show_screen_elements(self.postgame_screen_elements) - self.update_postgame_screen(self.last_state) + self.update_post_game_screen(self.last_state) if self.last_level: self.next_game_button.hide() self.finish_study_button.show() @@ -1148,37 +1149,27 @@ class PyGameGUI: self.show_screen_elements(self.end_screen_elements) def draw_main_window(self): + """Main draw function. Draws the main window and updates the UI. + Draws the game screen in Game and ControllerTutorial screen.""" self.main_window.fill( colors[self.visualization_config["GameWindow"]["background_color"]] ) - match self.menu_state: - case MenuStates.ControllerTutorial: - self.draw_tutorial_screen_frame() - case MenuStates.Game: - self.draw_game_screen_frame() + if self.menu_state == MenuStates.Tutorial: + self.draw_game_screen_frame(tutorial=True) + elif self.menu_state == MenuStates.Game: + self.draw_game_screen_frame(tutorial=False) self.manager.draw_ui(self.main_window) self.manager.update(self.time_delta) pygame.display.flip() - def draw_tutorial_screen_frame(self): - self.handle_keys() - self.handle_joy_stick_input(joysticks=self.joysticks) - - state = self.request_state() - self.vis.draw_gamescreen( - self.game_screen, - state, - self.grid_size, - [int(k.current_player) for k in self.key_sets], - ) - - game_screen_rect = self.game_screen.get_rect() - game_screen_rect.center = self.game_center - self.main_window.blit(self.game_screen, game_screen_rect) + def update_post_game_screen(self, state: dict): + """Updates the post game screen with the final score and the completed meals. - def update_postgame_screen(self, state): + Args: + state: The game state returned by the environment, containing served meals and score. + """ score = state["score"] # self.score_conclusion.set_text(f"Your final score is {score}. Hurray!") self.score_conclusion.set_text( @@ -1310,50 +1301,23 @@ class PyGameGUI: ) def exit_game(self): + """Exits the game.""" self.menu_state = MenuStates.PostGame if self.CONNECT_WITH_STUDY_SERVER: self.send_level_done() self.disconnect_websockets() - self.update_postgame_screen(self.last_state) + self.update_post_game_screen(self.last_state) self.update_screen_elements() self.beeped_once = False - def draw_game_screen_frame(self): - self.last_state = self.request_state() - - self.handle_keys() - self.handle_joy_stick_input(joysticks=self.joysticks) - - if not self.beeped_once and self.last_state["all_players_ready"]: - self.beeped_once = True - self.play_bell_sound() - - if self.last_state["ended"]: - self.exit_game() - - else: - self.draw_game(self.last_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) - - if not self.last_state["all_players_ready"]: - self.wait_players_label.show() - else: - self.wait_players_label.hide() - - def draw_game(self, state): + def draw_game(self, state: dict): """Main visualization function. - Args: state: The game state returned by the environment.""" + Args: + state: The game state returned by the environment. + """ self.vis.draw_gamescreen( self.game_screen, state, @@ -1361,7 +1325,6 @@ class PyGameGUI: [int(k.current_player) for k in self.key_sets], ) - # orders_surface = pygame.Surface((self.orders_container_width, self.screen_margin)) self.vis.draw_orders( screen=self.orders_image.image, state=state, @@ -1370,7 +1333,6 @@ class PyGameGUI: height=self.screen_margin, config=self.visualization_config, ) - # self.orders_image.set_image(orders_surface) border = self.visualization_config["GameWindow"]["game_border_size"] border_rect = pygame.Rect( @@ -1407,13 +1369,69 @@ class PyGameGUI: ), ) - def update_score_label(self, state): + def draw_game_screen_frame(self, tutorial: bool = False): + """Main visualization function for the game screen. + + Args: + tutorial: If True, the tutorial screen is drawn, which is a simplified version of the game screen. + """ + self.last_state = self.request_state() + + self.handle_keys() + self.handle_joy_stick_input(joysticks=self.joysticks) + + if tutorial: + self.vis.draw_gamescreen( + self.game_screen, + self.last_state, + self.grid_size, + [int(k.current_player) for k in self.key_sets], + ) + game_screen_rect = self.game_screen.get_rect() + game_screen_rect.center = self.game_center + self.main_window.blit(self.game_screen, game_screen_rect) + return + else: + if not self.beeped_once and self.last_state["all_players_ready"]: + self.beeped_once = True + self.play_bell_sound() + + if self.last_state["ended"]: + self.exit_game() + + else: + self.draw_game(self.last_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) + + if not self.last_state["all_players_ready"]: + self.wait_players_label.show() + else: + self.wait_players_label.hide() + + def update_score_label(self, state: dict): + """Updates the score label. + + Args: + state: The game state returned by the environment. + + """ score = state["score"] self.score_label.set_text( "translations.score", text_kwargs={"score": str(score)} ) def update_remaining_time(self, remaining_time: float): + """Updates the remaining time label. + + Args: + remaining_time: The remaining time in seconds. + """ hours, rem = divmod(int(remaining_time), 3600) minutes, seconds = divmod(rem, 60) display_time = f"{minutes}:{'%02d' % seconds}" @@ -1421,7 +1439,12 @@ class PyGameGUI: "translations.time_remaining", text_kwargs={"time": display_time} ) - def create_env_on_game_server(self, tutorial): + def create_env_on_game_server(self, tutorial: bool): + """Starts an environment on the game server. + + Args: + tutorial: If True, a tutorial environment is created. Else a normal game environment is created. + """ if tutorial: layout_path = ROOT_DIR / "configs" / "layouts" / "tutorial.layout" environment_config_path = ROOT_DIR / "configs" / "tutorial_env_config.yaml" @@ -1486,6 +1509,7 @@ class PyGameGUI: self.level_info["number_players"] = num_players def update_pregame_screen(self): + """Updates the pregame screen. Possible recipes in the level are displayed.""" self.level_name_label.set_text( "translations.level_name", text_kwargs={"level": self.level_info["name"]} ) @@ -1584,6 +1608,8 @@ class PyGameGUI: ) def setup_tutorial(self): + """Sets up the tutorial environment. This includes creating the environment + on the game server, and setting up the connection.""" answer = requests.post( f"{self.request_url}/connect_to_tutorial/{self.participant_id}" ) @@ -1603,7 +1629,9 @@ class PyGameGUI: log.warning("Could not create tutorial.") def get_game_connection(self): - if self.menu_state == MenuStates.ControllerTutorial: + """Sets up a connection to the game server. + This includes getting the player info, level info and player keys""" + if self.menu_state == MenuStates.Tutorial: self.setup_tutorial() self.key_sets = self.setup_player_keys(["0"], 1, False) self.vis.create_player_colors(1) @@ -1634,10 +1662,17 @@ class PyGameGUI: ) self.player_ids = list(self.player_info.keys()) - def create_and_connect_bot(self, player_id, player_info): + def create_and_connect_bot(self, player_id: str, player_info: PlayerInfo): + """Creates a bot process and connects it to the game server. + + Args: + player_id: The id/name of the player. + player_info: Player info containing client_id, player_hash player_id and websocket_url. + """ player_hash = player_info["player_hash"] print( - f'--general_plus="agent_websocket:{player_info["websocket_url"]};player_hash:{player_hash};agent_id:{player_id}"' + f'--general_plus="agent_websocket:{player_info["websocket_url"]};' + f'player_hash:{player_hash};agent_id:{player_id}"' ) if self.USE_AAAMBOS_AGENT: sub = Popen( @@ -1650,7 +1685,8 @@ class PyGameGUI: str(ROOT_DIR / "configs" / "agents" / "arch_config.yml"), "--run_config", str(ROOT_DIR / "configs" / "agents" / "run_config.yml"), - f'--general_plus="agent_websocket:{player_info["websocket_url"]};player_hash:{player_hash};agent_id:{player_id}"', + f'--general_plus="agent_websocket:{player_info["websocket_url"]};' + f'player_hash:{player_hash};agent_id:{player_id}"', f"--instance={player_hash}", ] ), @@ -1672,6 +1708,8 @@ class PyGameGUI: self.sub_processes.append(sub) def connect_websockets(self): + """Connects the websockets of the players to the game server. + If the player is a bot, a bot process is created""" for p, (player_id, player_info) in enumerate(self.player_info.items()): if p < self.number_humans_to_be_added: # add player websockets @@ -1688,18 +1726,23 @@ class PyGameGUI: self.websockets[player_id] = websocket else: - # create bots and add bot websockets self.create_and_connect_bot(player_id, player_info) if p == 0: self.state_player_id = player_id def setup_game(self): + """Sets up prerequisites for the game. This includes connecting the websockets, creating player colors + in the vis and setting the kitchen size.""" self.connect_websockets() self.vis.create_player_colors(self.level_info["number_players"]) self.kitchen_width, self.kitchen_height = self.level_info["kitchen_size"] def stop_game_on_server(self, reason: str) -> None: + """Stops the game directly on the game server. + Args: + reason: The reason for stopping the game. + """ log.debug(f"Stopping game: {reason}") if not self.CONNECT_WITH_STUDY_SERVER: answer = requests.post( @@ -1718,11 +1761,14 @@ class PyGameGUI: ) def send_tutorial_finished(self): + """Signals the study server that the tutorial was finished.""" requests.post( f"{self.request_url}/disconnect_from_tutorial/{self.participant_id}", ) def finished_button_press(self): + """Gets called when the finished button is pressed. + Stops the game on the game server if the study server is not used.""" if not self.CONNECT_WITH_STUDY_SERVER: self.stop_game_on_server("finished_button_pressed") self.menu_state = MenuStates.PostGame @@ -1730,6 +1776,7 @@ class PyGameGUI: self.update_screen_elements() def fullscreen_button_press(self): + """Toggles between fullscreen and windowed mode.""" self.fullscreen = not self.fullscreen self.set_window_size() self.init_ui_elements() @@ -1737,6 +1784,7 @@ class PyGameGUI: self.update_screen_elements() def reset_gui_values(self): + """Reset the values of the GUI elements to their default values. Default values are defined here.""" self.currently_controlled_player_idx = 0 self.number_humans_to_be_added = 1 self.number_bots_to_be_added = 0 @@ -1747,6 +1795,8 @@ class PyGameGUI: self.multiple_keysets = False def update_selection_elements(self): + """Updates the selection elements of the GUI. This includes the number of players, + the number of bots, the split players button and the multiple keysets button.""" if self.number_humans_to_be_added == 1: self.remove_human_button.disable() self.multiple_keysets_button.hide() @@ -1821,6 +1871,7 @@ class PyGameGUI: self.websockets[action.player].recv() def request_state(self): + """Requests the current state of the game environment from the game server.""" message_dict = { "type": PlayerRequestType.GET_STATE.value, "action": None, @@ -1831,7 +1882,19 @@ class PyGameGUI: state = json.loads(self.websockets[self.state_player_id].recv()) return state + def exit_tutorial(self): + """Exits the tutorial. Disconnects the websockets and signals the study server that the tutorial was + finished if the study server is used. Otherwise, the game is stopped on the game server. + """ + self.disconnect_websockets() + self.menu_state = MenuStates.PreGame + if self.CONNECT_WITH_STUDY_SERVER: + self.send_tutorial_finished() + else: + self.stop_game_on_server("tutorial_finished") + def disconnect_websockets(self): + """Disconnects the websockets. Kills all subprocesses that are running bots.""" for sub in self.sub_processes: try: if self.USE_AAAMBOS_AGENT: @@ -1850,15 +1913,18 @@ class PyGameGUI: for websocket in self.websockets.values(): websocket.close() - def play_bell_sound(self): + @staticmethod + def play_bell_sound(): + """Plays a bell sound when the game starts.""" bell_path = str(ROOT_DIR / "pygame_2d_vis" / "sync_bell.wav") mixer.init() mixer.music.load(bell_path) - mixer.music.set_volume(0.9) + mixer.music.set_volume(0.7) mixer.music.play() log.log(logging.INFO, "Started game, played bell sound") def start_study(self): + """Starts the study on the study server.""" answer = requests.post( f"{self.request_url}/start_study/{self.participant_id}/{self.number_humans_to_be_added}" ) @@ -1874,6 +1940,7 @@ class PyGameGUI: ) def send_level_done(self): + """Sends a message to the study server that the level was finished.""" answer = requests.post(f"{self.request_url}/level_done/{self.participant_id}") if answer.status_code != 200: log.warning( @@ -1883,22 +1950,24 @@ class PyGameGUI: ) def button_continue_postgame_pressed(self): + """Handles the continue button press on the postgame screen. If the study server is used, the connection to + a new game is set up. Otherwise, the start screen is shown. + """ if self.CONNECT_WITH_STUDY_SERVER: if not self.last_level: self.get_game_connection() else: - # self.current_layout_idx += 1 self.menu_state = MenuStates.Start return - # self.create_env_on_game_server(tutorial=False) - # if self.current_layout_idx == len(self.layout_file_paths) - 1: - # self.last_level = True - # else: - # log.debug(f"LEVEL: {self.layout_file_paths[self.current_layout_idx]}") - self.menu_state = MenuStates.PreGame def manage_button_event(self, button: pygame_gui.core.UIElement | None = None): + """Manages the button events. The button events are filtered by the current menu state and the button that + was pressed. + + Args: + button: The button that was pressed. + """ if button == self.quit_button: if self.fullscreen: self.fullscreen_button_press() @@ -1926,7 +1995,7 @@ class PyGameGUI: ): pass else: - self.menu_state = MenuStates.ControllerTutorial + self.menu_state = MenuStates.Tutorial if self.CONNECT_WITH_STUDY_SERVER: self.get_game_connection() else: @@ -1954,7 +2023,7 @@ class PyGameGUI: ############################################ - case MenuStates.ControllerTutorial: + case MenuStates.Tutorial: self.exit_tutorial() if self.CONNECT_WITH_STUDY_SERVER: self.start_study() @@ -1984,6 +2053,89 @@ class PyGameGUI: ############################################ + def add_joystick(self, event: pygame.event.Event): + """Adds a joystick to the list of joysticks and assigns it to a key set. + A pygame.JOYDEVICEADDED event is generated for every joystick connected at the start of the program. + + Args: + event: The event that is triggered when a joystick is connected. + """ + joy = pygame.joystick.Joystick(event.device_index) + self.joysticks[joy.get_instance_id()] = joy + + for key_set in self.key_sets: + if key_set.joystick is None: + key_set.joystick = joy.get_instance_id() + break + log.debug(f"Joystick {joy.get_instance_id()} connected") + + def remove_joystick(self, event: pygame.event.Event): + """Removes a joystick from the list of joysticks and unassigns it from a key set. + + Args: + event: The event that is triggered when a joystick is disconnected. + """ + del self.joysticks[event.instance_id] + for key_set in self.key_sets: + if key_set.joystick == event.instance_id: + key_set.joystick = None + log.debug(f"Joystick {event.instance_id} disconnected") + log.debug(f"Number of joysticks:" + str(pygame.joystick.get_count())) + + def process_gui_event(self, event: pygame.event.Event): + """Processes the pygame events. The events are filtered by their type and the current menu state. + + Args: + event: The pygame event that is processed. + """ + if event.type == pygame.QUIT: + self.disconnect_websockets() + self.running = False + + if event.type == pygame.JOYDEVICEADDED and pygame.joystick.get_count() > 0: + self.add_joystick(event) + if event.type == pygame.JOYDEVICEREMOVED: + self.remove_joystick(event) + + # Press enter key or controller start button instead of mouse button press + if ( + event.type == pygame.JOYBUTTONDOWN + and any([joy.get_button(7) for joy in self.joysticks.values()]) + or (event.type == pygame.KEYDOWN and event.key == pygame.K_RETURN) + ): + if self.menu_state == MenuStates.Start: + self.manage_button_event(self.start_button) + self.update_screen_elements() + + elif self.menu_state in [ + MenuStates.Tutorial, + MenuStates.PreGame, + MenuStates.PostGame, + ]: + self.manage_button_event(self.continue_button) + self.update_screen_elements() + + if event.type == pygame_gui.UI_BUTTON_PRESSED: + button = event.ui_element + self.manage_button_event(button) + if button in [ + self.start_button, + self.continue_button, + self.finish_study_button, + self.next_game_button, + ]: + self.update_screen_elements() + elif self.menu_state == MenuStates.Start: + self.update_selection_elements() + + if self.menu_state in [MenuStates.Game, MenuStates.Tutorial]: + if event.type in [pygame.KEYDOWN, pygame.KEYUP]: + self.handle_key_event(event) + if event.type in [pygame.JOYBUTTONDOWN, pygame.JOYBUTTONUP]: + self.handle_joy_stick_event(event, joysticks=self.joysticks) + + self.manager.process_events(event) + 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") @@ -1994,17 +2146,14 @@ class PyGameGUI: clock = pygame.time.Clock() - self.reset_window_size() + self.set_window_size() self.init_ui_elements() - self.reset_window_size() self.reset_gui_values() self.update_screen_elements() # Game loop self.running = True - # This dict can be left as-is, since pygame will generate a - # pygame.JOYDEVICEADDED event for every joystick connected - # at the start of the program. + # pygame.JOYDEVICEADDED event is generated for every joystick connected at the start of the program. self.joysticks = {} while self.running: @@ -2013,120 +2162,22 @@ class PyGameGUI: # PROCESSING EVENTS for event in pygame.event.get(): - if event.type == pygame.QUIT: - self.disconnect_websockets() - self.running = False - - # connect joystick - if ( - pygame.joystick.get_count() > 0 - and event.type == pygame.JOYDEVICEADDED - ): - # This event will be generated when the program starts for every - # joystick, filling up the list without needing to create them manually. - joy = pygame.joystick.Joystick(event.device_index) - self.joysticks[joy.get_instance_id()] = joy - - for key_set in self.key_sets: - if key_set.joystick is None: - key_set.joystick = joy.get_instance_id() - break - log.debug(f"Joystick {joy.get_instance_id()} connected") - - # disconnect joystick - if event.type == pygame.JOYDEVICEREMOVED: - del self.joysticks[event.instance_id] - for key_set in self.key_sets: - if key_set.joystick == event.instance_id: - key_set.joystick = None - log.debug(f"Joystick {event.instance_id} disconnected") - log.debug(f"Number of joysticks:"+str(pygame.joystick.get_count())) - - # Press enter key or controller start button instead of mouse button press - if ( - event.type == pygame.JOYBUTTONDOWN - and any( - [ - joy.get_button(7) for joy in self.joysticks.values() - ] - ) - or ( - event.type == pygame.KEYDOWN - and event.key == pygame.K_RETURN - ) - ): - if self.menu_state == MenuStates.Start: - self.manage_button_event(self.start_button) - self.update_screen_elements() - - elif self.menu_state in [ - MenuStates.ControllerTutorial, - MenuStates.PreGame, - MenuStates.PostGame, - ]: - self.manage_button_event(self.continue_button) - self.update_screen_elements() - - if event.type == pygame_gui.UI_BUTTON_PRESSED: - button = event.ui_element - self.manage_button_event(button) - if button in [ - self.start_button, - self.continue_button, - self.finish_study_button, - self.next_game_button, - ]: - self.update_screen_elements() - elif self.menu_state == MenuStates.Start: - self.update_selection_elements() - if event.type in [ - pygame.KEYDOWN, - pygame.KEYUP, - ] and self.menu_state in [ - MenuStates.Game, - MenuStates.ControllerTutorial, - ]: - self.handle_key_event(event) - - if event.type in [ - pygame.JOYBUTTONDOWN, - pygame.JOYBUTTONUP, - ] and self.menu_state in [ - MenuStates.Game, - MenuStates.ControllerTutorial, - ]: - self.handle_joy_stick_event(event, joysticks=self.joysticks) - - self.manager.process_events(event) + self.process_gui_event(event) # DRAWING self.draw_main_window() except (KeyboardInterrupt, SystemExit): self.running = False - self.disconnect_websockets() - if not self.CONNECT_WITH_STUDY_SERVER: - self.stop_game_on_server("Program exited.") - if self.fullscreen_button: - self.fullscreen_button_press() self.disconnect_websockets() if not self.CONNECT_WITH_STUDY_SERVER: self.stop_game_on_server("Program exited.") - if self.fullscreen: self.fullscreen_button_press() pygame.quit() sys.exit() - def exit_tutorial(self): - self.disconnect_websockets() - self.menu_state = MenuStates.PreGame - if self.CONNECT_WITH_STUDY_SERVER: - self.send_tutorial_finished() - else: - self.stop_game_on_server("tutorial_finished") - def main( study_url: str, @@ -2156,7 +2207,8 @@ if __name__ == "__main__": parser = argparse.ArgumentParser( prog="Cooperative Cuisine 2D PyGame Visualization", description="PyGameGUI: a PyGame 2D Visualization window.", - epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html", + epilog="For further information, " + "see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html", ) url_and_port_arguments(parser) @@ -2172,4 +2224,4 @@ if __name__ == "__main__": args.manager_ids, CONNECT_WITH_STUDY_SERVER=True, debug=args.do_study, - ) \ No newline at end of file + )