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

Merge branch '112-more-tests' into 'dev'

Resolve "More tests"

Closes #112

See merge request scs/cocosy/overcooked-simulator!79
parents 0ddb9e04 c86635e6
No related branches found
No related tags found
1 merge request!79Resolve "More tests"
Pipeline #48350 passed
......@@ -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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment