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
Select Git revision
  • 103-sounds
  • 119-docs-new-perspective
  • 124-additional-state-keys-and-values
  • 144-gkk-exercises
  • 157-show-content-progress
  • 159-reduce-gif-size
  • 160-baseagent-benchmarks
  • ba-lborn-dreamer_fun_rl
  • ba-lekoester-llm_agent_planning
  • ba-tfreitag-3d_gui_voicechat
  • dev
  • ffd-demo-arcade
  • llm-agent
  • ma-cklute-emoji_communication
  • main
  • markdown-center-badge-html-demo
  • rl-bachelor-thesis
  • show-number-content
  • voicechat
  • web_version
  • ffday
  • v1.0.0
  • v1.1.0
  • v1.1.3
  • v1.2.0
  • v1.3.0
  • v1.3.1
27 results

Target

Select target project
  • tkroecker/cooperative-cuisine
  • scs/cocosy/cooperative-cuisine
2 results
Select Git revision
  • 103-sounds
  • 119-docs-new-perspective
  • 124-additional-state-keys-and-values
  • 144-gkk-exercises
  • 157-show-content-progress
  • 159-reduce-gif-size
  • 160-baseagent-benchmarks
  • ba-lborn-dreamer_fun_rl
  • ba-lekoester-llm_agent_planning
  • ba-tfreitag-3d_gui_voicechat
  • dev
  • ffd-demo-arcade
  • llm-agent
  • ma-cklute-emoji_communication
  • main
  • markdown-center-badge-html-demo
  • rl-bachelor-thesis
  • show-number-content
  • voicechat
  • web_version
  • ffday
  • v1.0.0
  • v1.1.0
  • v1.1.3
  • v1.2.0
  • v1.3.0
  • v1.3.1
27 results
Show changes
Commits on Source (11)
  • Florian Schröder's avatar
    Update argument names in function calls · 794a4414
    Florian Schröder authored
    The argument names in the main function calls within study_server.py and game_server.py were updated for clarity. These changes better reflect their function within these scripts, making the code easier to understand and maintain. The modifications primarily involve changing 'url' and 'port' to 'game_url' and 'game_port', and 'port' to 'study_port'.
    794a4414
  • Florian Schröder's avatar
    Add missing comma in game_port argument · dee75f4c
    Florian Schröder authored
    A missing comma was discovered and corrected in the game_port argument on the study_server.py file. This could potentially prevent syntax errors and bugs that might result from incomplete statements and ensure smoother execution of the function.
    dee75f4c
  • Florian Schröder's avatar
    Add favicon to pdoc command in GitLab CI configuration · 76a69a2a
    Florian Schröder authored
    Added a favicon parameter to the pdoc command in the .gitlab-ci.yml file. This results in the favicon being specified during the documentation generation process. This change should improve the importance of the favicon when the documentation is viewed in a browser tab.
    76a69a2a
  • Florian Schröder's avatar
    Update CI config and improve code readability · 09a0bc5c
    Florian Schröder authored
    The CI configuration file and several Python scripts were updated for better adherence to best practices. The CI now includes a footer in the pdoc document generation step. Moreover, variable names and import paths were updated to be more explicit and clear. Some paths in the scripts were also generalized. In addition, the json.dumps function was used to print the create_json_schema function's output in a prettier format. Lastly, minor modifications were made to the README and comments for better clarity and understanding.
    09a0bc5c
  • Florian Schröder's avatar
    Update .gitlab-ci.yml and expand CHANGELOG.md · 593f63c1
    Florian Schröder authored
    Updated the pdoc command in .gitlab-ci.yml file to correct the hyphen usage in the "footer_text" flag. The CHANGELOG.md is significantly expanded to provide a more detailed and organized account of changes with version releases, following the guidelines from keepachangelog.com.
    593f63c1
  • Florian Schröder's avatar
    Update documentation and improve changelog · eb4ae7eb
    Florian Schröder authored
    The provided modifications involve an update to the .gitlab-ci.yml file to include a footer text in the project documentation. There's also an additional point added to the CHANGELOG.md file under the 'study participants' section for clarity. Furthermore, the README.md has been slightly restructured to explicitly mention the availability of extensive project details in the documentation.
    eb4ae7eb
  • Florian Schröder's avatar
    Update CI script and game configurations · f536d40e
    Florian Schröder authored
    Changed the pdoc execution command in the .gitlab-ci.yml to fix the link in the footer text. Adjustments were also made to the layout of the game in the basic.layout and __init__.py files, along with the game, player, and effect configurations. The player speed was reduced, the ability to undo dispenser pickup was added, and the effect
    f536d40e
  • Florian Schröder's avatar
    Update code comments and typespecs across modules · bef24476
    Florian Schröder authored
    Enhanced overall code clarity by adding and revising detailed comments, docstrings, and typespecs in various Python modules. Significant changes include refining functionality of effect management in 'effects.py', modifications to use of 'numpy' arrays and functions in 'movement.py', as well as adjustments to handling hooks. Additionally, minor updates in 'configs/study/level1/level1_config.yaml' and 'counter_factory.py' were made.
    bef24476
  • Florian Schröder's avatar
    Update .gitignore, .gitlab-ci.yml, player.py · a96a9cbb
    Florian Schröder authored
    Added 'generated' to .gitignore, updated pdoc footer text in .gitlab-ci.yml, and included interaction state for player in player.py. The player interaction with the counter is now being tracked and a new comment was added for the progress method. This will enhance player's tracking and usage in the game environment.
    a96a9cbb
  • Florian Schröder's avatar
    Add docstrings and minor adjustments to cooperative_cuisine · 5a9b4874
    Florian Schröder authored
    This commit adds detailed docstrings for various methods and classes in the 'cooperative_cuisine' module for enhanced code understanding. Also includes minor changes to the .gitlab-ci.yml and pygame_2d_vis/__init__.py files and adds an image in the docs markdown file. Code readability and maintainability improved with these changes.
    5a9b4874
  • Florian Schröder's avatar
    Refactor comments and organize default server settings · e8585344
    Florian Schröder authored
    The comments in various class definitions and methods across the code were reorganized to provide better clarity and readability. The handling of server default values has been centralized in utils.py for cleaner code and easier modifications in the future.
    e8585344
Showing
with 404 additions and 71 deletions
......@@ -2,6 +2,7 @@
# Edit at https://www.toptal.com/developers/gitignore?templates=python,intellij,visualstudiocode,pycharm,git,flask,django,docusaurus,ros,ros2,linux,macos,windows
playground
generated
### Django ###
*.log
......
......@@ -17,8 +17,8 @@ pages:
- apt-get update -qy
- apt-get install -y python3-dev python3-pip graphviz graphviz-dev
- pip install pdoc
- pip install ".[rl]"
- pdoc --output-dir public cooperative_cuisine !cooperative_cuisine.reinforcement_learning --logo https://gitlab.ub.uni-bielefeld.de/uploads/-/system/project/avatar/6780/Cooking-Vector-Illustration-Icon-Graphics-4267218-1-580x435.jpg --docformat google
- pip install .
- pdoc --output-dir public cooperative_cuisine !cooperative_cuisine.reinforcement_learning --logo https://gitlab.ub.uni-bielefeld.de/uploads/-/system/project/avatar/6780/Cooking-Vector-Illustration-Icon-Graphics-4267218-1-580x435.jpg --docformat google --favicon overcooked-simulator/cooperative_cuisine/pygame_2d_vis/images/favicon.ico --footer-text "Developed@SCS"
artifacts:
paths:
- public
......
# Release Notes
[How to keep the changelog](https://keepachangelog.com/en/1.1.0/):
- `Added` for new features.
- `Changed` for changes in existing functionality.
- `Deprecated` for soon-to-be removed features.
- `Removed` for now removed features.
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
## [1.0.0] (2024-03-XX)
Release of v1. Now change the development process to an incremental updates, use of the changelog and semantic
versioning.
_Cooperative Cuisine_ **v1** is feature complete. It contains a usable _Overcooked!_-like environment, game server,
study server, 2D visualization, etc.
### Added
- continuous-like movement and pushing other players around
- extending recording, scoring, msgs via hooks at different programm points
- implemented random-action agent
- players connect via websockets
- 2D visualization with pygame
- Everything is configurable with config files: item info (ingredients, meals, etc.), layout, environment,
visualization, study
- Game server that handles active environments
- Study environment with study server and study config. Does the match making
- For study participants: simple control tutorial, recipe guides, end screen.
- Controller support
### Security
- To manage environments on the game server, the "manager" needs to have a valid `manager_id` which is registered in the
game server.
- Registered players (humans and agents) need to connect to a websockets that are preregistered with a `client_id`.
- Each player actions need to be references with a `player_hash` that should only be known by the player / client that
controls the player.
## 0.1.0 (2023-11-23)
- Created
\ No newline at end of file
......@@ -7,9 +7,11 @@
# Cooperative Cuisine Environment
[Documentation](https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator)
The overcooked-like cooperative cuisine environment for real-time human cooperative interactions and artificial agents.
For an extensive introduction, have a look at
the [Documentation](https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator).
<div align="center">
<img src="cooperative_cuisine/pygame_2d_vis/images/cooperative_cuisine.png" width="800">
</div>
......
......@@ -46,7 +46,7 @@ cooperative_cuisine -s localhost -sp 8080 -g localhost -gp 8000
*The arguments shown are the defaults.*
You can also start the **Game Server**m **Study Server** (Matchmaking),and the **PyGame GUI** individually in different terminals.
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
......@@ -82,21 +82,22 @@ create_env = CreateEnvironmentConfig(
item_info_config=item_info,
environment_config=environment_config,
layout_config=layout,
seed=123456789,
).model_dump(mode="json")
env_info = requests.post("http://localhost:8000/manage/create_env", json=create_env)
if env_info.status_code == 403:
raise ValueError(f"Forbidden Request: {env_info.json()['detail']}")
env_info: CreateEnvResult = env_info.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).
```python
import json
import dataclasses
from websockets import connect
from websockets.sync.client import connect
from cooperative_cuisine.environment import Action, ActionType, InterActionData
from cooperative_cuisine.action import Action, ActionType, InterActionData
from cooperative_cuisine.utils import custom_asdict_factory
......@@ -104,12 +105,12 @@ p1_websocket = connect("ws://localhost:8000/ws/player/" + env_info["player_info"
# set player "0" as ready
p1_websocket.send(json.dumps({"type": "ready", "player_hash": env_info["player_info"]["0"]["player_hash"]}))
assert json.loads(websocket.recv())["status"] == 200, "not accepted player"
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(websocket.recv())
state = json.loads(p1_websocket.recv())
# send an action for player "0"
# --- movement ---
......@@ -139,7 +140,7 @@ p1_websocket.send(json.dumps({
action, dict_factory=custom_asdict_factory
),
}))
websocket.recv()
p1_websocket.recv()
```
......@@ -160,10 +161,12 @@ You can use the `cooperative_cuisine.environment.Environment` class
and call the `step`, `get_json_state`, and `perform_action` methods directly.
```python
import json
from datetime import timedelta
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.environment import Action, Environment
from cooperative_cuisine.action import Action
from cooperative_cuisine.environment import Environment
env = Environment(
env_config=ROOT_DIR / "configs" / "environment_config.yaml",
......@@ -182,7 +185,7 @@ while True:
if player_0_state["ended"]:
break
action = ... # Please refer to the above code but remember to use np.array instead of list for the movement direction vector
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)
```
......@@ -191,11 +194,12 @@ The JSON schema for the state of the environment for a player can be generated b
```bash
python state_representation.py
```
Should look like
Should look like - to have an interactive view, have a look at [jsonschemaviewer](https://navneethg.github.io/jsonschemaviewer/):
```
{'$defs': {'CookingEquipmentState': {'properties': {'content_list': {'items': {'$ref': '#/$defs/ItemState'}, 'title': 'Content List', 'type': 'array'}, 'content_ready': {'anyOf': [{'$ref': '#/$defs/ItemState'}, {'type': 'null'}]}}, 'required': ['content_list', 'content_ready'], 'title': 'CookingEquipmentState', 'type': 'object'}, 'CounterState': {'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': {'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': {'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': 'Basic information of the kitchen.', 'properties': {'width': {'title': 'Width', 'type': 'number'}, 'height': {'title': 'Height', 'type': 'number'}}, 'required': ['width', 'height'], 'title': 'KitchenInfo', 'type': 'object'}, 'OrderState': {'properties': {'id': {'title': 'Id', 'type': 'string'}, 'category': {'const': 'Order', 'title': 'Category'}, 'meal': {'title': 'Meal', 'type': 'string'}, 'start_time': {'format': 'date-time', 'title': 'Start Time', 'type': 'string'}, 'max_duration': {'title': 'Max Duration', 'type': 'number'}}, 'required': ['id', 'category', 'meal', 'start_time', 'max_duration'], 'title': 'OrderState', 'type': 'object'}, 'PlayerState': {'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': {'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'}, 'all_players_ready': {'title': 'All Players Ready', 'type': 'boolean'}, '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', 'all_players_ready', '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_json_state`
returns.
......@@ -261,14 +265,14 @@ For example
```
#QU#FO#TNLB#
#__________M
#__________K
W__________I
#__A_____A_D
@__________M
|__________K
$__________I
#__A____A__D
C__________E
C__________G
#__________#
#P#S+#X##S+#
#__________G
C__________#
##PS+#X##S+#
```
## Environment Config
......@@ -287,6 +291,7 @@ plates:
game:
time_limit_seconds: 300
undo_dispenser_pickup: true
meals:
all: true
......@@ -304,52 +309,58 @@ layout_chars:
U: Pot # with Stove
T: Tomato
orders:
orders: # how to create orders
...
player_config:
radius: 0.4
speed_units_per_seconds: 8
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
...
extra_setup_functions: # 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.
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`](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/-/blob/main/cooperative_cuisine/pygame_2d_vis/visualization.yaml?ref_type=heads).
Further, here, you can configure the size of the visualization like screen width, button sizes, fps, colors, etc.
## 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 on environment.
Further, you define how many players play together within an environment.
An example study config is:
```yaml
# Config paths are relative to configs folder.
# Layout files are relative to layouts folder.
levels:
- config_path: study/level1/level1_config.yaml
layout_path: overcooked-1/1-1-far-apart.layout
item_info_path: study/level1/level1_item_info.yaml
- 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: environment_config.yaml
layout_path: basic.layout
item_info_path: item_info.yaml
- 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/level2/level2_config.yaml
layout_path: overcooked-1/1-4-bottleneck.layout
item_info_path: study/level2/level2_item_info.yaml
- 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
......
......@@ -2,7 +2,7 @@
@__________M
|__________K
$__________I
#__A_____A_D
#__A____A__D
C__________E
#__________G
C__________#
......
......@@ -14,7 +14,8 @@ meals:
# if all: false -> only orders for these meals are generated
# TODO: what if this list is empty?
list:
- Burger
- TomatoSoup
- Salad
layout_chars:
_: Free
......
......@@ -371,7 +371,6 @@ class CounterFactory:
random=self.random,
**self.effect_manager_config[effect.manager]["kwargs"],
)
manager.set_counters(counters)
effect_manager[effect.manager] = manager
manager.add_effect(effect)
......
......@@ -30,32 +30,87 @@ if TYPE_CHECKING:
class EffectManager:
"""The EffectManager class is responsible for managing effects in an environment.
It provides methods to add effects, set counters, register new active effects, check if an effect is active,
remove active effects, and progress the manager.
"""
def __init__(self, hook: Hooks, random: Random) -> None:
self.effects = []
"""A list of ItemInfo objects representing the effects managed by the manager."""
self.counters = []
"""A list of Counter objects representing the counters in the environment."""
self.hook = hook
"""An instance of the Hooks class representing the hooks in the environment."""
self.new_effects: list[Tuple[Effect, Item | Counter]] = []
"""A list of tuples containing an Effect object and either an Item or Counter object representing the new
active effects."""
self.random = random
"""An instance of the Random class representing the random number generator."""
def add_effect(self, effect: ItemInfo):
"""Add the effect (item) info that the manager handles.
Args:
effect: An object of type ItemInfo representing the effect to be added.
"""
self.effects.append(effect)
def set_counters(self, counters: list[Counter]):
self.counters.extend(counters)
"""Sets the counters of the environment.
Args:
counters: A list of Counter objects representing the counters.
"""
self.counters = counters.copy()
def register_active_effect(self, effect: Effect, target: Item | Counter):
"""Register new active effects on a target item or counter.
Args:
effect: An instance of the Effect class representing the active effect to be registered.
target: An instance of either the Item or Counter class to which the effect will be applied.
"""
target.active_effects.append(effect)
self.new_effects.append((effect, target))
def progress(self, passed_time: timedelta, now: datetime):
"""Iterative progress call on the manager.
Similar to the `Counter.progress` method.
How often it is called depends on the `env_step_frequency` or how often `step` is called.
Args:
passed_time: The amount of time that has passed since the last call of the method.
now: The current env time.
"""
...
def can_start_effect_transition(
self, effect: ItemInfo, target: Item | Counter
) -> bool:
return effect.name not in [e.name for e in target.active_effects]
@staticmethod
def effect_is_active(effect: ItemInfo, target: Item | Counter) -> bool:
"""Checks if a given effect is active on a specified target.
Args:
effect: An instance of ItemInfo representing the effect to check.
target: An instance of Item or Counter representing the target on which the effect is being checked.
Returns:
A boolean value indicating whether the effect is active on the target. True if active, False otherwise.
"""
return effect.name in [e.name for e in target.active_effects]
def remove_active_effect(self, effect: Effect, target: Item | Counter):
"""Removes an active effect from a target item or counter.
Args:
effect (Effect): The effect to be removed.
target (Item | Counter): The target item or counter from which the effect will be removed.
"""
...
......@@ -89,7 +144,9 @@ class FireEffectManager(EffectManager):
"""
if self.new_effects:
# new effects since the last progress call
for effect, target in self.new_effects:
# update next fire spreading
self.effect_to_timer[effect.uuid] = now + timedelta(
seconds=self.random.uniform(*self.spreading_duration)
)
......@@ -98,6 +155,7 @@ class FireEffectManager(EffectManager):
)
self.hook(NEW_FIRE, target=target)
self.active_effects.append((effect, target))
# reset new effects
self.new_effects = []
if self.next_finished_timer < now:
for effect, target in self.active_effects:
......@@ -109,6 +167,7 @@ class FireEffectManager(EffectManager):
for counter in touching:
if counter.occupied_by:
if isinstance(counter.occupied_by, deque):
# only the top object on a plate stack is burning
self.apply_effect(effect, counter.occupied_by[-1])
else:
self.apply_effect(effect, counter.occupied_by)
......
"""
The _Cooperative Cuisine_ environment. It is configured via three configs:
- [`environment_config.yml`](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/-/blob/main/cooperative_cuisine/configs/environment_config.yaml?ref_type=heads)
- [`xyz.layout`](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/-/blob/main/cooperative_cuisine/configs/layouts/basic.layout?ref_type=heads)
- [`item_info.yml`](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/-/blob/main/cooperative_cuisine/configs/item_info.yaml?ref_type=heads)
You can pass either the file path or the file content (str) to the `Environment`class of the config files. Set the `as_files` parameter accordingly.
"""
from __future__ import annotations
import inspect
......@@ -60,9 +69,7 @@ from cooperative_cuisine.utils import (
from cooperative_cuisine.validation import Validation
log = logging.getLogger(__name__)
PREVENT_SQUEEZING_INTO_OTHER_PLAYERS = True
"""The logger for this module."""
class EnvironmentConfig(TypedDict):
......@@ -214,6 +221,11 @@ class Environment:
self.progressing_counters = []
"""Counters that needs to be called in the step function via the `progress` method."""
self.effect_manager: dict[
str, EffectManager
] = self.counter_factory.setup_effect_manger(self.counters)
self.overwrite_counters(self.counters)
self.recipe_validation = Validation(
......@@ -242,10 +254,6 @@ class Environment:
"""The relative env time when it will stop/end"""
log.debug(f"End time: {self.env_time_end}")
self.effect_manager: dict[
str, EffectManager
] = self.counter_factory.setup_effect_manger(self.counters)
self.info_msgs_per_player: dict[str, list[InfoMsg]] = defaultdict(list)
self.hook(
......@@ -276,6 +284,8 @@ class Environment:
self.counters,
)
)
for manager in self.effect_manager.values():
manager.set_counters(counters)
@property
def game_ended(self) -> bool:
......
......@@ -43,11 +43,15 @@ from cooperative_cuisine.utils import (
)
log = logging.getLogger(__name__)
"""The logger for this module."""
app = FastAPI()
"""The FastAPI app that runs the game server."""
TIME_AFTER_STOP_TO_DEL_ENV = 30
"""Time after stopping an environment how long it takes to delete the env data from the game server. In seconds."""
@dataclasses.dataclass
......@@ -792,7 +796,7 @@ if __name__ == "__main__":
disable_websocket_logging_arguments(parser)
add_list_of_manager_ids_arguments(parser)
args = parser.parse_args()
main(args.url, args.port, args.manager_ids, args.enable_websocket_logging)
main(args.game_url, args.game_port, args.manager_ids, args.enable_websocket_logging)
"""
Or in console:
uvicorn cooperative_cuisine.fastapi_game_server:app --reload
......
......@@ -21,7 +21,6 @@ from typing import Callable, Any, TYPE_CHECKING, Type
if TYPE_CHECKING:
from cooperative_cuisine.environment import Environment
# TODO add player_id as kwarg to all hooks -> pass player id to all methods
ITEM_INFO_LOADED = "item_info_load"
"""Called after the item info is loaded and stored in the env attribute `item_info`. The kwargs are the passed config
......@@ -128,6 +127,30 @@ DROP_OFF_ON_COOKING_EQUIPMENT = "drop_off_on_cooking_equipment"
class Hooks:
"""
Class Hooks
Represents a collection of hooks and provides methods to register callbacks for hooks and invoke the callbacks when hooks are triggered.
Attributes:
hooks (defaultdict[list]): A defaultdict containing lists of callbacks for each hook reference.
env (any): The environment variable passed to the Hooks instance.
Methods:
__init__(self, env)
Initializes a new instance of Hooks.
Args:
env (any): The environment variable to be stored in the env attribute.
__call__(self, hook_ref, **kwargs)
Invokes the callbacks associated with the specified hook reference.
Args:
hook_ref (str): The hook reference to trigger the callbacks for.
**kwargs: Additional keyword arguments to be passed to the callbacks.
"""
def __init__(self, env):
self.hooks = defaultdict(list)
self.env = env
......@@ -137,6 +160,12 @@ class Hooks:
callback(hook_ref=hook_ref, env=self.env, **kwargs)
def register_callback(self, hook_ref: str | list[str], callback: Callable):
"""Register a callback for a hook which is called when the hook is touched during execution.
Args:
hook_ref: A string or a list of strings representing the reference(s) of the hook(s) to register the callback for.
callback: A callable object (function or method) to be registered as a callback.
"""
if isinstance(hook_ref, (tuple, list, set)): # TODO check for iterable
for ref in hook_ref:
self.hooks[ref].append(callback)
......@@ -149,9 +178,49 @@ def print_hook_callback(text, env, **kwargs):
class HookCallbackClass:
"""
Class: HookCallbackClass
Represents a callback class for hook events.
Attributes:
- name: A string representing the name of the callback.
- env: An Environment object representing the environment in which the callback is being executed.
Methods:
- __init__(self, name: str, env: Environment, **kwargs):
Initializes a new instance of HookCallbackClass.
- __call__(self, hook_ref: str, env: Environment, **kwargs):
Abstract method that executes the callback logic when called.
Note:
- This class is meant to be subclassed and the __call__ method implemented according to specific needs.
- The **kwargs parameter allows for additional arguments to be passed to the callback function.
Usage Example:
```python
# Create an instance of HookCallbackClass
callback = HookCallbackClass("my_callback", my_env)
# Subclass HookCallbackClass and implement the __call__ method
class MyCallback(HookCallbackClass):
def __call__(self, hook_ref: str, env: Environment, **kwargs):
# Add custom callback logic here
# Create an instance of the subclass
my_callback = MyCallback("my_callback", my_env)
# Call the callback
my_callback("hook_reference", my_env)
```
"""
def __init__(self, name: str, env: Environment, **kwargs):
self.name = name
"""The name of the callback."""
self.env = env
"""Reference to the environment."""
@abstractmethod
def __call__(self, hook_ref: str, env: Environment, **kwargs):
......@@ -165,12 +234,40 @@ def hooks_via_callback_class(
callback_class: Type[HookCallbackClass],
callback_class_kwargs: dict[str, Any],
):
"""Function to reference in the `environment_config.yml` to add functionality via hooks and a configured callback class.
Args:
name: A string representing the name of the callback class instance.
env: An instance of the Environment class.
hooks: A list of strings representing the hooks for which the callback class instance needs to be registered.
callback_class: A type representing the class of the callback instance to be created.
callback_class_kwargs: A dictionary containing additional keyword arguments to be passed to the callback class constructor.
"""
recorder = callback_class(name=name, env=env, **callback_class_kwargs)
for hook in hooks:
env.register_callback_for_hook(hook, recorder)
def add_dummy_callbacks(env):
"""Checking the hooks-callback functionality.
Args:
env: The environment object that represents the system environment.
This method adds dummy callbacks to the given environment object. Each callback is registered for a specific hook using the `register_callback_for_hook` method of the environment.
The callbacks are defined using the `partial` function from the `functools` module. This allows us to pass additional arguments to the callback while registering it. The `print_hook
*_callback` function is used as the callback function, and it prints a message to the console.
Here are the hooks and corresponding messages that are registered:
1. SERVE_NOT_ORDERED_MEAL: Prints the message "You tried to serve a meal that was not ordered!"
2. SINK_START_INTERACT: Prints the message "You started to use the Sink!"
3. COMPLETED_ORDER: Prints the message "You completed an order!"
4. TRASHCAN_USAGE: Prints the message "You used the trashcan!"
These dummy callbacks can be used for testing or demonstration purposes.
"""
env.register_callback_for_hook(
SERVE_NOT_ORDERED_MEAL,
partial(
......
......@@ -28,6 +28,23 @@ from cooperative_cuisine.hooks import HookCallbackClass
class InfoMsgManager(HookCallbackClass):
"""
Class for managing info messages in an environment.
This class inherits from the `HookCallbackClass` class.
Attributes:
msg (str): The message to be displayed.
duration (datetime.timedelta): The duration for which the message should be displayed.
level (str): The level of the message.
Methods:
__init__(name, env, msg, duration, level, **kwargs): Initializes an instance of InfoMsgManager.
__call__(hook_ref, env, **kwargs): Adds the message to the info messages list for each player in the environment.
remove_old_msgs(env): Removes old messages from the environment.
"""
def __init__(
self,
name: str,
......@@ -56,6 +73,12 @@ class InfoMsgManager(HookCallbackClass):
@staticmethod
def remove_old_msgs(env: Environment):
"""
Removes old messages from the environment.
Args:
env (Environment): The environment object containing the messages.
"""
for player_id, msgs in env.info_msgs_per_player.items():
delete_msgs = []
for idx, msg in enumerate(msgs):
......
......@@ -116,7 +116,9 @@ class ItemInfo:
def __post_init__(self):
if self.seconds < 0.0:
raise ValueError(f"Expected seconds >= 0 for item '{self.name}', but got {self.seconds} in item info")
raise ValueError(
f"Expected seconds >= 0 for item '{self.name}', but got {self.seconds} in item info"
)
self.type = ItemType(self.type)
if self.effect_type:
self.effect_type = EffectType(self.effect_type)
......@@ -302,7 +304,7 @@ class CookingEquipment(Item):
if transition.type == ItemType.Effect:
if set(ingredients.keys()).issubset(
transition.needs
) and transition.manager.can_start_effect_transition(transition, self):
) and not transition.manager.effect_is_active(transition, self):
if transition.seconds == 0:
transition.manager.register_active_effect(
Effect(name=transition.name, item_info=transition), self
......
"""
Implements the moving of the players. Checks collisions and pushing other around.
For efficiency, we tried to do everything with numpy arrays and functions.
"""
from datetime import timedelta, datetime
from typing import Tuple
import numpy as np
import numpy.typing as npt
from scipy.spatial import distance_matrix
from cooperative_cuisine.counters import Counter
......@@ -8,18 +15,33 @@ from cooperative_cuisine.player import Player
class Movement:
"""Does the movement of the players."""
world_borders_lower = None
"""World borders lower bounds."""
world_borders_upper = None
"""World borders upper bounds."""
def __init__(self, counter_positions, player_config, world_borders):
self.counter_positions = counter_positions
"""Positions of all counters in an environment. Needs to be updated if the counters position changes."""
self.player_radius = player_config["radius"]
"""The radius of a player (indicating its size and collision sphere). Relative to one grid cell, e.g., `0.4`."""
self.player_interaction_range = player_config["interaction_range"]
"""The range of how far a player can interact with the closest counter."""
self.player_movement_speed = player_config["speed_units_per_seconds"]
"""How many grid cells a player can move in a second."""
self.world_borders = world_borders
"""The world border arrays. For easier numpy comparison with player position. The outer dimension needs to be
equal to the number of player."""
self.set_collision_arrays(1)
def set_collision_arrays(self, number_players):
def set_collision_arrays(self, number_players: int):
"""Sets collision arrays for the given number of players.
Args:
number_players (int): The number of players.
"""
self.world_borders_lower = self.world_borders[np.newaxis, :, 0].repeat(
number_players, axis=0
)
......@@ -27,7 +49,18 @@ class Movement:
number_players, axis=0
)
def get_counter_collisions(self, player_positions):
def get_counter_collisions(
self, player_positions: npt.NDArray[float]
) -> Tuple[npt.NDArray[bool], npt.NDArray[int], npt.NDArray[float]]:
"""
Args:
player_positions: 2D numpy array containing the positions of the players.
Returns:
collided: 1D numpy array indicating whether each player has collided with a counter.
relevant_axes: 1D numpy array indicating the relevant axis for each player's collision.
nearest_counter_to_player: 2D numpy array indicating the vector from each player to the nearest counter.
"""
counter_diff_vecs = (
player_positions[:, np.newaxis, :]
- self.counter_positions[np.newaxis, :, :]
......@@ -54,7 +87,19 @@ class Movement:
return collided, relevant_axes, nearest_counter_to_player
def get_player_push(self, player_positions):
def get_player_push(
self, player_positions: npt.NDArray[float]
) -> Tuple[npt.NDArray[bool], npt.NDArray[float]]:
"""Calculates the collision and push vectors for each player.
Args:
player_positions (numpy.ndarray): An array of shape (n, 2) representing the positions of the players.
Returns:
tuple: A tuple containing two numpy arrays. The first array (collisions) is a boolean array of shape (n,) indicating
whether each player has collided with another player. The second array (push_vectors) is a numpy array of shape (2,)
representing the total push vector for each player.
"""
distances_players_after_scipy = distance_matrix(
player_positions, player_positions
)
......@@ -77,8 +122,9 @@ class Movement:
players: dict[str, Player],
counters: list[Counter],
):
"""Moves a player in the direction specified in the action.action. If the player collides with a
counter or other player through this movement, then they are not moved.
"""Moves a player in the direction specified in the action.action.
If the player collides with a counter or other player through this movement, then they are not moved.
(The extended code with the two ifs is for sliding movement at the counters, which feels a bit smoother.
This happens, when the player moves diagonally against the counters or world boundary.
This just checks if the single axis party of the movement could move the player and does so at a lower rate.)
......
......@@ -174,6 +174,11 @@ class OrderManager:
"""Reference to the hook manager."""
def set_available_meals(self, available_meals):
"""Set the available meals from which orders can be generated.
Args:
available_meals (dict): A dictionary containing the available meals and their quantities.
"""
self.available_meals = available_meals
self.order_gen.available_meals = list(available_meals.values())
......@@ -370,8 +375,11 @@ class RandomOrderGeneration(OrderGeneration):
):
super().__init__(hook, random, **kwargs)
self.kwargs: RandomOrderKwarg = RandomOrderKwarg(**kwargs["kwargs"])
"""Configuration og the RandomOrder genration. See `RandomOrderKwarg`"""
self.next_order_time: datetime | None = datetime.max
"""For efficient checking to update order removable."""
self.number_cur_orders: int = 0
"""How many orders are currently open."""
self.needed_orders: int = 0
"""For the sample on dur but when it was restricted due to max order number."""
......@@ -387,6 +395,7 @@ class RandomOrderGeneration(OrderGeneration):
now,
self.kwargs.sample_on_serving,
)
self.number_cur_orders = 0
return []
def get_orders(
......
......@@ -85,6 +85,7 @@ class Player:
"""The env time until the player wants to move."""
self.interacting: bool = False
"""Is the player currently interacting with a counter."""
def set_movement(self, move_vector, move_until):
"""Called by the `perform_action` method. Movements will be performed (pos will be updated) in the `step`
......@@ -182,6 +183,15 @@ class Player:
self.last_interacted_counter = None
def progress(self, passed_time: timedelta, now: datetime):
"""Iterative progress call on the player.
Similar to the `Counter.progress` method.
How often it is called depends on the `env_step_frequency` or how often `step` is called.
Args:
passed_time: The amount of time that has passed since the last call of the method.
now: The current env time.
"""
if self.interacting and self.last_interacted_counter:
# TODO only interact on counter (Sink/CuttingBoard) if hands are free configure in config?
if self.holding:
......
......@@ -2,9 +2,11 @@
2D visualization of the CooperativeCuisine.
You can select the layout and start an environment:
- You can play the CooperativeCuisine. You can quit the application in the top right or end the level in the bottom right: [Screenshot](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/-/raw/main/overcooked_simulator/pygame_2d_vis/images/overcooked-start-screen.png?ref_type=heads)
- The orders are pictured in the top, the current score in the bottom left and the remaining time in the bottom: [Screenshot](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/-/raw/main/overcooked_simulator/pygame_2d_vis/images/overcooked-level-screen.png?ref_type=heads)
- The final screen after ending a level shows the score: [Screenshot](https://gitlab.ub.uni-bielefeld.de/scs/cocosy/overcooked-simulator/-/raw/main/overcooked_simulator/pygame_2d_vis/images/overcooked-end-screen.png?ref_type=heads)
- You can play the CooperativeCuisine. You can quit the application in the top right or end the level in the bottom right
- The orders are pictured in the top, the current score in the bottom left and the remaining time in the bottom
- The final screen after ending a level shows the score
.. include:: images/images_md_for_docs.md
The keys for the control of the players are:
......@@ -12,9 +14,12 @@ The keys for the control of the players are:
- Movement: `W`, `A`, `S`, `D`,
- Pickup: `E`
- Interact: `F`
- Swap Players (if configured): `SPACE`
### Player 2:
- Movement: `⬆`, `⬅`, `⬇`, `➡` (arrow keys)
- Pickup: `I`
- Interact: `SPACE`
- Interact: `O`
- Swap Players (if configured): `P`
"""
......@@ -43,6 +43,7 @@ class MenuStates(Enum):
log = logging.getLogger(__name__)
"""The logger for this module."""
class PlayerKeySet:
......
cooperative_cuisine/pygame_2d_vis/images/favicon.ico

14.7 KiB