diff --git a/overcooked_simulator/__main__.py b/overcooked_simulator/__main__.py index f48180ac4aa23e525646e844d9e53b9f49a71595..668071aebe23ac0f55a8829d8bab3b07e4d3a483 100644 --- a/overcooked_simulator/__main__.py +++ b/overcooked_simulator/__main__.py @@ -8,6 +8,8 @@ from overcooked_simulator.utils import ( add_list_of_manager_ids_arguments, ) +USE_STUDY_SERVER = True + def start_game_server(cli_args): from overcooked_simulator.game_server import main @@ -15,40 +17,92 @@ def start_game_server(cli_args): main(cli_args.url, cli_args.port, cli_args.manager_ids) +def start_study_server(cli_args): + from overcooked_simulator.example_study_server import main + + main( + cli_args.url, + cli_args.port, + game_server_url_="localhost:8000", + manager_ids=cli_args.manager_ids, + ) + + def start_pygame_gui(cli_args): from overcooked_simulator.gui_2d_vis.overcooked_gui import main - main(cli_args.url, cli_args.port, cli_args.manager_ids) + main( + cli_args.url, + cli_args.port, + cli_args.manager_ids, + CONNECT_WITH_STUDY_SERVER=USE_STUDY_SERVER, + ) def main(cli_args=None): + + study_server = None + study_server_args = None + if USE_STUDY_SERVER: + parser = argparse.ArgumentParser( + prog="Overcooked Simulator Study Server", + description="Study Server: Match Making, client pre and post managing.", + epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html", + ) + url_and_port_arguments( + parser=parser, server_name="Study Server", default_port=8080 + ) + add_list_of_manager_ids_arguments(parser=parser) + study_server_args = parser.parse_args() + if cli_args is None: parser = argparse.ArgumentParser( prog="Overcooked Simulator", description="Game Engine Server + PyGameGUI: Starts overcooked game engine server and a PyGame 2D Visualization window in two processes.", epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html", ) - url_and_port_arguments(parser) disable_websocket_logging_arguments(parser) add_list_of_manager_ids_arguments(parser) cli_args = parser.parse_args() + game_server = None pygame_gui = None try: print("Start game engine:") game_server = Process(target=start_game_server, args=(cli_args,)) game_server.start() - time.sleep(1) + time.sleep(0.5) + + if USE_STUDY_SERVER: + print("Start study server:") + study_server = Process(target=start_study_server, args=(study_server_args,)) + study_server.start() + time.sleep(0.5) + print("Start PyGame GUI:") pygame_gui = Process(target=start_pygame_gui, args=(cli_args,)) pygame_gui.start() - while pygame_gui.is_alive() and game_server.is_alive(): + + if USE_STUDY_SERVER: + print("Start PyGame GUI:") + pygame_gui_2 = Process(target=start_pygame_gui, args=(cli_args,)) + pygame_gui_2.start() + + print("Start PyGame GUI:") + pygame_gui_3 = Process(target=start_pygame_gui, args=(cli_args,)) + pygame_gui_3.start() + + while pygame_gui.is_alive(): time.sleep(1) + except KeyboardInterrupt: print("Received Keyboard interrupt") finally: + if USE_STUDY_SERVER and study_server is not None and study_server.is_alive(): + print("Terminate study server") + study_server.terminate() if game_server is not None and game_server.is_alive(): print("Terminate game server") game_server.terminate() diff --git a/overcooked_simulator/example_study_server.py b/overcooked_simulator/example_study_server.py index 778135f47054e0cdc4a948f7f3a1098b95542426..663976d3a9638bf70b639a3d7342839e722802e4 100644 --- a/overcooked_simulator/example_study_server.py +++ b/overcooked_simulator/example_study_server.py @@ -14,10 +14,12 @@ The environment starts when all players connected. import argparse import asyncio import logging -from typing import Tuple +from pathlib import Path +from typing import Tuple, TypedDict import requests import uvicorn +import yaml from fastapi import FastAPI from overcooked_simulator import ROOT_DIR @@ -32,7 +34,6 @@ NUMBER_PLAYER_PER_ENV = 2 log = logging.getLogger(__name__) - app = FastAPI() game_server_url = "localhost:8000" @@ -53,48 +54,74 @@ server_manager_id = None # </html> # """ -running_envs: dict[str, Tuple[int, dict[str, PlayerInfo], list[str]]] = {} -current_free_envs = [] + +HARDCODED_MANAGER_ID = "1234" + + +running_tutorials: dict[str, Tuple[int, dict[str, PlayerInfo], list[str]]] = {} + + +class LevelConfig(TypedDict): + name: str + config_path: str + layout_path: str + item_info_path: str + + +class StudyConfig(TypedDict): + levels: list[LevelConfig] + num_players: int -@app.post("/connect_to_game/{request_id}") -async def want_to_play(request_id: str): - global current_free_envs - # TODO based on study desing / internal state of request id current state (which level to play) - if current_free_envs: - current_free_env = current_free_envs.pop() +class StudyState: + def __init__(self, study_config_path: str | Path): + with open(study_config_path, "r") as file: + env_config_f = file.read() - running_envs[current_free_env][2].append(request_id) - new_running_env = ( - running_envs[current_free_env][0] + 1, - running_envs[current_free_env][1], - running_envs[current_free_env][2], + self.study_config: StudyConfig = yaml.load( + str(env_config_f), Loader=yaml.SafeLoader ) - player_info = running_envs[current_free_env][1][str(new_running_env[0])] - running_envs[current_free_env] = new_running_env - if new_running_env[0] < NUMBER_PLAYER_PER_ENV - 1: - current_free_env.append(current_free_env) - return player_info - else: - environment_config_path = ROOT_DIR / "game_content" / "environment_config.yaml" - layout_path = ROOT_DIR / "game_content" / "layouts" / "basic.layout" - item_info_path = ROOT_DIR / "game_content" / "item_info.yaml" - with open(item_info_path, "r") as file: + self.levels: list[LevelConfig] = self.study_config["levels"] + self.current_level_idx: int = 0 + + self.participant_id_to_player_info = {} + self.player_ids = {} + self.num_connected_players: int = 0 + + self.current_running_env = None + self.next_level_env = None + self.players_done = {} + + @property + def study_done(self): + return self.current_level_idx >= len(self.levels) + + @property + def is_full(self): + return ( + len(self.participant_id_to_player_info) == self.study_config["num_players"] + ) + + def create_env(self, level): + with open(ROOT_DIR / "game_content" / level["item_info_path"], "r") as file: item_info = file.read() - with open(layout_path, "r") as file: + with open( + ROOT_DIR / "game_content" / "layouts" / level["layout_path"], "r" + ) as file: layout = file.read() - with open(environment_config_path, "r") as file: + with open(ROOT_DIR / "game_content" / level["config_path"], "r") as file: environment_config = file.read() + creation_json = CreateEnvironmentConfig( manager_id=server_manager_id, - number_players=NUMBER_PLAYER_PER_ENV, + number_players=self.study_config["num_players"], environment_settings={"all_player_can_pause_game": False}, item_info_config=item_info, environment_config=environment_config, layout_config=layout, seed=1234567890, ).model_dump(mode="json") - # todo async + env_info = requests.post( game_server_url + "/manage/create_env/", json=creation_json ) @@ -102,16 +129,179 @@ async def want_to_play(request_id: str): if env_info.status_code == 403: raise ValueError(f"Forbidden Request: {env_info.json()['detail']}") env_info = env_info.json() - print(env_info) - running_envs[env_info["env_id"]] = (0, env_info["player_info"], [request_id]) - current_free_envs.append(env_info["env_id"]) - return env_info["player_info"]["0"] + return env_info + + def start(self): + level = self.levels[self.current_level_idx] + print("CREATING LEVEL:", level["name"]) + self.current_running_env = self.create_env(level) + # if len(self.levels) > 1: + # next_level = self.levels[self.current_level_idx] + # print("CREATING NEXT LEVEL:", next_level["name"]) + # self.next_level_env = self.create_env(next_level) + + def next_level(self): + requests.post( + f"{game_server_url}/manage/stop_env/", + json={ + "manager_id": HARDCODED_MANAGER_ID, + "env_id": self.current_running_env["env_id"], + "reason": "Next level", + }, + ) + + self.current_level_idx += 1 + if not self.study_done: + level = self.levels[self.current_level_idx] + print("CREATING LEVEL:", level["name"]) + self.current_running_env = self.create_env(level) + for participant_id, player_id in self.player_ids.items(): + player_id = self.player_ids[participant_id] + self.participant_id_to_player_info[ + participant_id + ] = self.current_running_env["player_info"][player_id] + + for key in self.players_done: + self.players_done[key] = False + return False + else: + return True + + def add_participant(self, participant_id: str): + player_name = str(self.num_connected_players) + player_info = self.current_running_env["player_info"][player_name] + self.participant_id_to_player_info[participant_id] = player_info + self.player_ids[participant_id] = player_info["player_id"] + self.num_connected_players += 1 + return player_info + + def player_finished_level(self, participant_id): + self.players_done[participant_id] = True + level_done = all(self.players_done.values()) + if level_done: + self.next_level() + return {"study_finished": self.study_done} + + def get_connection(self, participant_id: str): + player_info = self.participant_id_to_player_info[participant_id] + return player_info + + +class StudyManager: + def __init__(self): + self.running_studies: list[StudyState] = [] -def main(host, port, game_server_url_, manager_id): + self.participant_id_to_study_map: dict[str, StudyState] = {} + self.running_envs: dict[str, Tuple[int, dict[str, PlayerInfo], list[str]]] = {} + self.current_free_envs = [] + + def create_study(self): + study = StudyState(ROOT_DIR / "game_content" / "study_config.yaml") + study.start() + self.running_studies.append(study) + + def add_participant(self, participant_id): + player_info = None + if all([s.is_full for s in self.running_studies]): + self.create_study() + + for study in self.running_studies: + if not study.is_full: + player_info = study.add_participant(participant_id) + self.participant_id_to_study_map[participant_id] = study + return player_info + + def player_finished_level(self, participant_id: str): + assigned_study = self.participant_id_to_study_map[participant_id] + return assigned_study.player_finished_level(participant_id) + + def get_participant_game_connection(self, participant_id: str): + assigned_study = self.participant_id_to_study_map[participant_id] + return assigned_study.participant_id_to_player_info[participant_id] + + +study_manager = StudyManager() + + +@app.post("/start_study/{participant_id}") +async def start_study(participant_id: str): + player_info = study_manager.add_participant(participant_id) + print() + return player_info + + +@app.post("/level_done/{participant_id}") +async def next_level(participant_id: str): + is_there_next_level = study_manager.player_finished_level(participant_id) + return is_there_next_level + + +@app.post("/get_game_connection/{participant_id}") +async def finished_game(participant_id: str): + return study_manager.get_participant_game_connection(participant_id) + + +@app.post("/finished_game/{participant_id}") +async def finished_game(participant_id: str): + print(f"{participant_id} finished game.") + return None + + +@app.post("/connect_to_tutorial/{participant_id}") +async def want_to_play_tutorial(participant_id: str): + environment_config_path = ( + ROOT_DIR / "game_content" / "tutorial" / "tutorial_env_config.yaml" + ) + layout_path = ROOT_DIR / "game_content" / "tutorial" / "tutorial.layout" + item_info_path = ROOT_DIR / "game_content" / "item_info.yaml" + with open(item_info_path, "r") as file: + item_info = file.read() + with open(layout_path, "r") as file: + layout = file.read() + with open(environment_config_path, "r") as file: + environment_config = file.read() + creation_json = CreateEnvironmentConfig( + manager_id=server_manager_id, + number_players=1, + environment_settings={"all_player_can_pause_game": False}, + item_info_config=item_info, + environment_config=environment_config, + layout_config=layout, + seed=1234567890, + ).model_dump(mode="json") + # todo async + print("CREATING TUTORIAL ENVIRONMENT") + env_info = requests.post( + game_server_url + "/manage/create_env/", json=creation_json + ) + + if env_info.status_code == 403: + raise ValueError(f"Forbidden Request: {env_info.json()['detail']}") + env_info = env_info.json() + running_tutorials[participant_id] = env_info + return env_info["player_info"]["0"] + + +@app.post("/disconnect_from_tutorial/{participant_id}") +async def want_to_play_tutorial(participant_id: str): + requests.post( + f"{game_server_url}/manage/stop_env/", + json={ + "manager_id": HARDCODED_MANAGER_ID, + "env_id": running_tutorials[participant_id]["env_id"], + "reason": "Finished tutorial", + }, + ) + + +def main(host, port, game_server_url_, manager_ids): global game_server_url, server_manager_id + + manager_ids = ["1234"] + game_server_url = "http://" + game_server_url_ - server_manager_id = manager_id[0] + server_manager_id = manager_ids[0] print(f"Use {server_manager_id=} for {game_server_url=}") loop = asyncio.new_event_loop() config = uvicorn.Config(app, host=host, port=port, loop=loop) @@ -132,5 +322,5 @@ if __name__ == "__main__": args.url, args.port, game_server_url_="localhost:8000", - manager_id=args.manager_ids, + manager_ids=args.manager_ids, ) diff --git a/overcooked_simulator/game_content/environment_config.yaml b/overcooked_simulator/game_content/environment_config.yaml index 9895330f46f1436dfa1d61e9a2ef632a79f16d06..eadb5b3dde08eddc106ffb0b8eb81ccf2aa77280 100644 --- a/overcooked_simulator/game_content/environment_config.yaml +++ b/overcooked_simulator/game_content/environment_config.yaml @@ -5,7 +5,7 @@ plates: # range of seconds until the dirty plate arrives. game: - time_limit_seconds: 100 + time_limit_seconds: 20 meals: all: true diff --git a/overcooked_simulator/game_content/environment_config_dark.yaml b/overcooked_simulator/game_content/environment_config_dark.yaml new file mode 100644 index 0000000000000000000000000000000000000000..b11ae4fdda0647e4cf772698dc961b0659e309d2 --- /dev/null +++ b/overcooked_simulator/game_content/environment_config_dark.yaml @@ -0,0 +1,157 @@ +plates: + clean_plates: 1 + dirty_plates: 2 + plate_delay: [ 5, 10 ] + # range of seconds until the dirty plate arrives. + +game: + time_limit_seconds: 20 + +meals: + all: true + # if all: false -> only orders for these meals are generated + # TODO: what if this list is empty? + list: + - TomatoSoup + - OnionSoup + - Salad + +layout_chars: + _: Free + hash: Counter # # + A: Agent + pipe: Extinguisher + P: PlateDispenser + C: CuttingBoard + X: Trashcan + $: ServingWindow + S: Sink + +: SinkAddon + at: Plate # @ just a clean plate on a counter + U: Pot # with Stove + Q: Pan # with Stove + O: Peel # with Oven + F: Basket # with DeepFryer + T: Tomato + N: Onion # oNioN + L: Lettuce + K: Potato # Kartoffel + I: Fish # fIIIsh + D: Dough + E: Cheese # chEEEse + G: Sausage # sausaGe + B: Bun + M: Meat + question: Counter # ? mushroom + ↓: Counter + ^: Counter + right: Counter + left: Counter + wave: Free # ~ Water + minus: Free # - Ice + dquote: Counter # " wall/truck + p: Counter # second plate return ?? + + +orders: + order_gen_class: !!python/name:overcooked_simulator.order.RandomOrderGeneration '' + # the class to that receives the kwargs. Should be a child class of OrderGeneration in order.py + order_gen_kwargs: + order_duration_random_func: + # how long should the orders be alive + # 'random' library call with getattr, kwargs are passed to the function + func: uniform + kwargs: + a: 40 + b: 60 + max_orders: 6 + # maximum number of active orders at the same time + num_start_meals: 2 + # number of orders generated at the start of the environment + sample_on_dur_random_func: + # 'random' library call with getattr, kwargs are passed to the function + func: uniform + kwargs: + a: 10 + b: 20 + sample_on_serving: false + # Sample the delay for the next order only after a meal was served. + score_calc_gen_func: !!python/name:overcooked_simulator.order.simple_score_calc_gen_func '' + score_calc_gen_kwargs: + # the kwargs for the score_calc_gen_func + other: 20 + scores: + Burger: 15 + OnionSoup: 10 + Salad: 5 + TomatoSoup: 10 + expired_penalty_func: !!python/name:overcooked_simulator.order.simple_expired_penalty '' + expired_penalty_kwargs: + default: -5 + serving_not_ordered_meals: !!python/name:overcooked_simulator.order.serving_not_ordered_meals_with_zero_score '' + # a func that calcs a store for not ordered but served meals. Input: meal + penalty_for_trash: !!python/name:overcooked_simulator.order.penalty_for_each_item '' + # a func that calcs the penalty for items that the player puts into the trashcan. + +player_config: + radius: 0.4 + player_speed_units_per_seconds: 6 + interaction_range: 1.6 + restricted_view: True + view_angle: 70 + view_range: 4.5 # in grid units, can be "null" + +effect_manager: + FireManager: + class: !!python/name:overcooked_simulator.effect_manager.FireEffectManager '' + kwargs: + spreading_duration: [ 5, 10 ] + fire_burns_ingredients_and_meals: true + + +extra_setup_functions: + # json_states: + # func: !!python/name:overcooked_simulator.recording.class_recording_with_hooks '' + # kwargs: + # hooks: [ json_state ] + # log_class: !!python/name:overcooked_simulator.recording.LogRecorder '' + # log_class_kwargs: + # log_path: USER_LOG_DIR/ENV_NAME/json_states.jsonl + actions: + func: !!python/name:overcooked_simulator.recording.class_recording_with_hooks '' + kwargs: + hooks: [ pre_perform_action ] + log_class: !!python/name:overcooked_simulator.recording.LogRecorder '' + log_class_kwargs: + log_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl + random_env_events: + func: !!python/name:overcooked_simulator.recording.class_recording_with_hooks '' + kwargs: + hooks: [ order_duration_sample, plate_out_of_kitchen_time ] + log_class: !!python/name:overcooked_simulator.recording.LogRecorder '' + log_class_kwargs: + log_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl + add_hook_ref: true + env_configs: + func: !!python/name:overcooked_simulator.recording.class_recording_with_hooks '' + kwargs: + hooks: [ env_initialized, item_info_config ] + log_class: !!python/name:overcooked_simulator.recording.LogRecorder '' + log_class_kwargs: + log_path: USER_LOG_DIR/ENV_NAME/LOG_RECORD_NAME.jsonl + add_hook_ref: true +# info_msg: +# func: !!python/name:overcooked_simulator.recording.class_recording_with_hooks '' +# kwargs: +# hooks: [ cutting_board_100 ] +# log_class: !!python/name:overcooked_simulator.info_msg.InfoMsgManager '' +# log_class_kwargs: +# msg: Glückwunsch du hast was geschnitten! +# fire_msg: +# func: !!python/name:overcooked_simulator.recording.class_recording_with_hooks '' +# kwargs: +# hooks: [ new_fire ] +# log_class: !!python/name:overcooked_simulator.info_msg.InfoMsgManager '' +# log_class_kwargs: +# msg: Feuer, Feuer, Feuer +# level: Warning \ No newline at end of file diff --git a/overcooked_simulator/game_content/study_config.yaml b/overcooked_simulator/game_content/study_config.yaml index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..226b16638b0837b0c511fc80794969aa21ba5850 100644 --- a/overcooked_simulator/game_content/study_config.yaml +++ b/overcooked_simulator/game_content/study_config.yaml @@ -0,0 +1,23 @@ +levels: + - config_path: environment_config.yaml + layout_path: overcooked-1/1-1-far-apart.layout + item_info_path: item_info.yaml + name: "Level 1-1: Far Apart" + + - config_path: environment_config.yaml + layout_path: overcooked-1/1-4-bottleneck.layout + item_info_path: item_info.yaml + name: "Level 1-4: Bottleneck" + + # - config_path: environment_config.yaml + # layout_path: overcooked-1/1-5-circle.layout + # item_info_path: item_info.yaml + # name: "Level 1-5: Circle" + + - config_path: environment_config_dark.yaml + layout_path: overcooked-1/4-2-dark.layout + item_info_path: item_info.yaml + name: "Level 4-2: Dark" + + +num_players: 3 diff --git a/overcooked_simulator/game_content/layouts/tutorial.layout b/overcooked_simulator/game_content/tutorial/tutorial.layout similarity index 100% rename from overcooked_simulator/game_content/layouts/tutorial.layout rename to overcooked_simulator/game_content/tutorial/tutorial.layout diff --git a/overcooked_simulator/game_content/tutorial_env_config.yaml b/overcooked_simulator/game_content/tutorial/tutorial_env_config.yaml similarity index 99% rename from overcooked_simulator/game_content/tutorial_env_config.yaml rename to overcooked_simulator/game_content/tutorial/tutorial_env_config.yaml index cff3c29bc1aad863e9c1a8c3e57a5bca57b5d7fe..2d03eb20e13515ff609bdfdc7bc69e9c11363eaf 100644 --- a/overcooked_simulator/game_content/tutorial_env_config.yaml +++ b/overcooked_simulator/game_content/tutorial/tutorial_env_config.yaml @@ -64,7 +64,7 @@ orders: kwargs: a: 40 b: 60 - max_orders: 6 + max_orders: 0 # maximum number of active orders at the same time num_start_meals: 2 # number of orders generated at the start of the environment diff --git a/overcooked_simulator/game_server.py b/overcooked_simulator/game_server.py index 84879915dce1ff120d6f7a99e405466aa9f7adce..6a382616542459a44fc2fe5b9609f4b2f2b90b7e 100644 --- a/overcooked_simulator/game_server.py +++ b/overcooked_simulator/game_server.py @@ -36,7 +36,6 @@ from overcooked_simulator.server_results import ( PlayerRequestResult, ) from overcooked_simulator.utils import ( - setup_logging, url_and_port_arguments, add_list_of_manager_ids_arguments, disable_websocket_logging_arguments, @@ -204,6 +203,7 @@ class EnvironmentHandler: env_id=config.env_id, player_id=player_id, ) + log.debug(f"Added player {player_id} to env {config.env_id}") return new_player_info def start_env(self, env_id: str): @@ -219,6 +219,7 @@ class EnvironmentHandler: self.envs[env_id].start_time = start_time self.envs[env_id].last_step_time = time.time_ns() self.envs[env_id].environment.reset_env_time() + self.envs[env_id].environment.all_players_ready = True def get_state( self, player_hash: str @@ -296,8 +297,11 @@ class EnvironmentHandler: if self.envs[env_id].status != EnvironmentStatus.STOPPED: self.envs[env_id].status = EnvironmentStatus.STOPPED self.envs[env_id].stop_reason = reason + log.debug(f"Stopped environment: id={env_id}, reason={reason}") return 0 + log.debug(f"Could not stop environment: id={env_id}, env is not running") return 2 + log.debug(f"Could not stop environment: id={env_id}, no env with this id") return 1 def set_player_ready(self, player_hash) -> bool: @@ -751,7 +755,9 @@ async def websocket_player_endpoint(websocket: WebSocket, client_id: str): def main( host: str, port: int, manager_ids: list[str], enable_websocket_logging: bool = False ): - setup_logging(enable_websocket_logging) + manager_ids = ["1234"] + + # setup_logging(enable_websocket_logging) loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) environment_handler.extend_allowed_manager(manager_ids) diff --git a/overcooked_simulator/gui_2d_vis/gui_theme.json b/overcooked_simulator/gui_2d_vis/gui_theme.json index 8e7e819a989131890f69df5df701b99f810ecb50..9f41fea590a7d36ecb6d2f3a175c46c5500a348d 100644 --- a/overcooked_simulator/gui_2d_vis/gui_theme.json +++ b/overcooked_simulator/gui_2d_vis/gui_theme.json @@ -157,5 +157,14 @@ "bold": 1, "colour": "#000000" } + }, + "#wait_players_label": { + "colours": { + "normal_text": "#ff0000" + }, + "font": { + "size": 45, + "bold": 1 + } } } \ 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 772504b2fa21e6aeed410c0164500945d9d42fca..e0a9e1aaeb121fc4469f95c060778df60fea4142 100644 --- a/overcooked_simulator/gui_2d_vis/overcooked_gui.py +++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py @@ -32,13 +32,8 @@ from overcooked_simulator.utils import ( url_and_port_arguments, disable_websocket_logging_arguments, add_list_of_manager_ids_arguments, - setup_logging, ) -CONNECT_WITH_STUDY_SERVER = False -USE_AAAMBOS_AGENT = False -SHOW_TUTORIAL_GAME = True - class MenuStates(Enum): Start = "Start" @@ -112,17 +107,23 @@ class PyGameGUI: url: str, port: int, manager_ids: list[str], + CONNECT_WITH_STUDY_SERVER: bool, + USE_AAAMBOS_AGENT: bool, ): + self.CONNECT_WITH_STUDY_SERVER = CONNECT_WITH_STUDY_SERVER + self.USE_AAAMBOS_AGENT = USE_AAAMBOS_AGENT + pygame.init() pygame.display.set_icon( pygame.image.load(ROOT_DIR / "gui_2d_vis" / "images" / "fish3.png") ) + self.participant_id = uuid.uuid4().hex + self.game_screen: pygame.Surface = None self.FPS = 60 self.running = True - self.reset_gui_values() self.key_sets: list[PlayerKeySet] = [] self.websocket_url = f"ws://{url}:{port}/ws/player/" @@ -166,9 +167,9 @@ class PyGameGUI: ) self.current_layout_idx = 0 - def setup_player_keys(self, n=1, disjunct=False): - if n: - players = list(range(self.number_humans_to_be_added)) + def setup_player_keys(self, number_players, number_key_sets=1, disjunct=False): + if number_key_sets: + players = list(range(number_players)) key_set1 = PlayerKeySet( move_keys=[pygame.K_a, pygame.K_d, pygame.K_w, pygame.K_s], interact_key=pygame.K_f, @@ -188,13 +189,13 @@ class PyGameGUI: if disjunct: key_set1.set_controlled_players(players[::2]) key_set2.set_controlled_players(players[1::2]) - elif n > 1: + elif number_key_sets > 1: key_set1.set_controlled_players(players) key_set2.set_controlled_players(players) key_set1.other_keyset = [key_set2] key_set2.other_keyset = [key_set1] key_set2.next_player() - return key_sets[:n] + return key_sets[:number_key_sets] else: return [] @@ -235,7 +236,7 @@ class PyGameGUI: 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(current_player_name, ActionType.PUT, "pickup") + action = Action(self.player_id, ActionType.PUT, "pickup") self.send_action(action) if event.key == key_set.interact_key: @@ -246,10 +247,10 @@ class PyGameGUI: self.send_action(action) elif event.type == pygame.KEYUP: action = Action( - current_player_name, ActionType.INTERACT, InterActionData.STOP + self.player_id, ActionType.INTERACT, InterActionData.STOP ) self.send_action(action) - if event.key == key_set.switch_key and not CONNECT_WITH_STUDY_SERVER: + if event.key == key_set.switch_key and not self.CONNECT_WITH_STUDY_SERVER: if event.type == pygame.KEYDOWN: key_set.next_player() @@ -414,7 +415,7 @@ class PyGameGUI: manager=self.manager, object_id="#number_players_label", container=self.player_number_container, - text=f"Humans to be added: {self.number_humans_to_be_added}", + text=f"Humans to be added: -", anchors={"center": "center"}, ) @@ -424,7 +425,7 @@ class PyGameGUI: manager=self.manager, object_id="#number_bots_label", container=self.bot_number_container, - text=f"Bots to be added: {self.number_bots_to_be_added}", + text=f"Bots to be added: -", anchors={"center": "center"}, ) @@ -478,18 +479,19 @@ class PyGameGUI: ######################################################################## image = pygame.image.load( - ROOT_DIR / "gui_2d_vis" / "tutorial.png" + ROOT_DIR / "gui_2d_vis" / "tutorial_files" / "tutorial.drawio.png" ).convert_alpha() image_rect = image.get_rect() - image_rect.topleft = (40, 40) + image_rect.topleft = (20, self.buttons_height) self.tutorial_image = pygame_gui.elements.UIImage( image_rect, image, manager=self.manager, anchors={"top": "top", "left": "left"}, ) - img_height = self.window_height * 0.75 - img_width = img_height * (image_rect.width / image_rect.height) + img_width = self.window_width * 0.8 + # img_width = img_height * (image_rect.width / image_rect.height) + img_height = img_width * (image_rect.height / image_rect.width) new_dims = (img_width, img_height) self.tutorial_image.set_dimensions(new_dims) @@ -519,7 +521,7 @@ class PyGameGUI: ######################################################################## image = pygame.image.load( - ROOT_DIR / "gui_2d_vis" / "recipe_mock.png" + ROOT_DIR / "gui_2d_vis" / "tutorial_files" / "recipe_mock.png" ).convert_alpha() image_rect = image.get_rect() image_rect.top = 50 @@ -568,24 +570,43 @@ class PyGameGUI: object_id="#orders_label", ) + rect = pygame.Rect( + (0, 0), + (self.window_width * 0.2, self.buttons_height), + ) + rect.bottomleft = (0, 0) self.score_label = pygame_gui.elements.UILabel( - text=f"Score: _", - relative_rect=pygame.Rect( - (0, self.window_height - self.screen_margin), - (self.screen_margin * 2, self.screen_margin), - ), + text=f"Score not set", + relative_rect=rect, manager=self.manager, object_id="#score_label", + anchors={"bottom": "bottom", "left": "left"}, ) + rect = pygame.Rect( + (0, 0), + (self.window_width * 0.4, self.buttons_height), + ) + rect.bottom = 0 self.timer_label = pygame_gui.elements.UILabel( - text="GAMETIME", - relative_rect=pygame.Rect( - (self.screen_margin, self.window_height - self.screen_margin), - (self.game_width, self.screen_margin), - ), + text="GAMETIME not set", + relative_rect=rect, manager=self.manager, object_id="#timer_label", + anchors={"bottom": "bottom", "centerx": "centerx"}, + ) + + rect = pygame.Rect( + (0, 0), + (self.window_width, self.screen_margin), + ) + rect.right = 20 + self.wait_players_label = pygame_gui.elements.UILabel( + text="WAITING FOR OTHER PLAYERS", + relative_rect=rect, + manager=self.manager, + object_id="#wait_players_label", + anchors={"centery": "centery", "right": "right"}, ) ######################################################################## @@ -659,40 +680,40 @@ class PyGameGUI: self.tutorial_screen_elements = [ self.tutorial_image, self.continue_button, - self.fullscreen_button, - self.quit_button, ] self.pregame_screen_elements = [ self.recipe_image, self.level_name, self.continue_button, - self.quit_button, - self.fullscreen_button, ] self.game_screen_elements = [ - self.finished_button, self.orders_label, self.score_label, - self.quit_button, - self.fullscreen_button, self.timer_label, + self.wait_players_label, ] + self.postgame_screen_elements = [ self.conclusion_label, self.next_game_button, - self.fullscreen_button, - self.retry_button, - self.quit_button, - self.finish_study_button, ] + self.end_screen_elements = [ self.fullscreen_button, self.quit_button, self.thank_you_label, ] + self.rest = [ + self.fullscreen_button, + self.quit_button, + self.retry_button, + self.finish_study_button, + self.finished_button, + ] + def show_screen_elements(self, elements: list): for element in ( self.start_screen_elements @@ -701,6 +722,7 @@ class PyGameGUI: + self.game_screen_elements + self.postgame_screen_elements + self.end_screen_elements + + self.rest ): element.hide() for element in elements: @@ -710,19 +732,22 @@ class PyGameGUI: match self.menu_state: case MenuStates.Start: self.show_screen_elements(self.start_screen_elements) + + if self.CONNECT_WITH_STUDY_SERVER: + self.player_selection_container.hide() + self.update_selection_elements() case MenuStates.ControllerTutorial: self.show_screen_elements(self.tutorial_screen_elements) - self.reset_gui_values() self.setup_game(tutorial=True) self.set_game_size( - max_height=self.window_height * 0.4, - max_width=self.window_width * 0.4, + max_height=self.window_height * 0.3, + max_width=self.window_width * 0.3, ) self.set_window_size() self.game_center = ( - self.window_width - self.game_width / 2 - 30, - self.window_height - self.game_height / 2 - 50, + self.window_width - self.game_width / 2 - 20, + self.window_height - self.game_height / 2 - 20, ) case MenuStates.PreGame: self.show_screen_elements(self.pregame_screen_elements) @@ -730,6 +755,12 @@ class PyGameGUI: self.show_screen_elements(self.game_screen_elements) case MenuStates.PostGame: self.show_screen_elements(self.postgame_screen_elements) + if self.study_finished: + self.next_game_button.hide() + self.finish_study_button.show() + else: + self.next_game_button.show() + self.finish_study_button.hide() case MenuStates.End: self.show_screen_elements(self.end_screen_elements) @@ -737,20 +768,18 @@ class PyGameGUI: self.main_window.fill( colors[self.visualization_config["GameWindow"]["background_color"]] ) - self.manager.draw_ui(self.main_window) match self.menu_state: case MenuStates.Start: pass case MenuStates.ControllerTutorial: - if SHOW_TUTORIAL_GAME: - self.draw_tutorial_screen_frame() + self.draw_tutorial_screen_frame() case MenuStates.Game: self.draw_game_screen_frame() - case MenuStates.PostGame: self.update_conclusion_label(self.last_state) + self.manager.draw_ui(self.main_window) self.manager.update(self.time_delta) pygame.display.flip() @@ -775,8 +804,15 @@ class PyGameGUI: self.handle_keys() if self.last_state["ended"]: + self.menu_state = MenuStates.PostGame self.finished_button_press() self.disconnect_websockets() + + if self.CONNECT_WITH_STUDY_SERVER: + self.send_level_done() + + self.update_screen_elements() + else: self.draw_game(self.last_state) @@ -789,6 +825,11 @@ class PyGameGUI: 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): """Main visualization function. @@ -859,59 +900,109 @@ class PyGameGUI: display_time = f"{minutes}:{'%02d' % seconds}" self.timer_label.set_text(f"Time remaining: {display_time}") - def setup_environment(self, tutorial=False): - if CONNECT_WITH_STUDY_SERVER: + def create_env_on_game_server(self, tutorial): + if tutorial: + layout_path = ROOT_DIR / "game_content" / "tutorial" / "tutorial.layout" + environment_config_path = ( + ROOT_DIR / "game_content" / "tutorial" / "tutorial_env_config.yaml" + ) + else: + environment_config_path = ( + ROOT_DIR / "game_content" / "environment_config.yaml" + ) + layout_path = self.layout_file_paths[self.current_layout_idx] + + item_info_path = ROOT_DIR / "game_content" / "item_info.yaml" + with open(item_info_path, "r") as file: + item_info = file.read() + with open(layout_path, "r") as file: + layout = file.read() + with open(environment_config_path, "r") as file: + environment_config = file.read() + + num_players = 1 if tutorial else self.number_players + seed = 161616161616 + creation_json = CreateEnvironmentConfig( + manager_id=self.manager_id, + number_players=num_players, + environment_settings={"all_player_can_pause_game": False}, + item_info_config=item_info, + environment_config=environment_config, + layout_config=layout, + seed=seed, + ).model_dump(mode="json") + + # print(CreateEnvironmentConfig.model_validate_json(json_data=creation_json)) + env_info = requests.post( + f"{self.request_url}/manage/create_env/", + json=creation_json, + ) + if env_info.status_code == 403: + raise ValueError(f"Forbidden Request: {env_info.json()['detail']}") + env_info = env_info.json() + assert isinstance(env_info, dict), "Env info must be a dictionary" + self.current_env_id = env_info["env_id"] + self.player_info = env_info["player_info"] + if tutorial: + self.player_id = str(list(self.player_info.keys())[0]) + + def get_game_connection(self): + if self.menu_state == MenuStates.ControllerTutorial: self.player_info = requests.post( - f"http://localhost:8080/connect_to_game/{uuid.uuid4().hex}" + f"http://localhost:8080/connect_to_tutorial/{self.participant_id}" ).json() self.key_sets[0].current_player = int(self.player_info["player_id"]) + self.player_id = self.player_info["player_id"] self.player_info = {self.player_info["player_id"]: self.player_info} else: - if tutorial: - layout_path = ROOT_DIR / "game_content" / "layouts" / "tutorial.layout" - environment_config_path = ( - ROOT_DIR / "game_content" / "tutorial_env_config.yaml" - ) - else: - environment_config_path = ( - ROOT_DIR / "game_content" / "environment_config.yaml" - ) - layout_path = self.layout_file_paths[self.current_layout_idx] - - item_info_path = ROOT_DIR / "game_content" / "item_info.yaml" - with open(item_info_path, "r") as file: - item_info = file.read() - with open(layout_path, "r") as file: - layout = file.read() - with open(environment_config_path, "r") as file: - environment_config = file.read() - - seed = 161616161616 - creation_json = CreateEnvironmentConfig( - manager_id=self.manager_id, - number_players=1 if tutorial else self.number_players, - environment_settings={"all_player_can_pause_game": False}, - item_info_config=item_info, - environment_config=environment_config, - layout_config=layout, - seed=seed, - ).model_dump(mode="json") - - # print(CreateEnvironmentConfig.model_validate_json(json_data=creation_json)) - env_info = requests.post( - f"{self.request_url}/manage/create_env/", - json=creation_json, + self.player_info = requests.post( + f"http://localhost:8080/get_game_connection/{self.participant_id}" + ).json() + self.key_sets[0].current_player = int(self.player_info["player_id"]) + self.player_id = self.player_info["player_id"] + self.player_info = {self.player_info["player_id"]: self.player_info} + + def create_and_connect_bot(self, player_id, player_info): + player_hash = player_info["player_hash"] + print( + f'--general_plus="agent_websocket:{self.websocket_url + player_info["client_id"]};player_hash:{player_hash};agent_id:{player_id}"' + ) + if self.USE_AAAMBOS_AGENT: + sub = Popen( + " ".join( + [ + "exec", + "aaambos", + "run", + "--arch_config", + str(ROOT_DIR / "game_content" / "agents" / "arch_config.yml"), + "--run_config", + str(ROOT_DIR / "game_content" / "agents" / "run_config.yml"), + f'--general_plus="agent_websocket:{self.websocket_url + player_info["client_id"]};player_hash:{player_hash};agent_id:{player_id}"', + f"--instance={player_hash}", + ] + ), + shell=True, + ) + else: + sub = Popen( + " ".join( + [ + "python", + str(ROOT_DIR / "game_content" / "agents" / "random_agent.py"), + f'--uri {self.websocket_url + player_info["client_id"]}', + f"--player_hash {player_hash}", + f"--player_id {player_id}", + ] + ), + shell=True, ) - if env_info.status_code == 403: - raise ValueError(f"Forbidden Request: {env_info.json()['detail']}") - env_info = env_info.json() - assert isinstance(env_info, dict), "Env info must be a dictionary" - self.current_env_id = env_info["env_id"] - self.player_info = env_info["player_info"] - - state = None + self.sub_processes.append(sub) + + def connect_websockets(self): for p, (player_id, player_info) in enumerate(self.player_info.items()): if p < self.number_humans_to_be_added: + # add player websockets websocket = connect(self.websocket_url + player_info["client_id"]) websocket.send( json.dumps( @@ -922,133 +1013,60 @@ class PyGameGUI: json.loads(websocket.recv())["status"] == 200 ), "not accepted player" self.websockets[player_id] = websocket + else: - player_hash = player_info["player_hash"] - print( - f'--general_plus="agent_websocket:{self.websocket_url + player_info["client_id"]};player_hash:{player_hash};agent_id:{player_id}"' - ) - if USE_AAAMBOS_AGENT: - sub = Popen( - " ".join( - [ - "exec", - "aaambos", - "run", - "--arch_config", - str( - ROOT_DIR - / "game_content" - / "agents" - / "arch_config.yml" - ), - "--run_config", - str( - ROOT_DIR - / "game_content" - / "agents" - / "run_config.yml" - ), - f'--general_plus="agent_websocket:{self.websocket_url + player_info["client_id"]};player_hash:{player_hash};agent_id:{player_id}"', - f"--instance={player_hash}", - ] - ), - shell=True, - ) - else: - sub = Popen( - " ".join( - [ - "python", - str( - ROOT_DIR - / "game_content" - / "agents" - / "random_agent.py" - ), - f'--uri {self.websocket_url + player_info["client_id"]}', - f"--player_hash {player_hash}", - f"--player_id {player_id}", - ] - ), - shell=True, - ) - self.sub_processes.append(sub) + # create bots and add bot websockets + self.create_and_connect_bot(player_id, player_info) - if p + 1 == self.number_humans_to_be_added: + if p == 0: + # set initial player_id for requesting the state self.state_player_id = player_id - websocket.send( - json.dumps( - {"type": "get_state", "player_hash": player_info["player_hash"]} - ) - ) - state = json.loads(websocket.recv()) - - if not self.number_humans_to_be_added: - player_id = "0" - player_info = self.player_info[player_id] - websocket = connect(self.websocket_url + player_info["client_id"]) - websocket.send( - json.dumps({"type": "ready", "player_hash": player_info["player_hash"]}) - ) - assert json.loads(websocket.recv())["status"] == 200, "not accepted player" - self.websockets[player_id] = websocket - self.state_player_id = player_id - websocket.send( - json.dumps( - {"type": "get_state", "player_hash": player_info["player_hash"]} - ) - ) - state = json.loads(websocket.recv()) + # websocket.send( + # json.dumps( + # {"type": "get_state", "player_hash": player_info["player_hash"]} + # ) + # ) + # state = json.loads(websocket.recv()) - self.kitchen_width = state["kitchen"]["width"] - self.kitchen_height = state["kitchen"]["height"] + # return state def setup_game(self, tutorial=False): - 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( - min(self.number_humans_to_be_added, num_key_set), self.split_players - ) - - self.setup_environment(tutorial=tutorial) + if tutorial: + self.key_sets = self.setup_player_keys(1, 1, False) + self.vis.create_player_colors(1) + else: + self.number_players = ( + self.number_humans_to_be_added + self.number_bots_to_be_added + ) - # self.set_game_size() - # self.set_window_size() + 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( + self.number_humans_to_be_added, + min(self.number_humans_to_be_added, num_key_set), + self.split_players, + ) - def back_button_press(self): - self.menu_state = MenuStates.Start - self.reset_window_size() + if self.CONNECT_WITH_STUDY_SERVER: + self.get_game_connection() + else: + self.create_env_on_game_server(tutorial) - self.update_selection_elements() + self.connect_websockets() - log.debug("Pressed back button") + state = self.request_state() - def reset_button_press(self): - if not CONNECT_WITH_STUDY_SERVER: - requests.post( - f"{self.request_url}/manage/stop_env", - json={ - "manager_id": self.manager_id, - "env_id": self.current_env_id, - "reason": "reset button pressed", - }, - ) + self.vis.create_player_colors(len(state["players"])) - # self.websocket.send(json.dumps("reset_game")) - # answer = self.websocket.recv() log.debug("Pressed reset button") + self.kitchen_width = state["kitchen"]["width"] + self.kitchen_height = state["kitchen"]["height"] def stop_game(self, reason: str) -> None: - self.disconnect_websockets() log.debug(f"Stopping game: {reason}") - if not CONNECT_WITH_STUDY_SERVER: + if not self.CONNECT_WITH_STUDY_SERVER: requests.post( f"{self.request_url}/manage/stop_env/", json={ @@ -1058,11 +1076,18 @@ class PyGameGUI: }, ) + def send_tutorial_finished(self): + requests.post( + f"http://localhost:8080/disconnect_from_tutorial/{self.participant_id}", + ) + def finished_button_press(self): - self.stop_game("finished_button_pressed") + if not self.CONNECT_WITH_STUDY_SERVER: + self.stop_game("finished_button_pressed") self.menu_state = MenuStates.PostGame self.reset_window_size() log.debug("Pressed finished button") + self.update_screen_elements() def fullscreen_button_press(self): self.fullscreen = not self.fullscreen @@ -1087,10 +1112,15 @@ class PyGameGUI: self.number_humans_to_be_added = self.player_minimum else: self.remove_human_button.enable() + self.number_humans_to_be_added = max( self.player_minimum, self.number_humans_to_be_added ) + self.number_players = ( + self.number_humans_to_be_added + self.number_bots_to_be_added + ) + text = "WASD+ARROW" if self.multiple_keysets else "WASD" self.multiple_keysets_button.set_text(text) self.added_players_label.set_text( @@ -1107,6 +1137,11 @@ class PyGameGUI: else: self.split_players_button.hide() + if self.number_players == 0: + self.start_button.disable() + else: + self.start_button.enable() + def send_action(self, action: Action): """Sends an action to the game environment. @@ -1118,7 +1153,7 @@ class PyGameGUI: float(action.action_data[0]), float(action.action_data[1]), ] - self.websockets[action.player].send( + self.websockets[self.player_id].send( json.dumps( { "type": "action", @@ -1129,16 +1164,16 @@ class PyGameGUI: } ) ) - self.websockets[action.player].recv() + self.websockets[self.player_id].recv() def request_state(self): self.websockets[self.state_player_id].send( json.dumps( { "type": "get_state", - "player_hash": self.player_info[ - str(self.key_sets[0].current_player) - ]["player_hash"], + "player_hash": self.player_info[self.state_player_id][ + "player_hash" + ], } ) ) @@ -1148,7 +1183,7 @@ class PyGameGUI: def disconnect_websockets(self): for sub in self.sub_processes: try: - if USE_AAAMBOS_AGENT: + if self.USE_AAAMBOS_AGENT: pgrp = os.getpgid(sub.pid) os.killpg(pgrp, signal.SIGINT) subprocess.run( @@ -1164,16 +1199,32 @@ class PyGameGUI: for websocket in self.websockets.values(): websocket.close() + def start_study(self): + self.player_info = requests.post( + f"http://localhost:8080/start_study/{self.participant_id}" + ).json() + self.study_finished = False + + def send_level_done(self): + answer = requests.post( + f"http://localhost:8080/level_done/{self.participant_id}" + ).json() + self.study_finished = answer["study_finished"] + print("\nSTUDY DONE:", self.study_finished, "\n") + def manage_button_event(self, event): if event.ui_element == self.quit_button: self.running = False self.disconnect_websockets() + self.stop_game("Quit button") self.menu_state = MenuStates.Start log.debug("Pressed quit button") + return elif event.ui_element == self.fullscreen_button: self.fullscreen_button_press() log.debug("Pressed fullscreen button") + return # Filter by shown screen page match self.menu_state: @@ -1191,12 +1242,13 @@ class PyGameGUI: case self.add_human_player_button: self.number_humans_to_be_added += 1 + case self.add_bot_button: + self.number_bots_to_be_added += 1 + case self.remove_human_button: self.number_humans_to_be_added = max( - 0, self.number_humans_to_be_added - 1 + self.player_minimum, 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 @@ -1209,7 +1261,7 @@ class PyGameGUI: if self.split_players: self.player_minimum = 2 else: - self.player_minimum = 1 + self.player_minimum = 0 ############################################ @@ -1217,7 +1269,13 @@ class PyGameGUI: match event.ui_element: case self.continue_button: self.menu_state = MenuStates.PreGame - self.stop_game("tutorial_finished") + + if self.CONNECT_WITH_STUDY_SERVER: + self.send_tutorial_finished() + self.start_study() + else: + self.stop_game("tutorial_finished") + self.disconnect_websockets() ############################################ @@ -1226,7 +1284,6 @@ class PyGameGUI: case self.continue_button: self.setup_game() self.set_game_size() - self.set_window_size() self.menu_state = MenuStates.Game ############################################ @@ -1238,23 +1295,27 @@ class PyGameGUI: self.disconnect_websockets() self.finished_button_press() + if self.CONNECT_WITH_STUDY_SERVER: + self.send_level_done() + ############################################ case MenuStates.PostGame: match event.ui_element: case self.retry_button: - self.setup_game() + if not self.CONNECT_WITH_STUDY_SERVER: + self.stop_game("Retry button") self.menu_state = MenuStates.PreGame case self.next_game_button: - self.current_layout_idx += 1 - if self.current_layout_idx == len(self.layout_file_paths): - self.current_layout_idx = 0 - else: - log.debug( - f"LEVEL: {self.layout_file_paths[self.current_layout_idx]}" - ) - self.setup_game() + if not self.CONNECT_WITH_STUDY_SERVER: + self.current_layout_idx += 1 + if self.current_layout_idx == len(self.layout_file_paths): + self.current_layout_idx = 0 + else: + log.debug( + f"LEVEL: {self.layout_file_paths[self.current_layout_idx]}" + ) self.menu_state = MenuStates.PreGame case self.finish_study_button: @@ -1280,6 +1341,8 @@ class PyGameGUI: self.reset_window_size() self.init_ui_elements() + self.reset_gui_values() + self.update_screen_elements() # Game loop @@ -1291,12 +1354,12 @@ class PyGameGUI: # PROCESSING EVENTS for event in pygame.event.get(): if event.type == pygame.QUIT: + self.disconnect_websockets() self.running = False if event.type == pygame_gui.UI_BUTTON_PRESSED: self.manage_button_event(event) self.update_screen_elements() - self.update_selection_elements() if event.type in [ pygame.KEYDOWN, @@ -1314,18 +1377,31 @@ class PyGameGUI: except (KeyboardInterrupt, SystemExit): self.running = False + self.disconnect_websockets() + self.stop_game("Program exited.") + self.disconnect_websockets() self.stop_game("Program exited") pygame.quit() sys.exit() -def main(url: str, port: int, manager_ids: list[str]): - setup_logging() +def main( + url: str, + port: int, + manager_ids: list[str], + CONNECT_WITH_STUDY_SERVER=False, + USE_AAAMBOS_AGENT=False, +): + manager_ids = ["1234"] + + # setup_logging() gui = PyGameGUI( url=url, port=port, manager_ids=manager_ids, + CONNECT_WITH_STUDY_SERVER=CONNECT_WITH_STUDY_SERVER, + USE_AAAMBOS_AGENT=CONNECT_WITH_STUDY_SERVER, ) gui.start_pygame() diff --git a/overcooked_simulator/gui_2d_vis/recipe_mock.png b/overcooked_simulator/gui_2d_vis/tutorial_files/recipe_mock.png similarity index 100% rename from overcooked_simulator/gui_2d_vis/recipe_mock.png rename to overcooked_simulator/gui_2d_vis/tutorial_files/recipe_mock.png diff --git a/overcooked_simulator/gui_2d_vis/tutorial_files/tutorial.drawio.png b/overcooked_simulator/gui_2d_vis/tutorial_files/tutorial.drawio.png new file mode 100644 index 0000000000000000000000000000000000000000..8824cce8164089cc251217e21263c75fcd552254 Binary files /dev/null and b/overcooked_simulator/gui_2d_vis/tutorial_files/tutorial.drawio.png differ diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py index 15bf39c86263f76e795990919533e9f08c047995..41c6bc619520873037d2ba1b170a32cd82ce90cc 100644 --- a/overcooked_simulator/overcooked_environment.py +++ b/overcooked_simulator/overcooked_environment.py @@ -56,8 +56,6 @@ from overcooked_simulator.utils import create_init_env_time, get_closest log = logging.getLogger(__name__) -FOG_OF_WAR = True - PREVENT_SQUEEZING_INTO_OTHER_PLAYERS = False @@ -296,6 +294,8 @@ class Environment: env_start_time_worldtime=datetime.now(), ) + self.all_players_ready = False + def overwrite_counters(self, counters): self.counters = counters self.counter_positions = np.array([c.pos for c in self.counters]) @@ -469,11 +469,7 @@ class Environment: grid.append(grid_line) current_y += 1 - for line in grid: - print(line) grid = [line + ([0] * (max_width - len(line))) for line in grid] - for line in grid: - print(line) self.kitchen_width: float = max_width + starting_at self.kitchen_height = current_y @@ -826,6 +822,7 @@ class Environment: "kitchen": {"width": self.kitchen_width, "height": self.kitchen_height}, "score": self.order_and_score.score, "orders": self.order_and_score.order_state(), + "all_players_ready": self.all_players_ready, "ended": self.game_ended, "env_time": self.env_time.isoformat(), "remaining_time": max(