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 (4)
  • Florian Schröder's avatar
    Add pygame tests · 70a61b45
    Florian Schröder authored
    This commit introduces a new test suite for the pygame functionality. It includes tests for color formatting and uniqueness, angle calculations, and proper initialization of the Visualizer. The tests aim to enhance game integrity and avoid potential issues with visualization.
    70a61b45
  • Florian Schröder's avatar
    Update comparisons and variable types, add tests · 14a081cc
    Florian Schröder authored
    This commit modifies comparison operators for timers to be less than or equal in various scripts. It also changes variable types explicitly in scores.py and adds several new tests for functionalities like effects, scores, information messages, trashing function etc. Additional updates include changes in values for certain default arguments in command line parser for generating images in the script for 2D visualization.
    14a081cc
  • Florian Schröder's avatar
    Add test for tutorial connectivity and game item tests · c86635e6
    Florian Schröder authored
    Expanded the tests in the 'test_study_server.py' file to include testing for tutorial connectivity. Added tests in 'test_start.py' to test the functionality of PlateDispenser and Sink game items. This included tests to validate the handling of dirty and clean plates and testing item drops and pick-ups. Various adjustments were made to players' positions to accommodate the tests.
    c86635e6
  • Florian Schröder's avatar
    Merge branch '112-more-tests' into 'dev' · 42436541
    Florian Schröder authored
    Resolve "More tests"
    
    Closes #112
    
    See merge request scs/cocosy/overcooked-simulator!79
    42436541
......@@ -684,10 +684,10 @@ class PlateDispenser(Counter):
def progress(self, passed_time: timedelta, now: datetime):
"""Check if plates arrive from outside the kitchen and add a dirty plate accordingly"""
if self.next_plate_time < now:
if self.next_plate_time <= now:
idx_delete = []
for i, times in enumerate(self.out_of_kitchen_timer):
if times < now:
if times <= now:
self.hook(DIRTY_PLATE_ARRIVES, counter=self, times=times, now=now)
idx_delete.append(i)
log.debug("Add dirty plate")
......
......@@ -163,9 +163,9 @@ class FireEffectManager(EffectManager):
self.active_effects.append((effect, target))
# reset new effects
self.new_effects = []
if self.next_finished_timer < now:
if self.next_finished_timer <= now:
for effect, target in self.active_effects:
if self.effect_to_timer[effect.uuid] < now:
if self.effect_to_timer[effect.uuid] <= now:
if isinstance(target, Item):
target = find_item_on_counters(target.uuid, self.counters)
if target:
......
......@@ -95,7 +95,7 @@ class InfoMsgManager(HookCallbackClass):
for player_id, msgs in env.info_msgs_per_player.items():
delete_msgs = []
for idx, msg in enumerate(msgs):
if msg["end_time"] < env.env_time:
if msg["end_time"] <= env.env_time:
delete_msgs.append(idx)
for idx in reversed(delete_msgs):
msgs.pop(idx)
......@@ -2,6 +2,7 @@ import argparse
import colorsys
import json
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
......@@ -1002,7 +1003,21 @@ def generate_recipe_images(config: dict, folder_path: str | Path):
pygame.image.save(screen, f"{folder_path}/{graph_dict['meal']}.png")
if __name__ == "__main__":
def main(args):
"""
Runs the Cooperative Cuisine Image Generation process.
This method takes command line arguments to specify the state file, visualization configuration file, and output file for the generated image. It then reads the visualization configuration
* file and state file, and calls the 'save_screenshot' and 'generate_recipe_images' methods to generate the image.
Args:
-s, --state: A command line argument of type `argparse.FileType("r", encoding="UTF-8")`. Specifies the state file to use for image generation. If not provided, the default value is 'ROOT_DIR / "pygame_2d_vis" / "sample_state.json"'.
-v, --visualization_config: A command line argument of type `argparse.FileType("r", encoding="UTF-8")`. Specifies the visualization configuration file to use for image generation. If not provided, the default value is 'ROOT_DIR / "pygame_2d_vis" / "visualization.yaml"'.
-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.",
......@@ -1024,12 +1039,16 @@ if __name__ == "__main__":
"-o",
"--output_file",
type=str,
default=ROOT_DIR / "pygame_2d_vis" / "generated" / "screenshot.jpg",
default=ROOT_DIR / "generated" / "screenshot.jpg",
)
args = parser.parse_args()
args = parser.parse_args(args)
with open(args.visualization_config, "r") as f:
viz_config = yaml.safe_load(f)
with open(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)
if __name__ == "__main__":
main(sys.argv[1:])
......@@ -94,13 +94,13 @@ class ScoreViaHooks(HookCallbackClass):
**kwargs: Additional keyword arguments to be passed to the parent class.
"""
super().__init__(name, env, **kwargs)
self.score_map = score_map
self.score_map: dict[str, float] = score_map
"""Mapping of hook references to scores."""
self.static_score = static_score
self.static_score: float = static_score
"""The static score to be added if no other conditions are met."""
self.kwarg_filter = kwarg_filter
self.kwarg_filter: dict[str, Any] = kwarg_filter
"""Filtering condition for keyword arguments."""
self.score_on_specific_kwarg = score_on_specific_kwarg
self.score_on_specific_kwarg: str = score_on_specific_kwarg
"""The specific keyword argument to score on."""
def __call__(self, hook_ref: str, env: Environment, **kwargs):
......@@ -114,7 +114,7 @@ class ScoreViaHooks(HookCallbackClass):
self.env.increment_score(self.static_score, info=hook_ref)
elif self.score_map and hook_ref in self.score_map:
if self.kwarg_filter:
if kwargs.items() <= self.kwarg_filter.items():
if self.kwarg_filter.items() <= kwargs.items():
self.env.increment_score(
self.score_map[hook_ref],
info=f"{hook_ref} - {self.kwarg_filter}",
......
......@@ -282,3 +282,10 @@ def test_websocket_wrong_inputs(create_env_config):
finally:
task.cancel()
loop.close()
def test_root():
with TestClient(app) as client:
res = client.get("/")
assert res.status_code == status.HTTP_200_OK
assert res.json() == {"Cooperative": "Cuisine"}
import json
import os
import yaml
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.pygame_2d_vis.drawing import calc_angle, Visualizer, main
from cooperative_cuisine.pygame_2d_vis.game_colors import RGB, WHITE, colors
def test_colors():
assert RGB(red=255, green=255, blue=255).hex_format() == "#FFFFFF"
assert WHITE.hex_format() == "#FFFFFF"
assert len(colors) >= 552
def test_calc_angle():
assert calc_angle([0.0, 1.0], [1.0, 0.0]) == -90.0
assert calc_angle([1.0, 1.0], [1.0, -1.0]) == -90.0
def test_drawing():
main([])
assert os.path.exists(os.path.join(ROOT_DIR, "generated", "screenshot.jpg"))
def test_visualizer():
with open(ROOT_DIR / "pygame_2d_vis" / "visualization.yaml", "r") as file:
visualization_config = yaml.safe_load(file)
vis = Visualizer(visualization_config)
vis.create_player_colors(10)
assert len(vis.player_colors) >= 10
assert len(set(vis.player_colors)) >= 10
with open(ROOT_DIR / "pygame_2d_vis" / "sample_state.json", "r") as file:
state = json.load(file)
image = vis.get_state_image(40, state)
assert image.shape == (480, 360, 3)
from collections import deque
from datetime import timedelta
import numpy as np
import pytest
import yaml
from cooperative_cuisine import ROOT_DIR
from cooperative_cuisine.action import ActionType, InterActionData, Action
from cooperative_cuisine.counters import Counter, CuttingBoard
from cooperative_cuisine.counters import (
Counter,
CuttingBoard,
CookingCounter,
ServingWindow,
Trashcan,
PlateDispenser,
Sink,
)
from cooperative_cuisine.effects import FireEffectManager
from cooperative_cuisine.environment import (
Environment,
)
from cooperative_cuisine.game_server import PlayerRequestType
from cooperative_cuisine.hooks import Hooks
from cooperative_cuisine.items import Item, ItemInfo, ItemType
from cooperative_cuisine.hooks import (
Hooks,
SERVE_NOT_ORDERED_MEAL,
hooks_via_callback_class,
PLAYER_ADDED,
POST_STEP,
)
from cooperative_cuisine.info_msg import InfoMsgManager
from cooperative_cuisine.items import Item, ItemInfo, ItemType, Plate, CookingEquipment
from cooperative_cuisine.scores import ScoreViaHooks
from cooperative_cuisine.server_results import (
PlayerInfo,
CreateEnvResult,
......@@ -21,7 +40,7 @@ from cooperative_cuisine.state_representation import (
StateRepresentation,
create_json_schema,
)
from cooperative_cuisine.utils import create_init_env_time
from cooperative_cuisine.utils import create_init_env_time, get_touching_counters
layouts_folder = ROOT_DIR / "configs" / "layouts"
environment_config_path = ROOT_DIR / "configs" / "environment_config.yaml"
......@@ -54,7 +73,6 @@ def layout_config():
with open(layout_path, "r") as file:
layout = file.read()
return layout
env.add_player("0")
@pytest.fixture
......@@ -218,6 +236,7 @@ def test_processing(env_config, layout_config, item_info):
},
)
env.counters.append(counter)
env.overwrite_counters(env.counters)
tomato = Item(name="Tomato", item_info=None)
env.add_player("1", np.array([2, 3]))
......@@ -308,3 +327,360 @@ def test_server_result_definition():
msg="123",
player_hash="1234324",
)
def test_fire(env_config, layout_config, item_info):
env = Environment(env_config, layout_config, item_info, as_files=False)
env.add_player("0")
oven = None
for c in env.counters:
if (
isinstance(c, CookingCounter)
and c.name == "Stove"
and c.occupied_by.name == "Pan"
):
oven = c
break
assert oven is not None
raw_patty = Item(name="RawPatty", item_info=env.item_info["RawPatty"])
assert oven.can_drop_off(raw_patty)
oven.drop_off(raw_patty, "0")
assert isinstance(oven.occupied_by, CookingEquipment)
assert oven.occupied_by.content_list == [raw_patty]
env.step(timedelta(seconds=env.item_info["CookedPatty"].seconds))
assert oven.occupied_by.content_list[0].name == "CookedPatty"
env.step(timedelta(seconds=env.item_info["BurntCookedPatty"].seconds))
assert oven.occupied_by.content_list[0].name == "BurntCookedPatty"
env.step(timedelta(seconds=env.item_info["Fire"].seconds))
assert len(oven.occupied_by.active_effects) != 0
assert oven.occupied_by.active_effects[0].name == "Fire"
fire_manager = env.effect_manager["FireManager"]
assert isinstance(fire_manager, FireEffectManager)
env.step(fire_manager.next_finished_timer - env.env_time)
touching_counters = get_touching_counters(oven, env.counters)
next_empty = None
connect_counter = None
for c in touching_counters:
if c.occupied_by:
assert len(c.occupied_by.active_effects) == 1
else:
assert len(c.active_effects) == 1
next_touching = get_touching_counters(c, env.counters)
for a in next_touching:
if a not in touching_counters and a.__class__.__name__ == "Counter":
a.occupied_by = None
next_empty = a
connect_counter = c
env.step(timedelta(seconds=0.01))
assert next_empty is not None
next_empty.occupied_by = Item(name="Tomato", item_info=env.item_info["Tomato"])
env.step(
fire_manager.effect_to_timer[connect_counter.active_effects[0].uuid]
- env.env_time
)
assert len(next_empty.occupied_by.active_effects) == 1
fire_extinguisher = None
for c in env.counters:
if c.occupied_by and c.occupied_by.name == "Extinguisher":
fire_extinguisher = c.occupied_by
c.occupied_by = None
break
assert fire_extinguisher is not None
env.players["0"].holding = fire_extinguisher
env.players["0"].pos = oven.pos
env.players["0"].pos[1] += 1.0
env.perform_action(
Action(
player="0",
action_type=ActionType.MOVEMENT,
action_data=np.array([0.0, -1.0]),
duration=0.1,
)
)
env.step(timedelta(seconds=0.1))
env.perform_action(
Action(
player="0",
action_type=ActionType.INTERACT,
action_data=InterActionData.START,
)
)
env.step(timedelta(seconds=env.item_info["Extinguisher"].seconds))
env.step(timedelta(seconds=env.item_info["Extinguisher"].seconds))
assert len(oven.occupied_by.active_effects) == 0
def test_score(env_config, layout_config, item_info):
def incr_score_callback(hook_ref, env: Environment, meal, meal_name, **kwargs):
assert isinstance(meal, Item)
assert isinstance(meal_name, str)
assert meal_name == "TomatoSoup"
env.increment_score(1_000, "Soup Soup")
env = Environment(env_config, layout_config, item_info, as_files=False)
assert env.score == 0.0
env.add_player("0")
env.register_callback_for_hook(SERVE_NOT_ORDERED_MEAL, incr_score_callback)
env.register_callback_for_hook(
SERVE_NOT_ORDERED_MEAL,
ScoreViaHooks(
name="123",
env=env,
score_on_specific_kwarg="meal_name",
score_map={"TomatoSoup": 2_000},
),
)
env.register_callback_for_hook(
SERVE_NOT_ORDERED_MEAL,
ScoreViaHooks(name="124", env=env, score_map={SERVE_NOT_ORDERED_MEAL: 4_000}),
)
env.register_callback_for_hook(
SERVE_NOT_ORDERED_MEAL,
ScoreViaHooks(
name="124",
env=env,
score_map={SERVE_NOT_ORDERED_MEAL: 8_000},
kwarg_filter={"meal_name": "TomatoSoup"},
),
)
env.register_callback_for_hook(
SERVE_NOT_ORDERED_MEAL,
ScoreViaHooks(
name="123",
env=env,
score_on_specific_kwarg="meal_name",
static_score=16_000,
score_map={},
),
)
serving_window = None
for c in env.counters:
if isinstance(c, ServingWindow):
serving_window = c
break
assert serving_window is not None
env.order_manager.serving_not_ordered_meals = True
env.order_manager.open_orders = []
plate = Plate(
transitions=env.counter_factory.filter_item_info(
by_item_type=ItemType.Meal, add_effects=True
),
clean=True,
item_info=env.item_info["Plate"],
hook=env.hook,
)
plate.content_list = [
Item(name="TomatoSoup", item_info=env.item_info["TomatoSoup"])
]
assert serving_window.can_drop_off(plate)
returned = serving_window.drop_off(plate, "0")
assert returned is None
plates_prev = len(serving_window.plate_dispenser.occupied_by)
assert env.score >= 31_000
assert len(serving_window.plate_dispenser.out_of_kitchen_timer) == 1
env.step(serving_window.plate_dispenser.out_of_kitchen_timer[0] - env.env_time)
assert len(serving_window.plate_dispenser.out_of_kitchen_timer) == 0
assert len(serving_window.plate_dispenser.occupied_by) == plates_prev + 1
assert (
serving_window.plate_dispenser.occupied_by[0].clean
!= serving_window.plate_dispenser.plate_config.return_dirty
)
def test_info_msgs(env_config, layout_config, item_info):
env_config_dict = yaml.load(env_config, Loader=yaml.Loader)
# TODO change after merge with 115
env_config_dict["extra_setup_functions"]["dummy_msg"] = {
"func": hooks_via_callback_class,
"kwargs": {
"hooks": [PLAYER_ADDED],
"callback_class": InfoMsgManager,
"callback_class_kwargs": {"msg": "hello there"},
},
}
env_config_dict["extra_setup_functions"]["dummy_msg_2"] = {
"func": hooks_via_callback_class,
"kwargs": {
"hooks": [POST_STEP],
"callback_class": InfoMsgManager,
"callback_class_kwargs": {"msg": "step step"},
},
}
env_config = yaml.dump(env_config_dict)
env = Environment(env_config, layout_config, item_info, as_files=False)
env.add_player("0")
assert env.info_msgs_per_player["0"][0]["msg"] == "hello there"
env.step(timedelta(seconds=0.1))
assert env.info_msgs_per_player["0"][1]["msg"] == "step step"
env.step(env.info_msgs_per_player["0"][0]["end_time"] - env.env_time)
assert len(env.info_msgs_per_player["0"]) == 2
assert env.info_msgs_per_player["0"][0]["msg"] == "step step"
def test_trashcan(env_config, layout_config, item_info):
env = Environment(env_config, layout_config, item_info, as_files=False)
env.add_player("0")
trash = None
for c in env.counters:
if isinstance(c, Trashcan):
trash = c
break
assert trash is not None
item = Item(name="Tomato", item_info=env.item_info["Tomato"])
assert trash.can_drop_off(item)
assert trash.drop_off(item, "0") is None
plate = Plate(
transitions=env.counter_factory.filter_item_info(
by_item_type=ItemType.Meal, add_effects=True
),
clean=True,
item_info=env.item_info["Plate"],
hook=env.hook,
)
plate.content_list = [
Item(name="TomatoSoup", item_info=env.item_info["TomatoSoup"])
]
assert trash.can_drop_off(plate)
assert trash.drop_off(plate, "0") == plate
assert plate.content_list == []
assert trash.pick_up(True, "0") is None
def test_plate_dispenser(env_config, layout_config, item_info):
env = Environment(env_config, layout_config, item_info, as_files=False)
env.add_player("0")
plate_dis = None
for c in env.counters:
if isinstance(c, PlateDispenser):
plate_dis = c
break
assert plate_dis is not None
assert (
len(plate_dis.occupied_by) > 0
) # Otherwise adapt env_config above before passing to Environment
n_plates = len(plate_dis.occupied_by)
env = Environment(env_config, layout_config, item_info, as_files=False)
env.add_player("0")
item = Item(name="ChoppedTomato", item_info=env.item_info["ChoppedTomato"])
assert plate_dis.can_drop_off(item)
returned = plate_dis.drop_off(item, "0")
assert returned is None
assert len(plate_dis.occupied_by) == n_plates
first_plate = plate_dis.pick_up(True, "0")
assert isinstance(first_plate, Plate)
assert (
first_plate.content_list and first_plate.content_list[0].name == "ChoppedTomato"
)
assert plate_dis.can_drop_off(first_plate)
returned = plate_dis.drop_off(first_plate, "0")
assert isinstance(returned, Plate) and len(returned.content_list) == 0
assert plate_dis.occupied_by[-1].content_list[0].name == "ChoppedTomato"
plate_dis.occupied_by = deque()
assert plate_dis.can_drop_off(item)
returned = plate_dis.drop_off(item, "0")
assert returned is None
assert plate_dis.occupied_by[0].name == "ChoppedTomato"
def test_sink(env_config, layout_config, item_info):
env = Environment(env_config, layout_config, item_info, as_files=False)
env.add_player("0")
sink = None
for c in env.counters:
if isinstance(c, Sink):
sink = c
break
assert sink is not None
env.players["0"].pos = sink.pos
env.players["0"].pos[1] -= 1.0
env.perform_action(
Action(
player="0",
action_type=ActionType.MOVEMENT,
action_data=np.array([0.0, 1.0]),
duration=0.1,
)
)
env.step(timedelta(seconds=0.1))
plate = Plate(
transitions=env.counter_factory.filter_item_info(
by_item_type=ItemType.Meal, add_effects=True
),
clean=False,
item_info=env.item_info["Plate"],
hook=env.hook,
)
plate_2 = Plate(
transitions=env.counter_factory.filter_item_info(
by_item_type=ItemType.Meal, add_effects=True
),
clean=False,
item_info=env.item_info["Plate"],
hook=env.hook,
)
clean_plate = Plate(
transitions=env.counter_factory.filter_item_info(
by_item_type=ItemType.Meal, add_effects=True
),
clean=True,
item_info=env.item_info["Plate"],
hook=env.hook,
)
assert sink.can_drop_off(plate)
assert sink.drop_off(plate, "0") is None
assert sink.can_drop_off(plate_2)
assert sink.drop_off(plate_2, "0") is None
assert not sink.can_drop_off(clean_plate)
assert len(sink.occupied_by) == 2
env.perform_action(
Action(
player="0",
action_type=ActionType.INTERACT,
action_data=InterActionData.START,
)
)
env.step(timedelta(seconds=env.item_info["Plate"].seconds))
assert len(sink.occupied_by) == 1
assert len(sink.sink_addon.occupied_by) == 1
assert sink.sink_addon.occupied_by[0].clean
assert not sink.occupied_by[0].clean
assert sink.pick_up(True, "0") is None
env.step(timedelta(seconds=env.item_info["Plate"].seconds))
assert len(sink.occupied_by) == 0
assert len(sink.sink_addon.occupied_by) == 2
assert sink.sink_addon.occupied_by[0].clean
assert sink.sink_addon.occupied_by[1].clean
item = Item(name="ChoppedTomato", item_info=env.item_info["ChoppedTomato"])
assert sink.sink_addon.can_drop_off(item)
assert sink.sink_addon.drop_off(item, "0") is None
assert sink.sink_addon.pick_up(True, "0").content_list[0].name == "ChoppedTomato"
assert len(sink.sink_addon.occupied_by) == 1
......@@ -92,4 +92,40 @@ def test_game_server_crashed():
assert res.status_code == status.HTTP_500_INTERNAL_SERVER_ERROR
def test_tutorial():
test_response = Response()
test_response.status_code = status.HTTP_200_OK
test_response.encoding = "utf8"
test_response._content = json.dumps(
{
"player_info": {
"0": {
"player_id": "0",
"client_id": "ksjdhfkjsdfn",
"player_hash": "shdfbmsndfb",
}
},
"env_id": "123456789",
"recipe_graphs": [],
}
).encode()
with mock.patch.object(
study_server_module, "request_game_server", return_value=test_response
) as mock_call:
with TestClient(app) as client:
res = client.post("/connect_to_tutorial/124")
assert res.status_code == status.HTTP_200_OK
mock_call.assert_called_once()
with mock.patch.object(
study_server_module, "request_game_server", return_value=test_response
) as mock_call:
with TestClient(app) as client:
res = client.post("/disconnect_from_tutorial/124")
assert res.status_code == status.HTTP_200_OK
# TOOD test bots