Skip to content
Snippets Groups Projects
Commit fd11db04 authored by Florian Schröder's avatar Florian Schröder
Browse files

Merge branch '113-complete-cooperative-cousine-terminal-command' into 'dev'

Resolve "Complete Cooperative Cousine Terminal Command"

Closes #113

See merge request scs/cocosy/overcooked-simulator!90
parents 19de1cb7 67de4612
No related branches found
No related tags found
1 merge request!90Resolve "Complete Cooperative Cousine Terminal Command"
Pipeline #49889 passed
Showing with 693 additions and 406 deletions
......@@ -23,9 +23,16 @@
- Level layouts from 2d-grid-overcooked-literature
- Caching of graph recipe layouts
- Score label changes color when score changes
- Control screenshot, replay, single game/study server, gui via cli (sub commands)
### Changed
- cli control changed `cooperative_cuisine` is now `cooperative-cuisine` (`-` instead of `_`)
- cli now uses commands. Use `cooperative-cuisine start` now for the same behaviour as before (add arguments).
- `cooperative-cuisine` with arguments does not work anymore. The "no arguments" call results in running with defaults.
If you want to add arguments use the `start` subcommand
- `cooperative-cuisine -h` provides an overview of all sub commands. For individual help for each coomand,
run `cooperative-cuisine COMMAND -h`.
- `extra_setup_functions` now only contain hook callback classes (`hook_callbacks`). Therefore, the `func` and `kwargs`
fields were
removed and the kwargs are now the standard values for defined hooks. This reduced the complexity of the config.
......
......@@ -38,7 +38,7 @@ pip install -e .
Run it via the command line (in your pyenv/conda environment):
```bash
cooperative_cuisine -s localhost -sp 8080 -g localhost -gp 8000
cooperative-cuisine start -s localhost -sp 8080 -g localhost -gp 8000
```
*The arguments shown are the defaults.*
......@@ -47,11 +47,11 @@ You can also start the **Game Server**m **Study Server** (Matchmaking),and the *
terminals.
```bash
python3 cooperative_cuisine/game_server.py -g localhost -gp 8000 --manager-ids SECRETKEY1 SECRETKEY2
cooperative-cuisine game-server -g localhost -gp 8000 --manager-ids SECRETKEY1 SECRETKEY2
python3 cooperative_cuisine/study_server.py -s localhost -sp 8080 --manager-ids SECRETKEY1
cooperative-cuisine study-server -s localhost -sp 8080 -g localhost -gp 8000 --manager-ids SECRETKEY1
python3 cooperative_cuisine/pygame_2d_vis/gui.py -s localhost -sp 8080 -g localhost -gp 8000
cooperative-cuisine gui -s localhost -sp 8080 -g localhost -gp 8000
```
You can start also several GUIs. The study server does the matchmaking.
......
......@@ -21,7 +21,7 @@ like a "real", cooperative, human partner.
# Installation
You need a Python **3.10** or newer codna environment.
You need a Python **3.10** or newer conda environment.
```bash
conda install -c conda-forge pygraphviz
pip install cooperative_cuisine@git+https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator@main
......@@ -43,7 +43,7 @@ options.
Run it via the command line (in your pyenv/conda environment):
```bash
cooperative_cuisine -s localhost -sp 8080 -g localhost -gp 8000
cooperative-cuisine start -s localhost -sp 8080 -g localhost -gp 8000
```
*The arguments shown are the defaults.*
......@@ -51,13 +51,15 @@ cooperative_cuisine -s localhost -sp 8080 -g localhost -gp 8000
You can also start the **Game Server**, **Study Server** (Matchmaking),and the **PyGame GUI** individually in different terminals.
```bash
python3 cooperative_cuisine/game_server.py -g localhost -gp 8000 --manager-ids SECRETKEY1 SECRETKEY2
cooperative-cuisine game-server -g localhost -gp 8000 --manager-ids SECRETKEY1 SECRETKEY2
python3 cooperative_cuisine/study_server.py -s localhost -sp 8080 --manager-ids SECRETKEY1
cooperative-cuisine study-server -s localhost -sp 8080 -g localhost -gp 8000 --manager-ids SECRETKEY1
python3 cooperative_cuisine/pygame_2d_vis/gui.py -s localhost -sp 8080 -g localhost -gp 8000
cooperative-cuisine gui -s localhost -sp 8080
```
For more CLI arguments and a description of possible arguments, run `cooperative-cuisine -h`
## Connect with agent and receive game state
Or you start a game server, create an environment and connect each player/agent via a websocket connection.
......@@ -210,7 +212,7 @@ returns.
You might have stored some json states and now you want to visualize them via the
pygame-2d visualization. You can do that by running the `drawing.py` script and referencing a json file.
```bash
python3 cooperative_cuisine/pygame_2d_vis/drawing.py --state my_state.json
cooperative-cuisine screenshot --state my_state.json
```
- You can specify a different visualization config with `-v` or `--visualization_config`.
- You can specify the name of the output file with `-o` or `--output_file`. The default is `screenshot.jpg`.
......@@ -370,6 +372,7 @@ num_bots: 0
The API documentation follows the file and content structure in the repo.
On the left you can find the navigation panel that brings you to the implementation of
- the **action**s the player can perform,
- the **argument parser** definitions for the cli,
- the **counter factory** converts the characters in the layout file to counter instances,
- the **counters**, including the kitchen utility objects like dispenser, cooking counter (stove, deep fryer, oven),
sink, etc.,
......
......@@ -2,13 +2,15 @@ import argparse
import time
from multiprocessing import Process
from cooperative_cuisine.utils import (
url_and_port_arguments,
disable_websocket_logging_arguments,
add_list_of_manager_ids_arguments,
add_study_arguments,
add_gui_arguments,
from cooperative_cuisine.argument_parser import (
create_screenshot_parser,
create_study_server_parser,
create_game_server_parser,
create_normal_parser,
create_gui_parser,
create_server_parser,
)
from cooperative_cuisine.pygame_2d_vis.video_replay import create_replay_parser
USE_STUDY_SERVER = True
......@@ -29,6 +31,7 @@ def start_study_server(cli_args):
game_port=cli_args.game_port,
manager_ids=cli_args.manager_ids,
study_config_path=cli_args.study_config,
use_ssl=cli_args.ssl,
)
......@@ -41,31 +44,18 @@ def start_pygame_gui(cli_args):
cli_args.game_url,
cli_args.game_port,
cli_args.manager_ids,
CONNECT_WITH_STUDY_SERVER=USE_STUDY_SERVER,
debug=cli_args.do_study,
debug=cli_args.debug,
do_study=cli_args.do_study,
use_ssl=cli_args.ssl,
)
def main(cli_args=None):
study_server = None
parser = argparse.ArgumentParser(
prog="Cooperative Cuisine",
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)
add_study_arguments(parser)
add_gui_arguments(parser)
cli_args = parser.parse_args()
def start_server_and_gui(cli_args, no_gui=False):
study_sever = None
game_server = None
pygame_gui = None
try:
if USE_STUDY_SERVER:
if no_gui or cli_args.do_study or not cli_args.debug:
print("Start study server:")
study_server = Process(target=start_study_server, args=(cli_args,))
study_server.start()
......@@ -75,38 +65,21 @@ def main(cli_args=None):
game_server = Process(target=start_game_server, args=(cli_args,))
game_server.start()
time.sleep(0.5)
if no_gui:
while True:
time.sleep(1)
else:
print("Start PyGame GUI 1:")
pygame_gui = Process(target=start_pygame_gui, args=(cli_args,))
pygame_gui.start()
print("Start PyGame GUI 1:")
pygame_gui = Process(target=start_pygame_gui, args=(cli_args,))
pygame_gui.start()
if USE_STUDY_SERVER:
pass
# print("Start PyGame GUI 2:")
# pygame_gui_2 = Process(target=start_pygame_gui, args=(cli_args,))
# pygame_gui_2.start()
# # #
# print("Start PyGame GUI 3:")
# pygame_gui_3 = Process(target=start_pygame_gui, args=(cli_args,))
# pygame_gui_3.start()
#
# print("Start PyGame GUI 4:")
# pygame_gui_4 = Process(target=start_pygame_gui, args=(cli_args,))
# pygame_gui_4.start()
# while (
# pygame_gui.is_alive()
# and pygame_gui_2.is_alive()
# and pygame_gui_3.is_alive()
# ):
# time.sleep(1)
while pygame_gui.is_alive():
time.sleep(1)
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():
if 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():
......@@ -118,5 +91,88 @@ def main(cli_args=None):
time.sleep(0.1)
def main(cli_args=None):
parser = argparse.ArgumentParser(
prog="Cooperative Cuisine",
description="CLI control of cooperative cuisine. Start server, guis, execute utility code, etc. Use sub "
"commands to configure the execution. No commands or arguments results in starting a study "
"server, game server and a gui.",
epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html",
)
subparsers = parser.add_subparsers(
help="Available CLI of Cooperative Cuisine", dest="command"
)
normal_parser = subparsers.add_parser(
"start",
help="Start a game server, study server and a PyGame 2D Visualization.",
)
create_normal_parser(normal_parser)
study_server_parser = subparsers.add_parser(
"study-server", help="Start a study server.", aliases=["study"]
)
create_study_server_parser(study_server_parser)
game_server_parser = subparsers.add_parser(
"game-server", help="Start a game server.", aliases=["game"]
)
create_game_server_parser(game_server_parser)
gui_parser = subparsers.add_parser(
"gui", help="Start a PyGame 2D Visualization (a client).", aliases=["client"]
)
create_gui_parser(gui_parser)
screenshot_parser = subparsers.add_parser(
"screenshot", help="Create a screenshot from a json state.", aliases=["image"]
)
create_screenshot_parser(screenshot_parser)
replay_parser = subparsers.add_parser(
"replay",
help="Create replay from json states or recordings.",
aliases=["video", "video-replay"],
)
create_replay_parser(replay_parser)
server_parser = subparsers.add_parser(
"server",
help="Start game and study server.",
aliases=["game-and-study", "study-and-game"],
)
create_server_parser(server_parser)
cli_args = parser.parse_args()
if cli_args.command:
match cli_args.command:
case "screenshot" | "image":
from cooperative_cuisine.pygame_2d_vis.drawing import main
main(cli_args)
case "study-server" | "study":
start_study_server(cli_args)
case "game-server" | "game":
start_game_server(cli_args)
case "replay" | "video" | "video-replay":
from cooperative_cuisine.pygame_2d_vis.video_replay import main
main(cli_args)
case "start":
start_server_and_gui(cli_args)
case "gui" | "client":
start_pygame_gui(cli_args)
case "server" | "game-and-study" | "study-and-game":
start_server_and_gui(cli_args, no_gui=True)
case _:
parser.print_help()
else:
default_parser = argparse.ArgumentParser()
create_normal_parser(default_parser)
start_server_and_gui(default_parser.parse_args())
if __name__ == "__main__":
main()
import uuid
from argparse import ArgumentParser, FileType
from cooperative_cuisine import ROOT_DIR
DEFAULT_SERVER_URL = "localhost"
"""Default server URL of game and server study."""
DEFAULT_SERVER_PORT = 8080
"""Default study server port."""
DEFAULT_GAME_PORT = 8000
"""Default game server port."""
def study_server_arguments(
parser: ArgumentParser,
default_study_port=DEFAULT_SERVER_PORT,
default_server_url=DEFAULT_SERVER_URL,
):
"""Add arguments for study server ip and port.
Args:
parser: the parser to add the arguments.
default_study_port: the default port of the study server `DEFAULT_STUDY_PORT` -> 8080
default_server_url: the default ip/url/host of the study server `DEFAULT_SERVER_URL` -> `localhost`
"""
parser.add_argument(
"-s",
"--study-url",
"--study-host",
type=str,
default=default_server_url,
help=f"Overcooked Study Server host url.",
)
parser.add_argument(
"-p",
"--study-port",
type=int,
default=default_study_port,
help=f"Port number for the Study Server",
)
def game_server_arguments(
parser: ArgumentParser,
default_game_port=DEFAULT_GAME_PORT,
default_server_url=DEFAULT_SERVER_URL,
):
"""Add arguments for game server ip and port.
Args:
parser: the parser to add the arguments.
default_game_port: the default port of the game server `DEFAULT_GAME_PORT` -> 8000
default_server_url: the default ip/url/host of the game server `DEFAULT_SERVER_URL` -> `localhost`
"""
parser.add_argument(
"-g",
"--game-url",
"--game-host",
type=str,
default=default_server_url,
help=f"Overcooked Game Server url.",
)
parser.add_argument(
"-gp",
"--game-port",
type=int,
default=default_game_port,
help=f"Port number for the Game Server",
)
def disable_websocket_logging_arguments(parser):
"""Disables the logging of WebSocket arguments in the provided parser.
Args:
parser: The argument parser object (argparse.ArgumentParser) to which the
"--enable-websocket-logging" argument will be added.
"""
parser.add_argument(
"--enable-websocket-logging" "", action="store_true", default=True
)
def add_list_of_manager_ids_arguments(parser):
"""This function adds the manager ids argument to the given argument parser.
Args:
parser: An ArgumentParser object used to parse command line arguments.
Returns:
None
"""
parser.add_argument(
"-m",
"--manager-ids",
nargs="+",
type=str,
default=[uuid.uuid4().hex],
help="List of manager IDs that can create environments.",
)
def add_study_arguments(parser):
"""This function adds the study configuration argument to the given argument parser.
Args:
parser (argparse.ArgumentParser): The argument parser object.
Example:
```python
import argparse
parser = argparse.ArgumentParser()
add_study_arguments(parser)
```
"""
parser.add_argument(
"--study-config",
type=str,
default=ROOT_DIR / "configs" / "study" / "study_config.yaml",
help="The config of the study.",
)
def add_gui_arguments(parser):
"""Adds the gui debug argument to the given argument parser.
If set, additional debug / admin elements are shown.
Args:
parser (argparse.ArgumentParser): The argument parser object.
Example:
```python
import argparse
parser = argparse.ArgumentParser()
add_gui_arguments(parser)
```
"""
parser.add_argument(
"--do-study",
help="Start GUI in Fullscreen and do not show configuration and quit buttons.",
action="store_true",
)
parser.add_argument(
"--debug",
help="Debug GUI. No need for a study server. Select layouts in GUI. You need to specify a manager id for the game server.",
action="store_true",
)
def ssl_argument(parser: ArgumentParser):
"""Add the ssl argument to a parser.
Args:
parser: the parser to add the argument.
"""
parser.add_argument(
"--ssl",
action="store_true",
help="Use SSL certificate. Connect to https and wss.",
)
def visualization_config_argument(parser: ArgumentParser):
"""Add the visualization config argument to a parser.
Args:
parser: the parser to add the argument.
"""
parser.add_argument(
"-v",
"--visualization-config",
type=FileType("r", encoding="UTF-8"),
default=ROOT_DIR / "pygame_2d_vis" / "visualization.yaml",
)
def output_file_argument(parser: ArgumentParser, default_file: str):
"""Add the output file argument to a parser.
Args:
parser: the parser to add the argument.
default_file: the default file name (relative location)
"""
parser.add_argument(
"-o",
"--output-file",
type=str,
default=default_file,
)
def create_replay_parser(parser: ArgumentParser):
"""Add individually arguments for parsers that use the replay function of cooperative cuisine.
Args:
parser (ArgumentParser): The argument parser object.
"""
parser.add_argument("-j", "--json_state", help="Json states file path", type=str)
visualization_config_argument(parser)
parser.add_argument(
"-o",
"--output",
type=str,
default="<json_state_name>",
)
parser.add_argument(
"-p",
"--player-id",
type=str,
default=None,
help="Render view for specific player",
)
parser.add_argument(
"-g",
"--grid-size",
type=int,
default=40,
help="Number pixel for one cell in the grid.",
)
parser.add_argument(
"-a",
"--action-recording",
type=str,
default=None,
help="The path to the action recording",
)
parser.add_argument(
"-e",
"--env-configs",
type=str,
default=None,
help="The path to the environment config logs",
)
parser.add_argument(
"-s",
"--step-duration",
type=float,
default=1 / 200,
help="Step duration in seconds between environment steps.",
)
parser.add_argument(
"-f",
"--fps",
type=int,
default=24,
help="Frames per second to render images from the environment",
)
parser.add_argument(
"-d", "--display", action="store_true", help="Show generated images."
)
parser.add_argument(
"-n",
"--number-player",
type=int,
default=1,
help="Number of player to visualize. Should be the same as played the game.",
)
parser.add_argument(
"-b",
"--break-when-no-action",
action="store_true",
help="Stop rendering when no more actions are available.",
)
parser.add_argument(
"--video",
"--video-source",
type=str,
help="Create a video from a folder full of images.",
)
def create_screenshot_parser(parser: ArgumentParser):
"""Add cli arguments for running only the screenshot generation.
Args:
parser (ArgumentParser): The argument parser object.
"""
parser.add_argument(
"-s",
"--state",
type=FileType("r", encoding="UTF-8"),
default=ROOT_DIR / "pygame_2d_vis" / "sample_state.json",
)
visualization_config_argument(parser)
output_file_argument(parser, ROOT_DIR / "generated" / "screenshot.jpg")
def create_game_server_parser(parser: ArgumentParser):
"""Add cli arguments for running only a game server. For the game-server subcommand.
Args:
parser (ArgumentParser): The argument parser object.
"""
game_server_arguments(parser)
disable_websocket_logging_arguments(parser)
add_list_of_manager_ids_arguments(parser)
ssl_argument(parser)
def create_study_server_parser(parser: ArgumentParser):
"""Add cli arguments for running only a study server. For the study-server subcommand.
Args:
parser (ArgumentParser): The argument parser object.
"""
study_server_arguments(parser)
# TODO study server can handle several game server
game_server_arguments(parser)
add_list_of_manager_ids_arguments(parser=parser)
add_study_arguments(parser=parser)
ssl_argument(parser)
def create_gui_parser(parser: ArgumentParser):
"""Add cli arguments for running only a gui instance. For the gui / client subcommand.
Args:
parser (ArgumentParser): The argument parser object.
"""
study_server_arguments(parser)
disable_websocket_logging_arguments(parser)
add_list_of_manager_ids_arguments(parser)
add_gui_arguments(parser)
ssl_argument(parser)
game_server_arguments(parser)
def create_normal_parser(parser: ArgumentParser):
"""Add cli arguments for running the servers and one gui instance. For the start subcommand.
Args:
parser (ArgumentParser): The argument parser object.
"""
game_server_arguments(parser)
study_server_arguments(parser)
disable_websocket_logging_arguments(parser)
add_list_of_manager_ids_arguments(parser)
add_gui_arguments(parser)
add_study_arguments(parser)
ssl_argument(parser)
def create_server_parser(parser: ArgumentParser):
"""Add cli arguments for running the servers (game and study but no gui). For the server subcommand.
Args:
parser (ArgumentParser): The argument parser object.
"""
game_server_arguments(parser)
study_server_arguments(parser)
disable_websocket_logging_arguments(parser)
ssl_argument(parser)
add_list_of_manager_ids_arguments(parser)
add_study_arguments(parser)
......@@ -132,3 +132,5 @@ levels:
num_players: 1
num_bots: 0
study_log_path: USER_LOG_DIR/ENV_NAME/
\ No newline at end of file
......@@ -30,6 +30,7 @@ from starlette.websockets import WebSocketDisconnect
from typing_extensions import TypedDict
from cooperative_cuisine.action import Action
from cooperative_cuisine.argument_parser import create_game_server_parser
from cooperative_cuisine.environment import Environment
from cooperative_cuisine.server_results import (
CreateEnvResult,
......@@ -37,9 +38,6 @@ from cooperative_cuisine.server_results import (
PlayerRequestResult,
)
from cooperative_cuisine.utils import (
url_and_port_arguments,
add_list_of_manager_ids_arguments,
disable_websocket_logging_arguments,
setup_logging,
UUID_CUTOFF,
)
......@@ -831,6 +829,7 @@ 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
):
print("Manager IDs:", manager_ids)
setup_logging(enable_websocket_logging)
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
......@@ -849,10 +848,7 @@ if __name__ == "__main__":
epilog="For further information, see "
"https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/cooperative_cuisine.html",
)
url_and_port_arguments(parser)
disable_websocket_logging_arguments(parser)
add_list_of_manager_ids_arguments(parser)
create_game_server_parser(parser)
args = parser.parse_args()
main(args.game_url, args.game_port, args.manager_ids, args.enable_websocket_logging)
"""
......
......@@ -2,7 +2,6 @@ import argparse
import colorsys
import json
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
......@@ -13,6 +12,7 @@ import yaml
from scipy.spatial import KDTree
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.argument_parser import create_screenshot_parser
from cooperative_cuisine.environment import Environment
from cooperative_cuisine.pygame_2d_vis.game_colors import colors, RGB
from cooperative_cuisine.state_representation import (
......@@ -1035,7 +1035,7 @@ def generate_recipe_images(config: dict, folder_path: str | Path):
pygame.image.save(screen, f"{folder_path}/{graph_dict['meal']}.png")
def main(args):
def main(cli_args):
"""Runs the Cooperative Cuisine Image Generation process.
This method takes command line arguments to specify the state file, visualization configuration file, and output
......@@ -1049,37 +1049,21 @@ def main(args):
-o, --output_file: A command line argument of type `str`. Specifies the output file path for the generated image. If not provided, the default value is 'ROOT_DIR / "generated" / "screenshot.jpg"'.
"""
parser = argparse.ArgumentParser(
prog="Cooperative Cuisine Image Generation",
description="Generate images for a state in json.",
epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html",
)
parser.add_argument(
"-s",
"--state",
type=argparse.FileType("r", encoding="UTF-8"),
default=ROOT_DIR / "pygame_2d_vis" / "sample_state.json",
)
parser.add_argument(
"-v",
"--visualization_config",
type=argparse.FileType("r", encoding="UTF-8"),
default=ROOT_DIR / "pygame_2d_vis" / "visualization.yaml",
)
parser.add_argument(
"-o",
"--output_file",
type=str,
default=ROOT_DIR / "generated" / "screenshot.jpg",
)
args = parser.parse_args(args)
with open(args.visualization_config, "r") as f:
with open(cli_args.visualization_config, "r") as f:
viz_config = yaml.safe_load(f)
with open(args.state, "r") as f:
with open(cli_args.state, "r") as f:
state = json.load(f)
save_screenshot(state, viz_config, args.output_file)
generate_recipe_images(viz_config, args.output_file.parent)
save_screenshot(state, viz_config, cli_args.output_file)
generate_recipe_images(viz_config, cli_args.output_file.parent)
if __name__ == "__main__":
main(sys.argv[1:])
parser = argparse.ArgumentParser(
prog="Cooperative Cuisine Image Generation",
description="Generate images for a state in json.",
epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html",
)
create_screenshot_parser(parser)
args = parser.parse_args()
main(args)
......@@ -21,6 +21,7 @@ from websockets.sync.client import connect
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.action import ActionType, InterActionData, Action
from cooperative_cuisine.argument_parser import create_gui_parser
from cooperative_cuisine.game_server import (
CreateEnvironmentConfig,
WebsocketMessage,
......@@ -31,11 +32,7 @@ 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,
disable_websocket_logging_arguments,
add_list_of_manager_ids_arguments,
setup_logging,
add_gui_arguments,
)
......@@ -129,14 +126,14 @@ class PyGameGUI:
game_host: str,
game_port: int,
manager_ids: list[str],
CONNECT_WITH_STUDY_SERVER: bool,
USE_AAAMBOS_AGENT: bool,
debug: bool,
do_study: bool,
use_ssl: bool,
):
self.CONNECT_WITH_STUDY_SERVER = CONNECT_WITH_STUDY_SERVER
self.USE_AAAMBOS_AGENT = USE_AAAMBOS_AGENT
self.show_debug_elements = debug
self.show_debug_elements = not do_study
self.CONNECT_WITH_STUDY_SERVER = not debug
pygame.init()
pygame.display.set_icon(
......@@ -152,10 +149,12 @@ class PyGameGUI:
self.websockets = {}
if CONNECT_WITH_STUDY_SERVER:
self.request_url = f"http://{study_host}:{study_port}"
if self.CONNECT_WITH_STUDY_SERVER:
self.request_url = (
f"http{'s' if use_ssl else ''}://{study_host}:{study_port}"
)
else:
self.request_url = f"http://{game_host}:{game_port}"
self.request_url = f"http{'s' if use_ssl else ''}://{game_host}:{game_port}"
self.manager_id = random.choice(manager_ids)
......@@ -206,7 +205,7 @@ class PyGameGUI:
self.switch_score_color: bool = False
self.count_frames_score_label: int = 0
self.fullscreen = False if self.show_debug_elements else True
self.fullscreen = do_study
self.menu_state = MenuStates.Start
self.manager: pygame_gui.UIManager
......@@ -2226,9 +2225,10 @@ def main(
game_url: str,
game_port: int,
manager_ids: list[str],
CONNECT_WITH_STUDY_SERVER=False,
USE_AAAMBOS_AGENT=False,
debug=False,
do_study=False,
use_ssl=False,
):
setup_logging()
gui = PyGameGUI(
......@@ -2237,9 +2237,10 @@ def main(
game_host=game_url,
game_port=game_port,
manager_ids=manager_ids,
CONNECT_WITH_STUDY_SERVER=CONNECT_WITH_STUDY_SERVER,
USE_AAAMBOS_AGENT=USE_AAAMBOS_AGENT,
debug=debug,
do_study=do_study,
use_ssl=use_ssl,
)
gui.start_pygame()
......@@ -2251,11 +2252,7 @@ if __name__ == "__main__":
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)
add_gui_arguments(parser)
create_gui_parser(parser)
args = parser.parse_args()
main(
args.study_url,
......@@ -2263,6 +2260,7 @@ if __name__ == "__main__":
args.game_url,
args.game_port,
args.manager_ids,
CONNECT_WITH_STUDY_SERVER=True,
debug=args.do_study,
debug=args.debug,
do_study=args.do_study,
use_ssl=args.ssl,
)
......@@ -7,29 +7,28 @@ based on the actions. Until now, we did not find any deviations from the json st
# CLI
Sequence of images replay from actions:
```bash
python video_replay.py -a ~/.local/state/cooperative_cuisine/log/ENV_NAME/actions.jsonl -e ~/.local/state/cooperative_cuisine/log/ENV_NAME/env_configs.jsonl -d -n 2 -p "0"
cooperative-cuisine replay -a ~/.local/state/cooperative_cuisine/log/ENV_NAME/actions.jsonl -e ~/.local/state/cooperative_cuisine/log/ENV_NAME/env_configs.jsonl -d -n 2 -p "0"
```
Sequence of images replay from json states:
```bash
python video_replay.py -j ~/.local/state/cooperative_cuisine/log/ENV_NAME/json_states.jsonl -d -p "0"
cooperative-cuisine replay -j ~/.local/state/cooperative_cuisine/log/ENV_NAME/json_states.jsonl -d -p "0"
```
The `display` (`-d`, `--display`) requires `opencv-python` (cv2) installed. (`pip install opencv-python`)
Generate a video file from images (requires also `opencv-python`):
```bash
python video_replay.py --video ~/.local/state/cooperative_cuisine/log/ENV_NAME/DIR_NAME_WITH_IMAGES
cooperative-cuisine replay --video ~/.local/state/cooperative_cuisine/log/ENV_NAME/DIR_NAME_WITH_IMAGES
```
For additional CLI arguments:
```bash
python video_replay.py -h
cooperative-cuisine replay -h
```
# Code Documentation
"""
import argparse
import json
import os
import os.path
......@@ -42,8 +41,10 @@ import yaml
from PIL import Image
from tqdm import tqdm
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.action import Action
from cooperative_cuisine.argument_parser import (
create_replay_parser,
)
from cooperative_cuisine.environment import Environment
from cooperative_cuisine.pygame_2d_vis.drawing import Visualizer
from cooperative_cuisine.recording import FileRecorder
......@@ -297,127 +298,63 @@ def video_from_images(image_paths, video_name, fps):
print("See:", video_name)
if __name__ == "__main__":
parser = ArgumentParser(
prog="Cooperative Cuisine Video Generation",
description="Generate videos from recorded data.",
epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html",
)
parser.add_argument("-j", "--json_state", help="Json states file path", type=str)
parser.add_argument(
"-v",
"--visualization_config",
type=argparse.FileType("r", encoding="UTF-8"),
default=ROOT_DIR / "pygame_2d_vis" / "visualization.yaml",
)
parser.add_argument(
"-o",
"--output",
type=str,
default="<json_state_name>",
)
parser.add_argument(
"-p",
"--player_id",
type=str,
default=None,
help="Render view for specific player",
)
parser.add_argument(
"-g",
"--grid_size",
type=int,
default=GRID_SIZE_DEFAULT,
help="Number pixel for one cell in the grid.",
)
parser.add_argument(
"-a",
"--action_recording",
type=str,
default=None,
help="The path to the action recording",
)
parser.add_argument(
"-e",
"--env_configs",
type=str,
default=None,
help="The path to the environment config logs",
)
parser.add_argument(
"-s",
"--step_duration",
type=float,
default=1 / STEP_DURATION_DEFAULT,
help="Step duration in seconds between environment steps.",
)
parser.add_argument(
"-f",
"--fps",
type=int,
default=FPS_DEFAULT,
help="Frames per second to render images from the environment",
)
parser.add_argument(
"-d", "--display", action="store_true", help="Show generated images."
)
parser.add_argument(
"-n",
"--number_player",
type=int,
default=NUMBER_PLAYERS_DEFAULT,
help="Number of player to visualize. Should be the same as played the game.",
)
parser.add_argument(
"-b",
"--break_when_no_action",
action="store_true",
help="Stop rendering when no more actions are available.",
)
parser.add_argument(
"--video",
"--video_source",
type=str,
help="Create a video from a folder full of images.",
)
args = parser.parse_args()
with open(args.visualization_config, "r") as f:
def main(cli_args):
"""
Runs the video replay with the given command line arguments.
Args:
cli_args: An object containing the command line arguments.
"""
with open(cli_args.visualization_config, "r") as f:
viz_config = yaml.safe_load(f)
if args.json_state:
if cli_args.json_state:
target_directory = (
args.json_state.rsplit(".", maxsplit=1)[0]
if args.output == "<json_state_name>"
else args.output
cli_args.json_state.rsplit(".", maxsplit=1)[0]
if cli_args.output == "<json_state_name>"
else cli_args.output
)
from_json_states(
args.json_state,
cli_args.json_state,
viz_config,
target_directory,
args.player_id,
args.display,
args.grid_size,
cli_args.player_id,
cli_args.display,
cli_args.grid_size,
)
elif args.video:
elif cli_args.video:
target_directory = (
args.video + ".mp4" if args.output == "<json_state_name>" else args.output
cli_args.video + ".mp4"
if cli_args.output == "<json_state_name>"
else cli_args.output
)
video_from_images(args.video, target_directory, args.fps)
video_from_images(cli_args.video, target_directory, cli_args.fps)
else:
target_directory = (
args.action_recording.rsplit(".", maxsplit=1)[0]
if args.output == "<json_state_name>"
else args.output
cli_args.action_recording.rsplit(".", maxsplit=1)[0]
if cli_args.output == "<json_state_name>"
else cli_args.output
)
simulate(
args.action_recording,
args.env_configs,
cli_args.action_recording,
cli_args.env_configs,
viz_config,
target_directory,
args.player_id,
args.step_duration,
args.fps,
args.display,
args.number_player,
args.break_when_no_action,
args.grid_size,
cli_args.player_id,
cli_args.step_duration,
cli_args.fps,
cli_args.display,
cli_args.number_player,
cli_args.break_when_no_action,
cli_args.grid_size,
)
if __name__ == "__main__":
parser = ArgumentParser(
prog="Cooperative Cuisine Video Generation",
description="Generate videos from recorded data.",
epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html",
)
create_replay_parser(parser)
args = parser.parse_args()
main(args)
......@@ -10,9 +10,9 @@ python game_server.py --manager-ids COPIED_UUID
The environment starts when all players connected.
"""
import argparse
import asyncio
import json
import logging
import os
import signal
......@@ -25,18 +25,16 @@ from typing import Tuple, Any
import requests
import uvicorn
import yaml
from fastapi import FastAPI, HTTPException
from fastapi import FastAPI, HTTPException, Request
from pydantic import BaseModel
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.argument_parser import create_study_server_parser
from cooperative_cuisine.environment import EnvironmentConfig
from cooperative_cuisine.game_server import CreateEnvironmentConfig, EnvironmentData
from cooperative_cuisine.server_results import PlayerInfo, CreateEnvResult
from cooperative_cuisine.utils import (
url_and_port_arguments,
add_list_of_manager_ids_arguments,
expand_path,
add_study_arguments,
deep_update,
UUID_CUTOFF,
)
......@@ -295,7 +293,7 @@ class Study:
self.next_level()
def get_connection(
self, participant_id: str
self, participant_id: str, participant_host: str
) -> Tuple[dict[str, PlayerInfo] | None, LevelInfo | None]:
"""Get the assigned connections to the game server for a participant.
......@@ -319,6 +317,22 @@ class Study:
number_players=len(self.current_running_env["player_info"]),
kitchen_size=self.current_running_env["kitchen_size"],
)
log_path = expand_path(
self.study_config["study_log_path"],
env_name=self.current_running_env["env_id"],
)
os.makedirs(log_path, exist_ok=True)
with open(Path(log_path) / "study_log", "a") as log_file:
log_file.write(
json.dumps(
{
"env_id": self.current_running_env["env_id"],
"participant_ip": participant_host,
"level_info": level_info.dict(),
"player_info": player_info,
}
)
)
return player_info, level_info
else:
raise HTTPException(
......@@ -403,7 +417,7 @@ class StudyManager:
"""Port of the game server where the studies are running their environments."""
self.game_server_url: str = ""
"""Combined URL of the game server where the studies are running their environments."""
self.create_game_server_url()
self.create_game_server_url(use_ssl=False)
self.server_manager_id: str = ""
"""Manager id of this manager which will be registered in the game server."""
......@@ -467,7 +481,7 @@ class StudyManager:
raise HTTPException(status_code=409, detail="Participant not in any study.")
def get_participant_game_connection(
self, participant_id: str
self, participant_id: str, participant_host: str
) -> Tuple[dict[str, PlayerInfo], LevelInfo]:
"""Get the assigned connections to the game server for a participant.
......@@ -495,12 +509,15 @@ class StudyManager:
if participant_id in self.participant_id_to_study_map.keys():
assigned_study = self.participant_id_to_study_map[participant_id]
player_info, level_info = assigned_study.get_connection(participant_id)
player_info, level_info = assigned_study.get_connection(
participant_id, participant_host
)
return player_info, level_info
else:
raise HTTPException(status_code=409, detail="Participant not in any study.")
def set_game_server_url(self, game_host: str, game_port: int):
def set_game_server_url(self, game_host: str, game_port: int, use_ssl: bool):
"""Set the game server host address, port and combined url. These values are set this way because
the fastapi requests act on top level of the python script.
......@@ -510,10 +527,12 @@ class StudyManager:
"""
self.game_host = game_host
self.game_port = game_port
self.create_game_server_url()
self.create_game_server_url(use_ssl)
def create_game_server_url(self):
self.game_server_url = f"http://{self.game_host}:{self.game_port}"
def create_game_server_url(self, use_ssl: bool):
self.game_server_url = (
f"http{'s' if use_ssl else ''}://{self.game_host}:{self.game_port}"
)
def set_manager_id(self, manager_id: str):
"""Set the manager id of the study server. This value is set this way because
......@@ -623,6 +642,7 @@ async def level_done(participant_id: str):
@app.post("/get_game_connection/{participant_id}")
async def get_game_connection(
participant_id: str,
request: Request,
) -> dict[str, dict[str, PlayerInfo] | LevelInfo]:
"""Request to get the connection to the game server of a participant.
......@@ -634,7 +654,7 @@ async def get_game_connection(
"""
player_info, level_info = study_manager.get_participant_game_connection(
participant_id
participant_id, request.client.host if request.client is not None else "Test"
)
return {"player_info": player_info, "level_info": level_info}
......@@ -664,8 +684,18 @@ async def disconnect_from_tutorial(participant_id: str):
study_manager.end_tutorial(participant_id)
def main(study_host, study_port, game_host, game_port, manager_ids, study_config_path):
study_manager.set_game_server_url(game_host=game_host, game_port=game_port)
def main(
study_host,
study_port,
game_host,
game_port,
manager_ids,
study_config_path,
use_ssl,
):
study_manager.set_game_server_url(
game_host=game_host, game_port=game_port, use_ssl=use_ssl
)
study_manager.set_manager_id(manager_id=manager_ids[0])
study_manager.set_study_config(study_config_path=study_config_path)
......@@ -685,15 +715,8 @@ if __name__ == "__main__":
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",
)
add_list_of_manager_ids_arguments(parser=parser)
add_study_arguments(parser=parser)
create_study_server_parser(parser)
args = parser.parse_args()
game_server_url = f"https://{args.game_url}:{args.game_port}"
main(
args.study_url,
args.study_port,
......@@ -701,4 +724,5 @@ if __name__ == "__main__":
game_port=args.game_port,
manager_ids=args.manager_ids,
study_config_path=args.study_config,
use_ssl=args.ssl,
)
......@@ -9,7 +9,6 @@ import json
import logging
import os
import sys
import uuid
from collections import deque
from datetime import datetime, timedelta
from enum import Enum
......@@ -26,15 +25,6 @@ if TYPE_CHECKING:
from cooperative_cuisine.counters import Counter
from cooperative_cuisine.player import Player
DEFAULT_SERVER_URL = "localhost"
"""Default server URL of game and server study."""
DEFAULT_SERVER_PORT = 8080
"""Default study server port."""
DEFAULT_GAME_PORT = 8000
"""Default game server port."""
UUID_CUTOFF = 8
"""The cutoff length for UUIDs."""
......@@ -297,131 +287,6 @@ def setup_logging(enable_websocket_logging=False):
logging.getLogger("websockets.client").setLevel(logging.ERROR)
def url_and_port_arguments(
parser,
server_name="game server",
default_study_port=DEFAULT_SERVER_PORT,
default_game_port=DEFAULT_GAME_PORT,
default_server_url=DEFAULT_SERVER_URL,
):
"""Adds arguments to the given parser for the URL and port configuration of a server.
Args:
parser: The argument parser to add arguments to.
server_name: (Optional) The name of the server. Defaults to "game server".
default_study_port: (Optional) The default port number for the study URL. Defaults to 8080.
default_game_port: (Optional) The default port number for the game URL. Defaults to 8000.
default_server_url: (Optional) The default url for the server. Defaults to "localhost".
"""
parser.add_argument(
"-s",
"--study-url",
"--study-host",
type=str,
default=default_server_url,
help=f"Overcooked {server_name} study host url.",
)
parser.add_argument(
"-sp",
"--study-port",
type=int,
default=default_study_port,
help=f"Port number for the {server_name}",
)
parser.add_argument(
"-g",
"--game-url",
"--game-host",
type=str,
default=DEFAULT_SERVER_URL,
help=f"Overcooked {server_name} game server url.",
)
parser.add_argument(
"-gp",
"--game-port",
type=int,
default=default_game_port,
help=f"Port number for the {server_name}",
)
def disable_websocket_logging_arguments(parser):
"""Disables the logging of WebSocket arguments in the provided parser.
Args:
parser: The argument parser object (argparse.ArgumentParser) to which the
"--enable-websocket-logging" argument will be added.
"""
parser.add_argument(
"--enable-websocket-logging" "", action="store_true", default=True
)
def add_list_of_manager_ids_arguments(parser):
"""This function adds the manager ids argument to the given argument parser.
Args:
parser: An ArgumentParser object used to parse command line arguments.
Returns:
None
"""
parser.add_argument(
"-m",
"--manager-ids",
nargs="+",
type=str,
default=[uuid.uuid4().hex],
help="List of manager IDs that can create environments.",
)
def add_study_arguments(parser):
"""This function adds the study configuration argument to the given argument parser.
Args:
parser (argparse.ArgumentParser): The argument parser object.
Example:
```python
import argparse
parser = argparse.ArgumentParser()
add_study_arguments(parser)
```
"""
parser.add_argument(
"--study-config",
type=str,
default=ROOT_DIR / "configs" / "study" / "study_config.yaml",
help="The config of the study.",
)
def add_gui_arguments(parser):
"""Adds the gui debug argument to the given argument parser.
If set, additional debug / admin elements are shown.
Args:
parser (argparse.ArgumentParser): The argument parser object.
Example:
```python
import argparse
parser = argparse.ArgumentParser()
add_gui_arguments(parser)
```
"""
parser.add_argument(
"--do-study",
default=True,
help="Disable additional debug / admin elements.",
action="store_false",
)
class NumpyAndDataclassEncoder(json.JSONEncoder):
"""Special json encoder for numpy types"""
......
......@@ -47,7 +47,7 @@ setup(
],
description="The real-time overcooked simulation for a cognitive cooperative system",
entry_points={
"console_scripts": ["cooperative_cuisine = cooperative_cuisine.__main__:main"]
"console_scripts": ["cooperative-cuisine = cooperative_cuisine.__main__:main"]
},
install_requires=requirements,
license="MIT license",
......
import json
import os
from argparse import ArgumentParser
import yaml
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.argument_parser import create_screenshot_parser
from cooperative_cuisine.pygame_2d_vis.drawing import (
calc_angle,
Visualizer,
......@@ -25,7 +27,9 @@ def test_calc_angle():
def test_drawing():
main([])
parser = ArgumentParser()
create_screenshot_parser(parser)
main(parser.parse_args([]))
assert os.path.exists(os.path.join(ROOT_DIR, "generated", "screenshot.jpg"))
......
......@@ -4,18 +4,34 @@ from argparse import ArgumentParser
import networkx
import pytest
from cooperative_cuisine.argument_parser import (
game_server_arguments,
study_server_arguments,
disable_websocket_logging_arguments,
add_list_of_manager_ids_arguments,
add_gui_arguments,
add_study_arguments,
ssl_argument,
create_study_server_parser,
create_game_server_parser,
create_gui_parser,
create_replay_parser,
create_normal_parser,
create_server_parser,
)
from cooperative_cuisine.environment import Environment
from cooperative_cuisine.pygame_2d_vis.video_replay import (
GRID_SIZE_DEFAULT,
STEP_DURATION_DEFAULT,
FPS_DEFAULT,
NUMBER_PLAYERS_DEFAULT,
)
from cooperative_cuisine.state_representation import (
create_movement_graph,
restrict_movement_graph,
astar_heuristic,
)
from cooperative_cuisine.utils import (
url_and_port_arguments,
add_list_of_manager_ids_arguments,
disable_websocket_logging_arguments,
add_study_arguments,
add_gui_arguments,
create_layout_with_counters,
setup_logging,
)
......@@ -23,19 +39,20 @@ from tests.test_start import env_config_no_validation
from tests.test_start import layout_empty_config, item_info
def test_parser_gen():
def test_arguments():
parser = ArgumentParser()
url_and_port_arguments(parser)
game_server_arguments(parser)
study_server_arguments(parser)
disable_websocket_logging_arguments(parser)
add_list_of_manager_ids_arguments(parser)
add_study_arguments(parser)
add_gui_arguments(parser)
add_study_arguments(parser)
ssl_argument(parser)
parser.parse_args(
[
"-s",
"localhost",
"-sp",
"-p",
"8000",
"-g",
"localhost",
......@@ -49,6 +66,41 @@ def test_parser_gen():
)
def test_parser_creation():
parser = ArgumentParser()
create_game_server_parser(parser)
parser = ArgumentParser()
create_study_server_parser(parser)
parser = ArgumentParser()
create_gui_parser(parser)
parser = ArgumentParser()
create_replay_parser(parser)
print(parser)
grid_size_action = [
a for a in parser._optionals._actions if "--grid-size" in a.option_strings
][0]
assert grid_size_action.default == GRID_SIZE_DEFAULT
number_player_action = [
a for a in parser._optionals._actions if "--number-player" in a.option_strings
][0]
assert number_player_action.default == NUMBER_PLAYERS_DEFAULT
fps_action = [a for a in parser._optionals._actions if "--fps" in a.option_strings][
0
]
assert fps_action.default == FPS_DEFAULT
step_duration_action = [
a for a in parser._optionals._actions if "--step-duration" in a.option_strings
][0]
assert step_duration_action.default == 1 / STEP_DURATION_DEFAULT
parser = ArgumentParser()
create_server_parser(parser)
parser = ArgumentParser()
create_normal_parser(parser)
def test_layout_creation():
assert """###\n#_#\n###\n""" == create_layout_with_counters(3, 3)
assert """###\n#_#\n#_#\n###\n""" == create_layout_with_counters(3, 4)
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment