Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • scs/cocosy/cooperative-cuisine
1 result
Show changes
Commits on Source (6)
  • Florian Schröder's avatar
    Add logging for participant connections · 4b7855cc
    Florian Schröder authored
    A logging functionality has been added to record participant connections in the study_server.py. The logs, which include the participant's IP, environment ID, level info, and player info, are stored in the directory specified in the study_config.yaml file. Moreover, the participant's host IP is now provided when retrieving their game connection.
    4b7855cc
  • Florian Schröder's avatar
    Handle None client in game connection request · bf70afdc
    Florian Schröder authored
    The update modifies the get_participant_game_connection method in the study server. Now, the method uses "Test" as the default host if the request client is None. This ensures that the functionality will not break when dealing with test or invalid client requests.
    bf70afdc
  • Florian Schröder's avatar
    Extract argument parser creation into separate file · 27389f93
    Florian Schröder authored
    Moved the creation of argparse.ArgumentParser objects used to parse command line arguments into a separate module, `argument_parser.py`. This includes organizing all the add_argument calls into specific functions within this new module, in order to increase code readability, reusability and organization within the argument parsing codebase. All existing usages have been updated accordingly.
    27389f93
  • Florian Schröder's avatar
    Update SSL usage and enhance argument parsing · 843b145f
    Florian Schröder authored
    With this commit, the game now supports SSL connections. In addition, the argument parsing workflow was fundamentally improved to provide a clearer, better-structured usage of command line arguments for different functionalities. It also includes updates to the help documentations - now, each of these functionalities are accessible as subcommands with their own help messages. A minor update was also made to the test suite to reflect these changes.
    843b145f
  • Florian Schröder's avatar
    Merge remote-tracking branch 'origin/dev' into 113-complete-cooperative-cousine-terminal-command · 67de4612
    Florian Schröder authored
    # Conflicts:
    #	CHANGELOG.md
    #	cooperative_cuisine/pygame_2d_vis/gui.py
    67de4612
  • Florian Schröder's avatar
    Merge branch '113-complete-cooperative-cousine-terminal-command' into 'dev' · fd11db04
    Florian Schröder authored
    Resolve "Complete Cooperative Cousine Terminal Command"
    
    Closes #113
    
    See merge request scs/cocosy/overcooked-simulator!90
    fd11db04
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)
......