diff --git a/README.md b/README.md index 7222709f10f94cc0b0ab6343079ab35dfd8f970e..135e869b284165af0d9eebe765a90478cc7f1f31 100644 --- a/README.md +++ b/README.md @@ -20,14 +20,30 @@ In your `repo`, `PyCharmProjects` or similar directory with the correct environm git clone https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator.git cd overcooked_simulator pip install -e . -`` +``` #### Run -You can use it in your Python code or run the `main.py`from the command line: + +Run it via the command line (in your pyenv/conda environment): + ```bash -python3 overcooked_simulator/main.py +overcooked-sim --url "localhost" --port 8000 ``` +_The arguments are the defaults. Therefore, they are optional._ + +You can also start the **Game Server** and the **PyGame GUI** individually in different terminals. + +```bash +python3 overcooked_simulator/game_server.py --url "localhost" --port 8000 + +python3 overcooked_simulator/gui_2d_vis/overcooked_simulator.py --url "localhost" --port 8000 +``` + +You can start also several GUIs. + +You can replace the GUI with your own GUI (+ study server/matchmaking server). + ### Library Installation The correct environment needs to be active: diff --git a/overcooked_simulator/__init__.py b/overcooked_simulator/__init__.py index 2c7bb07c452710b36d9c0a5aa12cd56325842530..49355e1c86c40e0b0ce3fb6c323cb176e6ad3220 100644 --- a/overcooked_simulator/__init__.py +++ b/overcooked_simulator/__init__.py @@ -24,12 +24,22 @@ Our overcooked simulator is designed for real time interaction but also with rei It focuses on configurability, extensibility and appealing visualization options. ## Human Player -Start `main.py` in your python/conda environment: +Run it via the command line (in your pyenv/conda environment): + ```bash -python overcooked_simulator/main.py +overcooked-sim --url "localhost" --port 8000 ``` -## Connect with player and receive game state +_The arguments are the defaults. Therefore, they are optional._ + +You can also start the **Game Server** and the **PyGame GUI** individually in different terminals. + +```bash +python3 overcooked_simulator/game_server.py --url "localhost" --port 8000 + +python3 overcooked_simulator/gui_2d_vis/overcooked_simulator.py --url "localhost" --port 8000 + +## Connect with agent and receive game state ... ## Direct integration into your code. diff --git a/overcooked_simulator/__main__.py b/overcooked_simulator/__main__.py index a85f150ac7d4de61e1c4d8344e5e0fbee86658d6..f81398ece39babd670716b79b045ee3226176e97 100644 --- a/overcooked_simulator/__main__.py +++ b/overcooked_simulator/__main__.py @@ -2,26 +2,9 @@ import argparse import time from multiprocessing import Process -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", -) - -parser.add_argument( - "-url", - "--url", - "--host", - type=str, - default="localhost", - help="Overcooked game server host url.", -) -parser.add_argument( - "-p", - "--port", - type=int, - default=8000, - help="Port number for the game engine server", +from overcooked_simulator.utils import ( + url_and_port_arguments, + disable_websocket_logging_arguments, ) @@ -34,7 +17,7 @@ def start_game_server(cli_args): def start_pygame_gui(cli_args): from overcooked_simulator.gui_2d_vis.overcooked_gui import main - main() + main(cli_args.url, cli_args.port) def main(cli_args=None): @@ -65,6 +48,15 @@ def main(cli_args=None): if __name__ == "__main__": + 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) + args = parser.parse_args() print(args) main(args) diff --git a/overcooked_simulator/game_server.py b/overcooked_simulator/game_server.py index 1e7619b2a0243a9369e21b0da1a7041cb4ee0e61..35db3885b24d70c21ee3c37db3a95ddad423613c 100644 --- a/overcooked_simulator/game_server.py +++ b/overcooked_simulator/game_server.py @@ -9,6 +9,7 @@ player can state that them are ready to play, request the current game state, an """ from __future__ import annotations +import argparse import asyncio import dataclasses import json @@ -34,17 +35,14 @@ from overcooked_simulator.server_results import ( PlayerInfo, PlayerRequestResult, ) -from overcooked_simulator.utils import setup_logging +from overcooked_simulator.utils import setup_logging, url_and_port_arguments log = logging.getLogger(__name__) -setup_logging() app = FastAPI() - -WEBSOCKET_URL = "localhost" -WEBSOCKET_PORT = 8000 +TIME_AFTER_STOP_TO_DEL_ENV = 30 @dataclasses.dataclass @@ -420,6 +418,9 @@ class EnvironmentHandler: ) ) env_data.last_step_time = step_start + if env_data.environment.game_ended: + log.info(f"Env {env_id} ended. Set env to STOPPED.") + env_data.status = EnvironmentStatus.STOPPED elif ( env_data.status == EnvironmentStatus.WAITING_FOR_PLAYERS and self.check_all_player_ready(env_id) @@ -427,7 +428,8 @@ class EnvironmentHandler: self.start_env(env_id) elif ( env_data.status == EnvironmentStatus.STOPPED - and env_data.last_step_time + (60 * 1e9) < pre_step_start + and env_data.last_step_time + (TIME_AFTER_STOP_TO_DEL_ENV * 1e9) + < pre_step_start ): to_remove.append(env_id) if to_remove: @@ -709,7 +711,7 @@ async def websocket_player_endpoint(websocket: WebSocket, client_id: str): log.debug(f"Client #{client_id} disconnected") -def main(host: str = WEBSOCKET_URL, port: int = WEBSOCKET_PORT): +def main(host: str, port: int): loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) loop.create_task(environment_handler.environment_steps()) @@ -719,7 +721,16 @@ def main(host: str = WEBSOCKET_URL, port: int = WEBSOCKET_PORT): if __name__ == "__main__": - main() + parser = argparse.ArgumentParser( + prog="Overcooked Simulator Game Server", + description="Game Engine Server: Starts overcooked game engine server.", + epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html", + ) + + url_and_port_arguments(parser) + args = parser.parse_args() + setup_logging(args.enable_websocket_logging) + main(args.url, args.port) """ Or in console: uvicorn overcooked_simulator.fastapi_game_server:app --reload diff --git a/overcooked_simulator/gui_2d_vis/overcooked_gui.py b/overcooked_simulator/gui_2d_vis/overcooked_gui.py index 5bf51f92240ecf3b9445e9f4b91d708bc27111ea..1370d8475ccf53668005af4902b9939b1420d201 100644 --- a/overcooked_simulator/gui_2d_vis/overcooked_gui.py +++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py @@ -1,3 +1,4 @@ +import argparse import dataclasses import json import logging @@ -20,7 +21,12 @@ from overcooked_simulator.overcooked_environment import ( ActionType, InterActionData, ) -from overcooked_simulator.utils import custom_asdict_factory, setup_logging +from overcooked_simulator.utils import ( + custom_asdict_factory, + setup_logging, + url_and_port_arguments, + disable_websocket_logging_arguments, +) class MenuStates(Enum): @@ -33,7 +39,6 @@ MANAGER_ID = "1233245425" log = logging.getLogger(__name__) -setup_logging() class PlayerKeySet: @@ -67,6 +72,8 @@ class PyGameGUI: self, player_names: list[str | int], player_keys: list[pygame.key], + url: str, + port: str, ): self.game_screen = None self.FPS = 60 @@ -82,10 +89,11 @@ class PyGameGUI: ) ] - # self.websocket_url = "ws://localhost:8765" - self.websocket_url = "ws://localhost:8000/ws/player/" + self.websocket_url = f"ws://{url}:{port}/ws/player/" self.websockets = {} + self.request_url = f"http://{url}:{port}" + # TODO cache loaded images? with open(ROOT_DIR / "gui_2d_vis" / "visualization.yaml", "r") as file: self.visualization_config = yaml.safe_load(file) @@ -445,7 +453,7 @@ class PyGameGUI: ).model_dump(mode="json") # print(CreateEnvironmentConfig.model_validate_json(json_data=creation_json)) env_info = requests.post( - "http://localhost:8000/manage/create_env/", + f"{self.request_url}/manage/create_env/", json=creation_json, ) env_info = env_info.json() @@ -497,7 +505,7 @@ class PyGameGUI: def reset_button_press(self): requests.post( - "http://localhost:8000/manage/stop_env", + f"{self.request_url}/manage/stop_env", json={ "manager_id": MANAGER_ID, "env_id": self.current_env_id, @@ -510,7 +518,7 @@ class PyGameGUI: def finished_button_press(self): requests.post( - "http://localhost:8000/manage/stop_env", + f"{self.request_url}/manage/stop_env", json={ "manager_id": MANAGER_ID, "env_id": self.current_env_id, @@ -637,6 +645,7 @@ class PyGameGUI: if state["ended"]: self.finished_button_press() + self.disconnect_websockets() self.manage_button_visibility() else: self.draw(state) @@ -661,7 +670,7 @@ class PyGameGUI: sys.exit() -def main(): +def main(url, port): # TODO maybe read the player names and keyboard keys from config file? keys1 = [ pygame.K_LEFT, @@ -674,9 +683,21 @@ def main(): 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]) + gui = PyGameGUI( + list(map(str, range(number_players))), [keys1, keys2], url=url, port=port + ) gui.start_pygame() if __name__ == "__main__": - main() + parser = argparse.ArgumentParser( + prog="Overcooked Simulator 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", + ) + + url_and_port_arguments(parser) + disable_websocket_logging_arguments(parser) + args = parser.parse_args() + setup_logging(enable_websocket_logging=args.enable_websocket_logging) + main(args.url, args.port) diff --git a/overcooked_simulator/utils.py b/overcooked_simulator/utils.py index a782285469cd9145e04381b73ad8e1cd78e4eef3..ecfb5958a982c084ce46db849fa9568483fea3e5 100644 --- a/overcooked_simulator/utils.py +++ b/overcooked_simulator/utils.py @@ -23,7 +23,7 @@ def custom_asdict_factory(data): return dict((k, convert_value(v)) for k, v in data) -def setup_logging(): +def setup_logging(enable_websocket_logging=False): path_logs = ROOT_DIR.parent / "logs" os.makedirs(path_logs, exist_ok=True) logging.basicConfig( @@ -38,3 +38,31 @@ def setup_logging(): ], ) logging.getLogger("matplotlib").setLevel(logging.WARNING) + if not enable_websocket_logging: + logging.getLogger("asyncio").setLevel(logging.ERROR) + logging.getLogger("asyncio.coroutines").setLevel(logging.ERROR) + logging.getLogger("websockets.server").setLevel(logging.ERROR) + logging.getLogger("websockets.protocol").setLevel(logging.ERROR) + logging.getLogger("websockets.client").setLevel(logging.ERROR) + + +def url_and_port_arguments(parser): + parser.add_argument( + "-url", + "--url", + "--host", + type=str, + default="localhost", + help="Overcooked game server host url.", + ) + parser.add_argument( + "-p", + "--port", + type=int, + default=8000, + help="Port number for the game engine server", + ) + + +def disable_websocket_logging_arguments(parser): + parser.add_argument("--enable-websocket-logging", action="store_true", default=True)