cooperative_cuisine
This is the documentation of Cooperative Cuisine.
About the package
The package contains an environment for cooperation between players/agents. A PyGameGUI visualizes the game to human or virtual agents in 2D. A 3D web-enabled version (for example for online studies, currently under development) can be found here.
For a guided introduction to specific features, have a look at the cooperative_cuisine.tutorials page.
Background / Literature
The overcooked/cooking domain is a well-established cooperation domain/task. There exists environments designed for reinforcement learning agents as well as the game and adaptations of the game for human players in a more "real-time"-like environment. They all mostly differ in the visual and graphics dimension. 2D versions like overcooked-ai, gym-cooking are the most well-known in the community. Besides, the general adaptations of the original overcooked game. Cooperative Cuisine, we want to bring both worlds together: the reinforcement learning and real-time playable environment with an appealing visualization. Enable the potential of developing artificial agents that play with humans like a "real", cooperative, human partner.
Installation
Local Editable Installation
Easy with conda or mamba:
git clone https://gitlab.ub.uni-bielefeld.de/scs/cocosy/cooperative-cuisine.git
conda env create -f cooperative-cuisine/conda.recipe/environment.yaml
conda activate cocu
Otherwise, you need to make sure graphviz is installed. Also, you can install it via pip directly:
conda install -c conda-forge pygraphviz
git clone https://gitlab.ub.uni-bielefeld.de/scs/cocosy/cooperative-cuisine.git
cd cooperative-cuisine
pip install -e .
With these installs, you can just checkout to other branches with git (and if dependencies do not change without pip install -e .), e.g., git checkout dev, and add new configs, images, etc. and change the code.
Library Installation
The correct environment needs to be active:
conda install -c conda-forge pygraphviz # or find another way to install graphviz on your machine
pip install cooperative_cuisine@git+https://gitlab.ub.uni-bielefeld.de/scs/cocosy/cooperative-cuisine@main
You can now use the environment and/or simulator in your python code. Just by importing
it import cooperative_cuisine
Agent Installation
If you want to play with some agents, install them via
pip install cocu_agents@git+https://gitlab.ub.uni-bielefeld.de/scs/cocosy/cocu-agents@main
Usage / Examples
Our Cooperative Cuisine is designed for real time interaction but also for reinforcement learning (gymnasium environment) with time independent step function calls. It focuses on configurability, extensibility and appealing visualization options.
Human Player
Run it via the command line (in your pyenv/conda environment):
cooperative-cuisine start -s localhost -sp 8080 -g localhost -gp 8000
If npm is installed, a web demo using this client will automatically be served by the study server (--web). The PixiJS visualization client can also be easily included in external projects. For more information, see the README.md in the pixijs_2d_vis directory.
The arguments shown are the defaults.
You can also start the Game Server, Study Server (Matchmaking),and the PyGame GUI individually in different terminals.
cooperative-cuisine game-server -g localhost -gp 8000 --manager-ids SECRETKEY1 SECRETKEY2
cooperative-cuisine study-server -s localhost -sp 8080 -g localhost -gp 8000 --manager-ids SECRETKEY1
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.
To start a game server see above. Your own manager needs to create an environment.
import requests
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.game_server import CreateEnvironmentConfig
from cooperative_cuisine.server_results import CreateEnvResult
with open(ROOT_DIR / "configs" / "item_info.yaml", "r", encoding="utf-8") as file:
item_info = file.read()
with open(ROOT_DIR / "configs" / "layouts" / "basic.layout", "r", encoding="utf-8") as file:
layout = file.read()
with open(ROOT_DIR / "configs" / "environment_config.yaml", "r", encoding="utf-8") as file:
environment_config = file.read()
create_env = CreateEnvironmentConfig(
manager_id="SECRETKEY1",
number_players=2,
environment_settings={"all_player_can_pause_game": False},
item_info_config=item_info,
environment_config=environment_config,
layout_config=layout,
seed=123456789,
).model_dump(mode="json")
post_result = requests.post("http://localhost:8000/manage/create_env", json=create_env)
if post_result.status_code == 403:
raise ValueError(f"Forbidden Request: {post_result.json()['detail']}")
env_info: CreateEnvResult = post_result.json()
Connect each player via a websocket (threaded or async).
import json
import dataclasses
from websockets.sync.client import connect
from cooperative_cuisine.action import Action, ActionType, InterActionData
from cooperative_cuisine.utils import custom_asdict_factory
p1_websocket = connect("ws://localhost:8000/ws/player/" + env_info["player_info"]["0"]["client_id"])
# set player "0" as ready
p1_websocket.send(json.dumps({"type": "ready", "player_hash": env_info["player_info"]["0"]["player_hash"]}))
assert json.loads(p1_websocket.recv())["status"] == 200, "not accepted player"
# get the state for player "0", call it on every frame/step
p1_websocket.send(json.dumps({"type": "get_state", "player_hash": env_info["player_info"]["0"]["player_hash"]}))
state = json.loads(p1_websocket.recv())
# send an action for player "0"
# --- movement ---
action = Action(
player="0",
action_type=ActionType.MOVEMENT,
action_data=[0.0, 1.0], # direction (here straight up)
duration=0.5 # seconds
)
# --- pickup/drop off ---
action = Action(
player="0",
action_type=ActionType.PICK_UP_DROP,
action_data=None,
)
# --- interact ---
action = Action(
player="0",
action_type=ActionType.INTERACT,
action_data=InterActionData.START # InterActionData.STOP when to stop the interaction
)
p1_websocket.send(json.dumps({
"type": "action",
"player_hash": env_info["player_info"]["0"]["player_hash"],
"action": dataclasses.asdict(
action, dict_factory=custom_asdict_factory
),
}))
p1_websocket.recv()
Stop the environment if you want the game to end before the time is up.
requests.post(
"http://localhost:8000/manage/stop_env",
json={
"manager_id": "SECRETKEY1",
"env_id": env_info["env_id"],
"reason": "closed environment",
},
)
Direct integration into your code
You can use the cooperative_cuisine.environment.Environment class
and call the step, get_state, and perform_action methods directly.
import json
from datetime import timedelta
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.action import Action
from cooperative_cuisine.environment import Environment
env = Environment(
env_config=ROOT_DIR / "configs" / "environment_config.yaml",
layout_config=ROOT_DIR / "configs" / "layouts" / "basic.layout",
item_info=ROOT_DIR / "configs" / "item_info.yaml"
)
env.add_player("0")
env.add_player("1")
while True:
# adapt this to real time if needed with time.sleep etc.
env.step(timedelta(seconds=0.1))
player_0_state = env.get_state("0")
if player_0_state["ended"]:
break
action = Action(...) # Please refer to the above code but remember to use np.array instead of list for the movement direction vector
env.perform_action(action)
JSON State
The JSON schema for the state of the environment for a player can be generated by running the state_representation.py
python state_representation.py
Should look like - to have an interactive view, have a look at jsonschemaviewer:
{"$defs": {"CookingEquipmentState": {"description": "Format of the state representation of cooking equipment.", "properties": {"id": {"title": "Id", "type": "string"}, "category": {"anyOf": [{"const": "Item"}, {"const": "ItemCookingEquipment"}], "title": "Category"}, "type": {"title": "Type", "type": "string"}, "progress_percentage": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Progress Percentage"}, "inverse_progress": {"title": "Inverse Progress", "type": "boolean"}, "active_effects": {"items": {"$ref": "#/$defs/EffectState"}, "title": "Active Effects", "type": "array"}, "content_list": {"items": {"$ref": "#/$defs/ItemState"}, "title": "Content List", "type": "array"}, "content_ready": {"anyOf": [{"$ref": "#/$defs/ItemState"}, {"type": "null"}]}}, "required": ["id", "category", "type", "progress_percentage", "inverse_progress", "active_effects", "content_list", "content_ready"], "title": "CookingEquipmentState", "type": "object"}, "CounterState": {"description": "Format of the state representation of a counter.", "properties": {"id": {"title": "Id", "type": "string"}, "category": {"const": "Counter", "title": "Category"}, "type": {"title": "Type", "type": "string"}, "pos": {"items": {"type": "number"}, "title": "Pos", "type": "array"}, "orientation": {"items": {"type": "number"}, "title": "Orientation", "type": "array"}, "occupied_by": {"anyOf": [{"items": {"anyOf": [{"$ref": "#/$defs/ItemState"}, {"$ref": "#/$defs/CookingEquipmentState"}]}, "type": "array"}, {"$ref": "#/$defs/ItemState"}, {"$ref": "#/$defs/CookingEquipmentState"}, {"type": "null"}], "title": "Occupied By"}, "active_effects": {"items": {"$ref": "#/$defs/EffectState"}, "title": "Active Effects", "type": "array"}}, "required": ["id", "category", "type", "pos", "orientation", "occupied_by", "active_effects"], "title": "CounterState", "type": "object"}, "EffectState": {"description": "Format of the state representation of an effect (fire).", "properties": {"id": {"title": "Id", "type": "string"}, "type": {"title": "Type", "type": "string"}, "progress_percentage": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Progress Percentage"}, "inverse_progress": {"title": "Inverse Progress", "type": "boolean"}}, "required": ["id", "type", "progress_percentage", "inverse_progress"], "title": "EffectState", "type": "object"}, "ItemState": {"description": "Format of the state representation of an item.", "properties": {"id": {"title": "Id", "type": "string"}, "category": {"anyOf": [{"const": "Item"}, {"const": "ItemCookingEquipment"}], "title": "Category"}, "type": {"title": "Type", "type": "string"}, "progress_percentage": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Progress Percentage"}, "inverse_progress": {"title": "Inverse Progress", "type": "boolean"}, "active_effects": {"items": {"$ref": "#/$defs/EffectState"}, "title": "Active Effects", "type": "array"}}, "required": ["id", "category", "type", "progress_percentage", "inverse_progress", "active_effects"], "title": "ItemState", "type": "object"}, "KitchenInfo": {"description": "Format of the state representation of basic information of the kitchen.", "properties": {"width": {"title": "Width", "type": "number"}, "height": {"title": "Height", "type": "number"}}, "required": ["width", "height"], "title": "KitchenInfo", "type": "object"}, "OrderState": {"description": "Format of the state representation of an order.", "properties": {"id": {"title": "Id", "type": "string"}, "category": {"const": "Order", "title": "Category"}, "meal": {"title": "Meal", "type": "string"}, "start_time": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "string"}], "title": "Start Time"}, "max_duration": {"title": "Max Duration", "type": "number"}, "score": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Score"}}, "required": ["id", "category", "meal", "start_time", "max_duration", "score"], "title": "OrderState", "type": "object"}, "PlayerState": {"description": "Format of the state representation of a player.", "properties": {"id": {"title": "Id", "type": "string"}, "pos": {"items": {"type": "number"}, "title": "Pos", "type": "array"}, "facing_direction": {"items": {"type": "number"}, "title": "Facing Direction", "type": "array"}, "holding": {"anyOf": [{"$ref": "#/$defs/ItemState"}, {"$ref": "#/$defs/CookingEquipmentState"}, {"type": "null"}], "title": "Holding"}, "current_nearest_counter_pos": {"anyOf": [{"items": {"type": "number"}, "type": "array"}, {"type": "null"}], "title": "Current Nearest Counter Pos"}, "current_nearest_counter_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Current Nearest Counter Id"}}, "required": ["id", "pos", "facing_direction", "holding", "current_nearest_counter_pos", "current_nearest_counter_id"], "title": "PlayerState", "type": "object"}, "ViewRestriction": {"description": "Format of the state representation of a view restriction from the players perspectives.
Currently, as a view cone, like a flashlight in the dark.", "properties": {"direction": {"items": {"type": "number"}, "title": "Direction", "type": "array"}, "position": {"items": {"type": "number"}, "title": "Position", "type": "array"}, "angle": {"title": "Angle", "type": "integer"}, "counter_mask": {"anyOf": [{"items": {"type": "boolean"}, "type": "array"}, {"type": "null"}], "title": "Counter Mask"}, "range": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Range"}}, "required": ["direction", "position", "angle", "counter_mask", "range"], "title": "ViewRestriction", "type": "object"}}, "description": "The format of the returned state representation.", "properties": {"players": {"items": {"$ref": "#/$defs/PlayerState"}, "title": "Players", "type": "array"}, "counters": {"items": {"$ref": "#/$defs/CounterState"}, "title": "Counters", "type": "array"}, "kitchen": {"$ref": "#/$defs/KitchenInfo"}, "score": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Score"}, "orders": {"items": {"$ref": "#/$defs/OrderState"}, "title": "Orders", "type": "array"}, "ended": {"title": "Ended", "type": "boolean"}, "env_time": {"format": "date-time", "title": "Env Time", "type": "string"}, "remaining_time": {"title": "Remaining Time", "type": "number"}, "view_restrictions": {"anyOf": [{"items": {"$ref": "#/$defs/ViewRestriction"}, "type": "array"}, {"type": "null"}], "title": "View Restrictions"}, "served_meals": {"items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"type": "string"}, {"type": "string"}], "type": "array"}, "title": "Served Meals", "type": "array"}, "info_msg": {"items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"type": "string"}, {"type": "string"}], "type": "array"}, "title": "Info Msg", "type": "array"}}, "required": ["players", "counters", "kitchen", "score", "orders", "ended", "env_time", "remaining_time", "view_restrictions", "served_meals", "info_msg"], "title": "StateRepresentation", "type": "object"}
The BaseModel and TypedDicts can be found in cooperative_cuisine.state_representation. The
cooperative_cuisine.state_representation.StateRepresentation represents the json state that the get_state
returns.
Generate images from JSON states
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.
cooperative-cuisine screenshot --state my_state.json
- You can specify a different visualization config with
-vor--visualization_config. - You can specify the name of the output file with
-oor--output_file. The default isscreenshot.jpg.
Generate images/videos from recordings
You can record json states or only the actions and the environment config via hooks and the recording class.
If you want to generate images or complete videos, see cooperative_cuisine.pygame_2d_vis.video_replay.
Configuration
The environment configuration is currently done with 3 config files + GUI configuration.
Item Config
The item config defines which ingredients, cooking equipment and meals can exist and how meals and processed ingredients can be cooked/created.
For example
CuttingBoard:
type: Equipment
Stove:
type: Equipment
Pot:
type: Equipment
equipment: Stove
Tomato:
type: Ingredient
ChoppedTomato:
type: Ingredient
needs: [ Tomato ]
seconds: 4.0
equipment: CuttingBoard
TomatoSoup:
type: Meal
needs: [ ChoppedTomato, ChoppedTomato, ChoppedTomato ]
seconds: 6.0
equipment: Pot
Layout Config
You can define the layout of the kitchen via a layout file. The position of counters are based on a grid system, even when the players do not move grid steps but continuous steps. Each character defines a different type of counter. Which character is mapped to which counter is defined in the Environment config.
For example
#QU#FO#TNLB#
@__________M
|__________K
$__________I
#__A____A__D
C__________E
#__________G
C__________#
##PS+#X##S+#
Environment Config
The environment config details how a level/environment is defined. Here, the available plates, meals, order and player configuration is done.
For example
plates:
clean_plates: 1
dirty_plates: 2
plate_delay: [ 5, 10 ]
# range of seconds until the dirty plate arrives.
game:
time_limit_seconds: 300
undo_dispenser_pickup: true
validate_recipes: true
layout_chars:
_: Free
hash: Counter
A: Agent
P: PlateDispenser
C: CuttingBoard
X: Trashcan
W: ServingWindow
S: Sink
+: SinkAddon
U: Pot # with Stove
T: Tomato
orders: # how to create orders
meals:
all: true
...
player_config:
radius: 0.4
speed_units_per_seconds: 6
interaction_range: 1.6
restricted_view: False
view_angle: 70
view_range: 4 # in grid units, can be "null"
effect_manager: # fire effect
...
hook_callbacks: # scores, recording, msgs, etc.
...
PyGame Visualization Config
Here the visualisation for all objects is defined. Reference the images or define a list of base shapes that represent
the counters, ingredients, meals and players. Cooperative Cuisine comes with images for the defined meals.
You can extend it easily. Just have a look at the default visualisation.yml.
Further, here, you can configure the size of the visualization like screen width, button sizes, fps, colors, etc.
You can change the localisation (language) of the visualization by changing the language key in the visualization
config, we provide "en" and "de" translations. You can add own languages by adding i18 translation files to the
pygame_2d_vis/locals folder and continue_{lang}.png, controls_{lang}.png, try_{lang}.png images in the pygame_2d_vis/gui_images folder (we provide a tutorial.drawio template file.
You can edit it online at draw.io).
Study Config
You can setup a study with a study config. It defines which levels the player will play after they connect to the study server. Further, you define how many players play together within an environment.
levels:
- config_path: STUDY_DIR/level1/level1_config.yaml
layout_path: LAYOUTS_DIR/overcooked-1/1-1-far-apart.layout
item_info_path: STUDY_DIR/level1/level1_item_info.yaml
name: "Level 1-1: Far Apart"
- config_path: CONFIGS_DIR/environment_config.yaml
layout_path: LAYOUTS_DIR/basic.layout
item_info_path: CONFIGS_DIR/item_info.yaml
name: "Basic"
- config_path: STUDY_DIR/level2/level2_config.yaml
layout_path: LAYOUTS_DIR/overcooked-1/1-4-bottleneck.layout
item_info_path: STUDY_DIR/level2/level2_item_info.yaml
name: "Level 1-4: Bottleneck"
num_players: 1
num_bots: 0
STUDY_DIR, LAYOUTS_DIR, CONFIG_DIR, etc. will be replaced accordingly to their names.
Citation
Structure of the Documentation
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 actions the player can perform,
- the argument parser definitions for the cli,
- the base agent class, for abstracting some agent logic (e.g. path planning),
- 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.,
- the effect manager, how the fire effect is defined,
- the environment, handles the incoming actions and provides the state,
- the game server, which can manage several running environments and can communicates via FastAPI post requests and websockets,
- the hooks, you can adapt and extend recording, scoring, msgs to players, etc. via hooks,
- the hook addons to extend the environment for custom features,
- the info msgs to players, based on hook events, short text msgs can be generated,
- the game items, the holdable ingredients, cooking equipment, composed ingredients, and meals,
- the calculation of player movement,
- the orders, how to sample incoming orders and their attributes,
- the player/agent, that interacts in the environment,
- the pygame 2d visualization, GUI, drawing, and video generation,
- the pixijs 2d visualization for web apps,
- the recording, via hooks, actions, environment configs, states, etc. can be recorded in files,
- the scores, via hooks, events can affect the scores,
- type hints are defined in state representation for the json state and server results for the data returned by the game server in post requests.
- the study server, the match making server,
- utility code,
- the config validation and graph generation,
- the interface for different voice chat APIs (currently custom build CoCuVc und Mumble).
License
Cooperative Cuisine © 2025 by Social Cognitive Systems Group is licensed under CC BY-NC-SA 4.0 
1""" 2 3This is the documentation of Cooperative Cuisine. 4 5# About the package 6 7The package contains an environment for cooperation between players/agents. A PyGameGUI visualizes the game to 8human or virtual agents in 2D. A 3D web-enabled version (for example for online studies, currently under development) 9can be found [here](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/godot-overcooked-3d-visualization). 10 11For a guided introduction to specific features, have a look at the `cooperative_cuisine.tutorials` page. 12 13 14# Background / Literature 15The overcooked/cooking domain is a well-established cooperation domain/task. There exists 16environments designed for reinforcement learning agents as well as the game and adaptations of the game for human 17players in a more "real-time"-like environment. They all mostly differ in the visual and graphics dimension. 2D 18versions like [overcooked-ai](https://github.com/HumanCompatibleAI/overcooked_ai), [gym-cooking]( 19https://github.com/rosewang2008/gym-cooking) are the most well-known in the community. Besides, the general 20adaptations of the original overcooked game. Cooperative Cuisine, we want to bring both worlds together: the 21reinforcement learning and real-time playable environment with an appealing visualization. Enable the potential of 22developing artificial agents that play with humans like a "real", cooperative, human partner. 23 24# Installation 25 26### Local Editable Installation 27Easy with conda or mamba: 28```bash 29git clone https://gitlab.ub.uni-bielefeld.de/scs/cocosy/cooperative-cuisine.git 30conda env create -f cooperative-cuisine/conda.recipe/environment.yaml 31conda activate cocu 32``` 33 34Otherwise, you need to make sure graphviz is installed. Also, you can install it via pip directly: 35```bash 36conda install -c conda-forge pygraphviz 37git clone https://gitlab.ub.uni-bielefeld.de/scs/cocosy/cooperative-cuisine.git 38cd cooperative-cuisine 39pip install -e . 40``` 41With these installs, you can just checkout to other branches with git (and if dependencies do not change without `pip install -e .`), e.g., `git checkout dev`, and add new configs, images, etc. and change the code. 42 43 44### Library Installation 45The correct environment needs to be active: 46 47```bash 48conda install -c conda-forge pygraphviz # or find another way to install graphviz on your machine 49pip install cooperative_cuisine@git+https://gitlab.ub.uni-bielefeld.de/scs/cocosy/cooperative-cuisine@main 50``` 51You can now use the environment and/or simulator in your python code. Just by importing 52it `import cooperative_cuisine` 53 54### Agent Installation 55If you want to play with some agents, install them via 56```bash 57pip install cocu_agents@git+https://gitlab.ub.uni-bielefeld.de/scs/cocosy/cocu-agents@main 58``` 59 60 61# Usage / Examples 62 Our Cooperative Cuisine is designed for real time interaction but also for reinforcement 63learning (gymnasium environment) with time independent step function calls. It focuses on configurability, extensibility and appealing visualization 64options. 65 66## Human Player 67Run it via the command line (in your pyenv/conda environment): 68 69```bash 70cooperative-cuisine start -s localhost -sp 8080 -g localhost -gp 8000 71``` 72 73If npm is installed, a web demo using this client will automatically be served by the study server (`--web`). The PixiJS visualization client can also be easily included in external projects. For more information, see the [README.md](cooperative_cuisine/pixijs_2d_vis/README.md) in the `pixijs_2d_vis` directory. 74 75 76*The arguments shown are the defaults.* 77 78You can also start the **Game Server**, **Study Server** (Matchmaking),and the **PyGame GUI** individually in different terminals. 79 80```bash 81cooperative-cuisine game-server -g localhost -gp 8000 --manager-ids SECRETKEY1 SECRETKEY2 82 83cooperative-cuisine study-server -s localhost -sp 8080 -g localhost -gp 8000 --manager-ids SECRETKEY1 84 85cooperative-cuisine gui -s localhost -sp 8080 86``` 87 88For more CLI arguments and a description of possible arguments, run `cooperative-cuisine -h` 89 90## Connect with agent and receive game state 91Or you start a game server, create an environment and connect each player/agent via a websocket connection. 92 93To start a game server see above. Your own manager needs to create an environment. 94```python 95import requests 96 97from cooperative_cuisine import ROOT_DIR 98from cooperative_cuisine.game_server import CreateEnvironmentConfig 99from cooperative_cuisine.server_results import CreateEnvResult 100 101 102with open(ROOT_DIR / "configs" / "item_info.yaml", "r", encoding="utf-8") as file: 103 item_info = file.read() 104with open(ROOT_DIR / "configs" / "layouts" / "basic.layout", "r", encoding="utf-8") as file: 105 layout = file.read() 106with open(ROOT_DIR / "configs" / "environment_config.yaml", "r", encoding="utf-8") as file: 107 environment_config = file.read() 108 109create_env = CreateEnvironmentConfig( 110 manager_id="SECRETKEY1", 111 number_players=2, 112 environment_settings={"all_player_can_pause_game": False}, 113 item_info_config=item_info, 114 environment_config=environment_config, 115 layout_config=layout, 116 seed=123456789, 117).model_dump(mode="json") 118 119post_result = requests.post("http://localhost:8000/manage/create_env", json=create_env) 120if post_result.status_code == 403: 121 raise ValueError(f"Forbidden Request: {post_result.json()['detail']}") 122env_info: CreateEnvResult = post_result.json() 123``` 124 125Connect each player via a websocket (threaded or async). 126```python 127import json 128import dataclasses 129from websockets.sync.client import connect 130 131from cooperative_cuisine.action import Action, ActionType, InterActionData 132from cooperative_cuisine.utils import custom_asdict_factory 133 134 135p1_websocket = connect("ws://localhost:8000/ws/player/" + env_info["player_info"]["0"]["client_id"]) 136 137# set player "0" as ready 138p1_websocket.send(json.dumps({"type": "ready", "player_hash": env_info["player_info"]["0"]["player_hash"]})) 139assert json.loads(p1_websocket.recv())["status"] == 200, "not accepted player" 140 141 142# get the state for player "0", call it on every frame/step 143p1_websocket.send(json.dumps({"type": "get_state", "player_hash": env_info["player_info"]["0"]["player_hash"]})) 144state = json.loads(p1_websocket.recv()) 145 146# send an action for player "0" 147# --- movement --- 148action = Action( 149 player="0", 150 action_type=ActionType.MOVEMENT, 151 action_data=[0.0, 1.0], # direction (here straight up) 152 duration=0.5 # seconds 153) 154# --- pickup/drop off --- 155action = Action( 156 player="0", 157 action_type=ActionType.PICK_UP_DROP, 158 action_data=None, 159) 160# --- interact --- 161action = Action( 162 player="0", 163 action_type=ActionType.INTERACT, 164 action_data=InterActionData.START # InterActionData.STOP when to stop the interaction 165) 166 167p1_websocket.send(json.dumps({ 168 "type": "action", 169 "player_hash": env_info["player_info"]["0"]["player_hash"], 170 "action": dataclasses.asdict( 171 action, dict_factory=custom_asdict_factory 172 ), 173})) 174p1_websocket.recv() 175 176``` 177 178Stop the environment if you want the game to end before the time is up. 179```python 180requests.post( 181 "http://localhost:8000/manage/stop_env", 182 json={ 183 "manager_id": "SECRETKEY1", 184 "env_id": env_info["env_id"], 185 "reason": "closed environment", 186 }, 187) 188``` 189 190## Direct integration into your code 191You can use the `cooperative_cuisine.environment.Environment` class 192and call the `step`, `get_state`, and `perform_action` methods directly. 193 194```python 195import json 196from datetime import timedelta 197 198from cooperative_cuisine import ROOT_DIR 199from cooperative_cuisine.action import Action 200from cooperative_cuisine.environment import Environment 201 202env = Environment( 203 env_config=ROOT_DIR / "configs" / "environment_config.yaml", 204 layout_config=ROOT_DIR / "configs" / "layouts" / "basic.layout", 205 item_info=ROOT_DIR / "configs" / "item_info.yaml" 206) 207 208env.add_player("0") 209env.add_player("1") 210 211while True: 212 # adapt this to real time if needed with time.sleep etc. 213 env.step(timedelta(seconds=0.1)) 214 215 player_0_state = env.get_state("0") 216 if player_0_state["ended"]: 217 break 218 219 action = Action(...) # Please refer to the above code but remember to use np.array instead of list for the movement direction vector 220 env.perform_action(action) 221``` 222 223# JSON State 224The JSON schema for the state of the environment for a player can be generated by running the `state_representation.py` 225```bash 226python state_representation.py 227``` 228Should look like - to have an interactive view, have a look at [jsonschemaviewer](https://navneethg.github.io/jsonschemaviewer/): 229``` 230{"$defs": {"CookingEquipmentState": {"description": "Format of the state representation of cooking equipment.", "properties": {"id": {"title": "Id", "type": "string"}, "category": {"anyOf": [{"const": "Item"}, {"const": "ItemCookingEquipment"}], "title": "Category"}, "type": {"title": "Type", "type": "string"}, "progress_percentage": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Progress Percentage"}, "inverse_progress": {"title": "Inverse Progress", "type": "boolean"}, "active_effects": {"items": {"$ref": "#/$defs/EffectState"}, "title": "Active Effects", "type": "array"}, "content_list": {"items": {"$ref": "#/$defs/ItemState"}, "title": "Content List", "type": "array"}, "content_ready": {"anyOf": [{"$ref": "#/$defs/ItemState"}, {"type": "null"}]}}, "required": ["id", "category", "type", "progress_percentage", "inverse_progress", "active_effects", "content_list", "content_ready"], "title": "CookingEquipmentState", "type": "object"}, "CounterState": {"description": "Format of the state representation of a counter.", "properties": {"id": {"title": "Id", "type": "string"}, "category": {"const": "Counter", "title": "Category"}, "type": {"title": "Type", "type": "string"}, "pos": {"items": {"type": "number"}, "title": "Pos", "type": "array"}, "orientation": {"items": {"type": "number"}, "title": "Orientation", "type": "array"}, "occupied_by": {"anyOf": [{"items": {"anyOf": [{"$ref": "#/$defs/ItemState"}, {"$ref": "#/$defs/CookingEquipmentState"}]}, "type": "array"}, {"$ref": "#/$defs/ItemState"}, {"$ref": "#/$defs/CookingEquipmentState"}, {"type": "null"}], "title": "Occupied By"}, "active_effects": {"items": {"$ref": "#/$defs/EffectState"}, "title": "Active Effects", "type": "array"}}, "required": ["id", "category", "type", "pos", "orientation", "occupied_by", "active_effects"], "title": "CounterState", "type": "object"}, "EffectState": {"description": "Format of the state representation of an effect (fire).", "properties": {"id": {"title": "Id", "type": "string"}, "type": {"title": "Type", "type": "string"}, "progress_percentage": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Progress Percentage"}, "inverse_progress": {"title": "Inverse Progress", "type": "boolean"}}, "required": ["id", "type", "progress_percentage", "inverse_progress"], "title": "EffectState", "type": "object"}, "ItemState": {"description": "Format of the state representation of an item.", "properties": {"id": {"title": "Id", "type": "string"}, "category": {"anyOf": [{"const": "Item"}, {"const": "ItemCookingEquipment"}], "title": "Category"}, "type": {"title": "Type", "type": "string"}, "progress_percentage": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Progress Percentage"}, "inverse_progress": {"title": "Inverse Progress", "type": "boolean"}, "active_effects": {"items": {"$ref": "#/$defs/EffectState"}, "title": "Active Effects", "type": "array"}}, "required": ["id", "category", "type", "progress_percentage", "inverse_progress", "active_effects"], "title": "ItemState", "type": "object"}, "KitchenInfo": {"description": "Format of the state representation of basic information of the kitchen.", "properties": {"width": {"title": "Width", "type": "number"}, "height": {"title": "Height", "type": "number"}}, "required": ["width", "height"], "title": "KitchenInfo", "type": "object"}, "OrderState": {"description": "Format of the state representation of an order.", "properties": {"id": {"title": "Id", "type": "string"}, "category": {"const": "Order", "title": "Category"}, "meal": {"title": "Meal", "type": "string"}, "start_time": {"anyOf": [{"format": "date-time", "type": "string"}, {"type": "string"}], "title": "Start Time"}, "max_duration": {"title": "Max Duration", "type": "number"}, "score": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Score"}}, "required": ["id", "category", "meal", "start_time", "max_duration", "score"], "title": "OrderState", "type": "object"}, "PlayerState": {"description": "Format of the state representation of a player.", "properties": {"id": {"title": "Id", "type": "string"}, "pos": {"items": {"type": "number"}, "title": "Pos", "type": "array"}, "facing_direction": {"items": {"type": "number"}, "title": "Facing Direction", "type": "array"}, "holding": {"anyOf": [{"$ref": "#/$defs/ItemState"}, {"$ref": "#/$defs/CookingEquipmentState"}, {"type": "null"}], "title": "Holding"}, "current_nearest_counter_pos": {"anyOf": [{"items": {"type": "number"}, "type": "array"}, {"type": "null"}], "title": "Current Nearest Counter Pos"}, "current_nearest_counter_id": {"anyOf": [{"type": "string"}, {"type": "null"}], "title": "Current Nearest Counter Id"}}, "required": ["id", "pos", "facing_direction", "holding", "current_nearest_counter_pos", "current_nearest_counter_id"], "title": "PlayerState", "type": "object"}, "ViewRestriction": {"description": "Format of the state representation of a view restriction from the players perspectives.\nCurrently, as a view cone, like a flashlight in the dark.", "properties": {"direction": {"items": {"type": "number"}, "title": "Direction", "type": "array"}, "position": {"items": {"type": "number"}, "title": "Position", "type": "array"}, "angle": {"title": "Angle", "type": "integer"}, "counter_mask": {"anyOf": [{"items": {"type": "boolean"}, "type": "array"}, {"type": "null"}], "title": "Counter Mask"}, "range": {"anyOf": [{"type": "number"}, {"type": "null"}], "title": "Range"}}, "required": ["direction", "position", "angle", "counter_mask", "range"], "title": "ViewRestriction", "type": "object"}}, "description": "The format of the returned state representation.", "properties": {"players": {"items": {"$ref": "#/$defs/PlayerState"}, "title": "Players", "type": "array"}, "counters": {"items": {"$ref": "#/$defs/CounterState"}, "title": "Counters", "type": "array"}, "kitchen": {"$ref": "#/$defs/KitchenInfo"}, "score": {"anyOf": [{"type": "number"}, {"type": "integer"}], "title": "Score"}, "orders": {"items": {"$ref": "#/$defs/OrderState"}, "title": "Orders", "type": "array"}, "ended": {"title": "Ended", "type": "boolean"}, "env_time": {"format": "date-time", "title": "Env Time", "type": "string"}, "remaining_time": {"title": "Remaining Time", "type": "number"}, "view_restrictions": {"anyOf": [{"items": {"$ref": "#/$defs/ViewRestriction"}, "type": "array"}, {"type": "null"}], "title": "View Restrictions"}, "served_meals": {"items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"type": "string"}, {"type": "string"}], "type": "array"}, "title": "Served Meals", "type": "array"}, "info_msg": {"items": {"maxItems": 2, "minItems": 2, "prefixItems": [{"type": "string"}, {"type": "string"}], "type": "array"}, "title": "Info Msg", "type": "array"}}, "required": ["players", "counters", "kitchen", "score", "orders", "ended", "env_time", "remaining_time", "view_restrictions", "served_meals", "info_msg"], "title": "StateRepresentation", "type": "object"} 231``` 232 233The BaseModel and TypedDicts can be found in `cooperative_cuisine.state_representation`. The 234`cooperative_cuisine.state_representation.StateRepresentation` represents the json state that the `get_state` 235returns. 236 237## Generate images from JSON states 238You might have stored some json states and now you want to visualize them via the 239pygame-2d visualization. You can do that by running the `drawing.py` script and referencing a json file. 240```bash 241cooperative-cuisine screenshot --state my_state.json 242``` 243- You can specify a different visualization config with `-v` or `--visualization_config`. 244- You can specify the name of the output file with `-o` or `--output_file`. The default is `screenshot.jpg`. 245 246## Generate images/videos from recordings 247You can record json states or only the actions and the environment config via hooks and the recording class. 248 249If you want to generate images or complete videos, see `cooperative_cuisine.pygame_2d_vis.video_replay`. 250 251# Configuration 252 253The environment configuration is currently done with 3 config files + GUI configuration. 254 255## Item Config 256 257The item config defines which ingredients, cooking equipment and meals can exist and how meals and processed ingredients 258can be cooked/created. 259 260For example 261 262 CuttingBoard: 263 type: Equipment 264 265 Stove: 266 type: Equipment 267 268 Pot: 269 type: Equipment 270 equipment: Stove 271 272 Tomato: 273 type: Ingredient 274 275 ChoppedTomato: 276 type: Ingredient 277 needs: [ Tomato ] 278 seconds: 4.0 279 equipment: CuttingBoard 280 281 TomatoSoup: 282 type: Meal 283 needs: [ ChoppedTomato, ChoppedTomato, ChoppedTomato ] 284 seconds: 6.0 285 equipment: Pot 286 287 288## Layout Config 289 290You can define the layout of the kitchen via a layout file. The position of counters are based on a grid system, even 291when the players do not move grid steps but continuous steps. Each character defines a different type of counter. Which 292character is mapped to which counter is defined in the Environment config. 293 294For example 295 296``` 297#QU#FO#TNLB# 298@__________M 299|__________K 300$__________I 301#__A____A__D 302C__________E 303#__________G 304C__________# 305##PS+#X##S+# 306``` 307 308## Environment Config 309 310The environment config details how a level/environment is defined. Here, the available plates, meals, order and player 311configuration is done. 312 313For example 314 315```yaml 316plates: 317 clean_plates: 1 318 dirty_plates: 2 319 plate_delay: [ 5, 10 ] 320 # range of seconds until the dirty plate arrives. 321 322game: 323 time_limit_seconds: 300 324 undo_dispenser_pickup: true 325 validate_recipes: true 326 327layout_chars: 328 _: Free 329 hash: Counter 330 A: Agent 331 P: PlateDispenser 332 C: CuttingBoard 333 X: Trashcan 334 W: ServingWindow 335 S: Sink 336 +: SinkAddon 337 U: Pot # with Stove 338 T: Tomato 339 340orders: # how to create orders 341 meals: 342 all: true 343 ... 344 345player_config: 346 radius: 0.4 347 speed_units_per_seconds: 6 348 interaction_range: 1.6 349 restricted_view: False 350 view_angle: 70 351 view_range: 4 # in grid units, can be "null" 352 353effect_manager: # fire effect 354 ... 355 356hook_callbacks: # scores, recording, msgs, etc. 357 ... 358``` 359 360## PyGame Visualization Config 361 362Here the visualisation for all objects is defined. Reference the images or define a list of base shapes that represent 363the counters, ingredients, meals and players. Cooperative Cuisine comes with images for the defined meals. 364You can extend it easily. Just have a look at the default [`visualisation.yml`](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/cooperative-cuisine/-/blob/main/cooperative_cuisine/pygame_2d_vis/visualization.yaml?ref_type=heads). 365Further, here, you can configure the size of the visualization like screen width, button sizes, fps, colors, etc. 366 367You can change the localisation (language) of the visualization by changing the `language` key in the visualization 368config, we provide "en" and "de" translations. You can add own languages by adding i18 translation files to the 369`pygame_2d_vis/locals` folder and `continue_{lang}.png`, `controls_{lang}.png`, `try_{lang}.png` images in the `pygame_2d_vis/gui_images` folder (we provide a `tutorial.drawio` template file. 370You can edit it online at [draw.io](https://app.diagrams.net/)). 371 372## Study Config 373 374You can setup a study with a study config. 375It defines which levels the player will play after they connect to the study server. 376Further, you define how many players play together within an environment. 377 378```yaml 379levels: 380 - config_path: STUDY_DIR/level1/level1_config.yaml 381 layout_path: LAYOUTS_DIR/overcooked-1/1-1-far-apart.layout 382 item_info_path: STUDY_DIR/level1/level1_item_info.yaml 383 name: "Level 1-1: Far Apart" 384 385 - config_path: CONFIGS_DIR/environment_config.yaml 386 layout_path: LAYOUTS_DIR/basic.layout 387 item_info_path: CONFIGS_DIR/item_info.yaml 388 name: "Basic" 389 390 - config_path: STUDY_DIR/level2/level2_config.yaml 391 layout_path: LAYOUTS_DIR/overcooked-1/1-4-bottleneck.layout 392 item_info_path: STUDY_DIR/level2/level2_item_info.yaml 393 name: "Level 1-4: Bottleneck" 394 395num_players: 1 396num_bots: 0 397``` 398`STUDY_DIR`, `LAYOUTS_DIR`, `CONFIG_DIR`, etc. will be replaced accordingly to their names. 399 400# Citation 401 402# Structure of the Documentation 403The API documentation follows the file and content structure in the repo. 404On the left you can find the navigation panel that brings you to the implementation of 405- the **action**s the player can perform, 406- the **argument parser** definitions for the cli, 407- the **base agent** class, for abstracting some agent logic (e.g. path planning), 408- the **counter factory** converts the characters in the layout file to counter instances, 409- the **counters**, including the kitchen utility objects like dispenser, cooking counter (stove, deep fryer, oven), 410 sink, etc., 411- the **effect manager**, how the fire effect is defined, 412- the **environment**, handles the incoming actions and provides the state, 413- the **game server**, which can manage several running environments and can communicates via FastAPI post requests and 414websockets, 415- the **hooks**, you can adapt and extend recording, scoring, msgs to players, etc. via hooks, 416- the **hook addons** to extend the environment for custom features, 417- the **info msgs** to players, based on hook events, short text msgs can be generated, 418- the game **items**, the holdable ingredients, cooking equipment, composed ingredients, and meals, 419- the calculation of player **movement**, 420- the **orders**, how to sample incoming orders and their attributes, 421- the **player**/agent, that interacts in the environment, 422- the **pygame 2d visualization**, GUI, drawing, and video generation, 423- the **pixijs 2d visualization** for web apps, 424- the **recording**, via hooks, actions, environment configs, states, etc. can be recorded in files, 425- the **scores**, via hooks, events can affect the scores, 426- type hints are defined in **state representation** for the json state and **server results** for the data returned by 427the game server in post requests. 428- the **study server**, the match making server, 429- **util**ity code, 430- the config **validation** and graph generation, 431- the interface for different **voice chat** APIs (currently custom build CoCuVc und Mumble). 432 433# License 434Cooperative Cuisine © 2025 by [Social Cognitive Systems Group](https://scs.techfak.uni-bielefeld.de/) is licensed under [`CC BY-NC-SA 4.0`](https://creativecommons.org/licenses/by-nc-sa/4.0/?ref=chooser-v1) [](https://creativecommons.org/licenses/by-nc-sa/4.0/) 435""" 436import os 437from pathlib import Path 438 439 440ROOT_DIR = Path(os.path.dirname(os.path.abspath(__file__))) # This is your Project Root 441"""A path variable to get access to the layouts coming with the package. For example, 442```python 443from cooperative_cuisine import ROOT_DIR 444 445environment_config_path = ROOT_DIR / "configs" / "environment_config.yaml" 446``` 447"""
A path variable to get access to the layouts coming with the package. For example,
from cooperative_cuisine import ROOT_DIR
environment_config_path = ROOT_DIR / "configs" / "environment_config.yaml"