diff --git a/overcooked_simulator/gui_2d_vis/drawing.py b/overcooked_simulator/gui_2d_vis/drawing.py index d2f44c42de154e0f98b59bb02c572518422c7a63..d2d96231c56d2338d9e790e011eb86faac0c3527 100644 --- a/overcooked_simulator/gui_2d_vis/drawing.py +++ b/overcooked_simulator/gui_2d_vis/drawing.py @@ -67,9 +67,10 @@ class Visualizer: def draw_gamescreen( self, - screen, - state, - grid_size, + screen: pygame.Surface, + state: dict, + grid_size: int, + controlled_player_idxs: list[int], ): width = int(np.ceil(state["kitchen"]["width"] * grid_size)) height = int(np.ceil(state["kitchen"]["height"] * grid_size)) @@ -85,14 +86,20 @@ class Visualizer: grid_size, ) + for idx, col in zip(controlled_player_idxs, [colors["blue"], colors["red"]]): + pygame.draw.circle( + screen, + col, + np.array(state["players"][idx]["pos"]) * grid_size + (grid_size // 2), + (grid_size / 2), + ) + self.draw_players( screen, state["players"], grid_size, ) - print(state) - def draw_background(self, surface, width, height, grid_size): """Visualizes a game background.""" block_size = grid_size // 2 # Set the size of the grid block @@ -152,7 +159,7 @@ class Visualizer: if USE_PLAYER_COOK_SPRITES: pygame.draw.circle( screen, - self.player_colors[p_idx], + colors[self.player_colors[p_idx]], pos - facing * grid_size * 0.25, grid_size * 0.2, ) diff --git a/overcooked_simulator/gui_2d_vis/gui_theme.json b/overcooked_simulator/gui_2d_vis/gui_theme.json index cabbe0368805a697727c738673b1783ac1e56e7e..37c116f45629adcf8aa960e01ac0bc1a60c0a564 100644 --- a/overcooked_simulator/gui_2d_vis/gui_theme.json +++ b/overcooked_simulator/gui_2d_vis/gui_theme.json @@ -92,5 +92,10 @@ "normal_border": "#000000", "normal_text": "#000000" } + }, + "#players": { + "colours": { + "normal_bg": "#fffacd" + } } } \ No newline at end of file diff --git a/overcooked_simulator/gui_2d_vis/overcooked_gui.py b/overcooked_simulator/gui_2d_vis/overcooked_gui.py index 7922bc2fedf5de237a620e01ef0ad2496e942da9..c50760996cc3092f942ae80fe17ce10453fb1c3d 100644 --- a/overcooked_simulator/gui_2d_vis/overcooked_gui.py +++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py @@ -45,7 +45,14 @@ class PlayerKeySet: First four keys are for movement. Order: Down, Up, Left, Right. 5th key is for interacting with counters. 6th key ist for picking up things or dropping them. """ - def __init__(self, player_name: str | int, keys: list[pygame.key]): + def __init__( + self, + move_keys: list[pygame.key], + interact_key: pygame.key, + pickup_key: pygame.key, + switch_key: pygame.key, + players: list[int], + ): """Creates a player key set which contains information about which keyboard keys control the player. Movement keys in the following order: Down, Up, Left, Right @@ -54,14 +61,26 @@ class PlayerKeySet: player_name: The name of the player to control. keys: The keys which control this player in the following order: Down, Up, Left, Right, Interact, Pickup. """ - self.name = player_name - 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.move_vectors: list[list[int]] = [[-1, 0], [1, 0], [0, -1], [0, 1]] + self.key_to_movement: dict[pygame.key, list[int]] = { + key: vec for (key, vec) in zip(move_keys, self.move_vectors) } - self.interact_key = self.player_keys[-2] - self.pickup_key = self.player_keys[-1] + self.move_keys: list[pygame.key] = move_keys + self.interact_key: pygame.key = interact_key + self.pickup_key: pygame.key = pickup_key + self.switch_key: pygame.key = switch_key + self.controlled_players: list[int] = players + self.current_player: int = players[0] + + def set_controlled_players(self, controlled_players: list[int]) -> None: + self.controlled_players = controlled_players + self.current_player = self.controlled_players[0] + + def next_player(self) -> None: + self.current_player += 1 + if self.current_player > self.controlled_players[-1]: + self.current_player = self.controlled_players[0] + print(self.current_player, self.controlled_players) class PyGameGUI: @@ -69,8 +88,6 @@ class PyGameGUI: def __init__( self, - player_names: list[str | int], - player_keys: list[pygame.key], url: str, port: int, manager_ids: list[str], @@ -79,15 +96,8 @@ class PyGameGUI: self.FPS = 60 self.running = True - self.player_names = player_names - self.player_keys = player_keys - - self.player_key_sets: list[PlayerKeySet] = [ - PlayerKeySet(player_name, keys) - for player_name, keys in zip( - self.player_names, self.player_keys[: len(self.player_names)] - ) - ] + self.reset_gui_values() + self.key_sets: list[PlayerKeySet] = self.setup_player_keys(1) self.websocket_url = f"ws://{url}:{port}/ws/player/" self.websockets = {} @@ -124,7 +134,6 @@ class PyGameGUI: self.manager: pygame_gui.UIManager self.vis = Visualizer(self.visualization_config) - self.vis.create_player_colors(len(self.player_names)) def get_window_sizes(self, state: dict): kitchen_width = state["kitchen"]["width"] @@ -167,23 +176,53 @@ class PyGameGUI: grid_size, ) + def setup_player_keys(self, n=1, disjunct=False): + key_set1 = PlayerKeySet( + move_keys=[pygame.K_a, pygame.K_d, pygame.K_w, pygame.K_s], + interact_key=pygame.K_f, + pickup_key=pygame.K_e, + switch_key=pygame.K_SPACE, + players=list(range(self.number_humans_to_be_added)), + ) + key_set2 = PlayerKeySet( + move_keys=[pygame.K_LEFT, pygame.K_RIGHT, pygame.K_UP, pygame.K_DOWN], + interact_key=pygame.K_i, + pickup_key=pygame.K_o, + switch_key=pygame.K_p, + players=list(range(self.number_humans_to_be_added)), + ) + key_sets = [key_set1, key_set2] + + if disjunct: + split_idx = int(np.ceil(self.number_humans_to_be_added / 2)) + key_set1.set_controlled_players(list(range(0, split_idx))) + key_set2.set_controlled_players( + list(range(split_idx, self.number_humans_to_be_added)) + ) + return key_sets[:n] + 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, key_set in enumerate(self.player_key_sets): - relevant_keys = [keys[k] for k in key_set.player_keys] - if any(relevant_keys[:-2]): + for key_set in self.key_sets: + current_player_name = str(key_set.current_player) + relevant_keys = [keys[k] for k in key_set.move_keys] + if any(relevant_keys): move_vec = np.zeros(2) - for idx, pressed in enumerate(relevant_keys[:-2]): + for idx, pressed in enumerate(relevant_keys): if pressed: move_vec += key_set.move_vectors[idx] if np.linalg.norm(move_vec) != 0: move_vec = move_vec / np.linalg.norm(move_vec) action = Action( - key_set.name, ActionType.MOVEMENT, move_vec, duration=1 / self.FPS + current_player_name, + ActionType.MOVEMENT, + move_vec, + duration=1 / self.FPS, ) self.send_action(action) @@ -195,22 +234,27 @@ class PyGameGUI: Args: event: Pygame event for extracting the key action. """ - for key_set in self.player_key_sets: + + for key_set in self.key_sets: + current_player_name = str(key_set.current_player) if event.key == key_set.pickup_key and event.type == pygame.KEYDOWN: - action = Action(key_set.name, ActionType.PUT, "pickup") + action = Action(current_player_name, ActionType.PUT, "pickup") self.send_action(action) if event.key == key_set.interact_key: if event.type == pygame.KEYDOWN: action = Action( - key_set.name, ActionType.INTERACT, InterActionData.START + current_player_name, ActionType.INTERACT, InterActionData.START ) self.send_action(action) elif event.type == pygame.KEYUP: action = Action( - key_set.name, ActionType.INTERACT, InterActionData.STOP + current_player_name, ActionType.INTERACT, InterActionData.STOP ) self.send_action(action) + if event.key == key_set.switch_key: + if event.type == pygame.KEYDOWN: + key_set.next_player() def init_ui_elements(self): self.manager = pygame_gui.UIManager((self.window_width, self.window_height)) @@ -218,28 +262,28 @@ class PyGameGUI: self.start_button = pygame_gui.elements.UIButton( relative_rect=pygame.Rect( - ( - (self.window_width // 2) - self.buttons_width // 2, - (self.window_height / 2) - self.buttons_height // 2, - ), - (self.buttons_width, self.buttons_height), + (0, 0), (self.buttons_width, self.buttons_height) ), text="Start Game", manager=self.manager, + anchors={"center": "center"}, ) self.start_button.can_hover() - self.quit_button = pygame_gui.elements.UIButton( - relative_rect=pygame.Rect( - ( - (self.window_width - self.buttons_width), - 0, - ), - (self.buttons_width, self.buttons_height), + quit_rect = pygame.Rect( + ( + 0, + 0, ), + (self.buttons_width, self.buttons_height), + ) + quit_rect.topright = (0, 0) + self.quit_button = pygame_gui.elements.UIButton( + relative_rect=quit_rect, text="Quit Game", manager=self.manager, object_id="#quit_button", + anchors={"right": "right", "top": "top"}, ) self.quit_button.can_hover() @@ -340,6 +384,148 @@ class PyGameGUI: object_id="#score_label", ) + player_selection_rect = pygame.Rect( + (0, 0), + ( + self.window_width, + (self.window_height // 2) - (self.buttons_height // 2), + ), + ) + player_selection_rect.bottom = 0 + self.player_selection_container = pygame_gui.elements.UIPanel( + player_selection_rect, + manager=self.manager, + object_id="#players", + anchors={"bottom": "bottom", "centerx": "centerx"}, + ) + + multiple_keysets_button_rect = pygame.Rect((0, 0), (210, 50)) + self.multiple_keysets_button = pygame_gui.elements.UIButton( + relative_rect=multiple_keysets_button_rect, + manager=self.manager, + container=self.player_selection_container, + text="not set", + anchors={"left": "left", "centery": "centery"}, + object_id="#wasd", + ) + + split_players_button_rect = pygame.Rect((0, 0), (210, 50)) + self.split_players_button = pygame_gui.elements.UIButton( + relative_rect=split_players_button_rect, + manager=self.manager, + container=self.player_selection_container, + text="not set", + anchors={"centerx": "centerx", "centery": "centery"}, + object_id="#checkbox_split", + ) + if self.multiple_keysets: + self.split_players_button.show() + else: + self.split_players_button.hide() + + xbox_controller_button_rect = pygame.Rect((0, 0), (210, 50)) + xbox_controller_button_rect.right = 0 + self.xbox_controller_button = pygame_gui.elements.UIButton( + relative_rect=xbox_controller_button_rect, + manager=self.manager, + container=self.player_selection_container, + text="Use game controller?", + anchors={"right": "right", "centery": "centery"}, + object_id="#controller", + ) + + players_container_rect = pygame.Rect( + (0, 0), + (500, self.player_selection_container.get_abs_rect().height // 3), + ) + self.players_container = pygame_gui.elements.UIPanel( + relative_rect=players_container_rect, + manager=self.manager, + object_id="#players_players", + container=self.player_selection_container, + anchors={"top": "top", "centerx": "centerx"}, + ) + + bot_container_rect = pygame.Rect( + (0, 0), + (500, self.player_selection_container.get_abs_rect().height // 3), + ) + bot_container_rect.bottom = 0 + self.bots_container = pygame_gui.elements.UIPanel( + relative_rect=bot_container_rect, + manager=self.manager, + object_id="#players_bots", + container=self.player_selection_container, + anchors={"bottom": "bottom", "centerx": "centerx"}, + ) + + number_players_rect = pygame.Rect((0, 0), (200, 200)) + self.added_players_label = pygame_gui.elements.UILabel( + number_players_rect, + manager=self.manager, + container=self.players_container, + text=f"Humans to be added: {self.number_humans_to_be_added}", + anchors={"center": "center"}, + ) + + number_bots_rect = pygame.Rect((0, 0), (200, 200)) + self.added_bots_label = pygame_gui.elements.UILabel( + number_bots_rect, + manager=self.manager, + container=self.bots_container, + text=f"Bots to be added: {self.number_bots_to_be_added}", + anchors={"center": "center"}, + ) + + size = 70 + add_player_button_rect = pygame.Rect((0, 0), (size, size)) + add_player_button_rect.right = 0 + self.add_human_player_button = pygame_gui.elements.UIButton( + relative_rect=add_player_button_rect, + text="+", + manager=self.manager, + object_id="#add_player", + container=self.players_container, + anchors={"right": "right", "centery": "centery"}, + ) + self.add_human_player_button.can_hover() + + remove_player_button_rect = pygame.Rect((0, 0), (size, size)) + remove_player_button_rect.left = 0 + self.remove_human_button = pygame_gui.elements.UIButton( + relative_rect=remove_player_button_rect, + text="-", + manager=self.manager, + object_id="#remove_player", + container=self.players_container, + anchors={"left": "left", "centery": "centery"}, + ) + self.remove_human_button.can_hover() + + add_bot_button_rect = pygame.Rect((0, 0), (size, size)) + add_bot_button_rect.right = 0 + self.add_bot_button = pygame_gui.elements.UIButton( + relative_rect=add_bot_button_rect, + text="+", + manager=self.manager, + object_id="#add_bot", + container=self.bots_container, + anchors={"right": "right", "centery": "centery"}, + ) + self.add_bot_button.can_hover() + + remove_bot_button_rect = pygame.Rect((0, 0), (size, size)) + remove_bot_button_rect.left = 0 + self.remove_bot_button = pygame_gui.elements.UIButton( + relative_rect=remove_bot_button_rect, + text="-", + manager=self.manager, + object_id="#remove_bot", + container=self.bots_container, + anchors={"left": "left", "centery": "centery"}, + ) + self.remove_bot_button.can_hover() + def draw(self, state): """Main visualization function. @@ -348,6 +534,7 @@ class PyGameGUI: self.game_screen, state, self.grid_size, + [k.current_player for k in self.key_sets], ) # self.manager.draw_ui(self.main_window) @@ -414,6 +601,8 @@ class PyGameGUI: self.timer_label.hide() self.orders_label.hide() self.conclusion_label.hide() + + self.player_selection_container.show() case MenuStates.Game: self.start_button.hide() self.back_button.hide() @@ -425,6 +614,9 @@ class PyGameGUI: self.timer_label.show() self.orders_label.show() self.conclusion_label.hide() + + self.player_selection_container.hide() + case MenuStates.End: self.start_button.hide() self.back_button.show() @@ -436,6 +628,8 @@ class PyGameGUI: self.orders_label.hide() self.conclusion_label.show() + self.player_selection_container.hide() + def update_score_label(self, state): score = state["score"] self.score_label.set_text(f"Score {score}") @@ -460,9 +654,10 @@ class PyGameGUI: layout = file.read() with open(environment_config_path, "r") as file: environment_config = file.read() + creation_json = CreateEnvironmentConfig( manager_id=self.manager_id, - number_players=2, + number_players=self.number_players, environment_settings={"all_player_can_pause_game": False}, item_info_config=item_info, environment_config=environment_config, @@ -503,6 +698,20 @@ class PyGameGUI: def start_button_press(self): self.menu_state = MenuStates.Game + self.number_players = ( + self.number_humans_to_be_added + self.number_bots_to_be_added + ) + self.vis.create_player_colors(self.number_players) + + if self.split_players: + assert ( + self.number_humans_to_be_added > 1 + ), "Not enough players for key configuration." + num_key_set = 2 if self.multiple_keysets else 1 + self.key_sets = self.setup_player_keys( + max(self.number_players, num_key_set), self.split_players + ) + self.setup_environment() self.set_window_size() @@ -515,6 +724,9 @@ class PyGameGUI: def back_button_press(self): self.menu_state = MenuStates.Start self.reset_window_size() + + self.reset_gui_values() + log.debug("Pressed back button") def quit_button_press(self): @@ -523,6 +735,8 @@ class PyGameGUI: log.debug("Pressed quit button") def reset_button_press(self): + # self.reset_gui_values() + requests.post( f"{self.request_url}/manage/stop_env", json={ @@ -548,6 +762,31 @@ class PyGameGUI: self.reset_window_size() log.debug("Pressed finished button") + def reset_gui_values(self): + self.currently_controlled_player_idx = 0 + self.number_humans_to_be_added = 1 + self.number_bots_to_be_added = 0 + self.split_players = False + self.multiple_keysets = False + + def update_selection_elements(self): + text = "WASD+ARROW" if self.multiple_keysets else "WASD" + self.multiple_keysets_button.set_text(text) + self.split_players_button + self.added_players_label.set_text( + f"Humans to be added: {self.number_humans_to_be_added}" + ) + self.added_bots_label.set_text( + f"Bots to be added: {self.number_bots_to_be_added}" + ) + text = "Yes" if self.split_players else "No" + self.split_players_button.set_text(f"Split players: {text}") + + if self.multiple_keysets: + self.split_players_button.show() + else: + self.split_players_button.hide() + def send_action(self, action: Action): """Sends an action to the game environment. @@ -583,8 +822,6 @@ class PyGameGUI: } ) ) - # self.websocket.send(json.dumps("get_state")) - # state_dict = json.loads(self.websocket.recv()) state = json.loads(self.websockets[self.state_player_id].recv()) return state @@ -606,6 +843,8 @@ class PyGameGUI: self.init_ui_elements() self.manage_button_visibility() + self.update_selection_elements() + # Game loop self.running = True while self.running: @@ -636,6 +875,28 @@ class PyGameGUI: self.disconnect_websockets() self.start_button_press() + case self.add_human_player_button: + self.number_humans_to_be_added += 1 + case self.remove_human_button: + self.number_humans_to_be_added = max( + 0, self.number_humans_to_be_added - 1 + ) + case self.add_bot_button: + self.number_bots_to_be_added += 1 + case self.remove_bot_button: + self.number_bots_to_be_added = max( + 0, self.number_bots_to_be_added - 1 + ) + case self.multiple_keysets_button: + self.multiple_keysets = not self.multiple_keysets + self.split_players = False + case self.split_players_button: + self.split_players = not self.split_players + case self.xbox_controller_button: + print("xbox_controller_button pressed.") + + self.update_selection_elements() + self.manage_button_visibility() if ( @@ -693,21 +954,7 @@ class PyGameGUI: def main(url: str, port: int, manager_ids: list[str]): - # TODO maybe read the player names and keyboard keys from config file? - 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] - - number_players = 2 gui = PyGameGUI( - list(map(str, range(number_players))), - [keys1, keys2], url=url, port=port, manager_ids=manager_ids,