diff --git a/overcooked_simulator/counter_factory.py b/overcooked_simulator/counter_factory.py index 9b1487c3706a70dde8b224283a1877c126c41b56..639459a56508c627eb5015aa5b3080bfcd2a1737 100644 --- a/overcooked_simulator/counter_factory.py +++ b/overcooked_simulator/counter_factory.py @@ -81,10 +81,10 @@ def convert_words_to_chars(layout_chars_config: dict[str, str]) -> dict[str, str """ word_refs = { "hash": "#", - "space": " ", + # "space": " ", "dot": ".", "comma": ",", - "semicolon": ";", + # "semicolon": ";", "colon": ":", "minus": "-", "exclamation": "!", @@ -98,6 +98,7 @@ def convert_words_to_chars(layout_chars_config: dict[str, str]) -> dict[str, str "left": "<", "pipe": "|", "at": "@", + "wave": "~", # ~ is None / null in yaml "ocurlybracket": "{", "ccurlybracket": "}", "osquarebracket": "[", @@ -163,8 +164,11 @@ class CounterFactory: """A set of characters that represent counters for agents or free spaces.""" self.counter_classes: dict[str, Type] = dict( - inspect.getmembers( - sys.modules["overcooked_simulator.counters"], inspect.isclass + filter( + lambda k: issubclass(k[1], Counter), + inspect.getmembers( + sys.modules["overcooked_simulator.counters"], inspect.isclass + ), ) ) """A dictionary of counter classes imported from the 'overcooked_simulator.counters' module.""" @@ -191,6 +195,8 @@ class CounterFactory: assert self.can_map(c), f"Can't map counter char {c}" counter_class = None + if c == "@": + print("-") if self.layout_chars_config[c] in self.item_info: item_info = self.item_info[self.layout_chars_config[c]] if item_info.type == ItemType.Equipment and item_info.equipment: @@ -221,7 +227,20 @@ class CounterFactory: ) if counter_class is None: - counter_class = self.counter_classes[self.layout_chars_config[c]] + if self.layout_chars_config[c] in self.counter_classes: + counter_class = self.counter_classes[self.layout_chars_config[c]] + elif self.layout_chars_config[c] == "Plate": + return Counter( + pos=pos, + hook=self.hook, + occupied_by=Plate( + transitions=self.filter_item_info( + by_item_type=ItemType.Meal, add_effects=True + ), + clean=True, + item_info=self.item_info[Plate.__name__], + ), + ) kwargs = { "pos": pos, "hook": self.hook, diff --git a/overcooked_simulator/counters.py b/overcooked_simulator/counters.py index 3d930aa54e15a4c6ad9f187b3766eee34f96cc07..40779e5cb228c993a65b5264f684225ea1ff78bc 100644 --- a/overcooked_simulator/counters.py +++ b/overcooked_simulator/counters.py @@ -623,7 +623,7 @@ class Trashcan(Counter): return None def can_drop_off(self, item: Item) -> bool: - return True + return item.name != "Extinguisher" class CookingCounter(Counter): diff --git a/overcooked_simulator/example_study_server.py b/overcooked_simulator/example_study_server.py new file mode 100644 index 0000000000000000000000000000000000000000..778135f47054e0cdc4a948f7f3a1098b95542426 --- /dev/null +++ b/overcooked_simulator/example_study_server.py @@ -0,0 +1,136 @@ +""" +# Usage +- Set `CONNECT_WITH_STUDY_SERVER` in overcooked_gui.py to True. +- Run this script. Copy the manager id that is printed +- Run the game_server.py script with the manager id copied from the terminal +``` +python game_server.py --manager_ids COPIED_UUID +``` +- Run 2 overcooked_gui.py scripts in different terminals. For more players change `NUMBER_PLAYER_PER_ENV` and start more guis. + +The environment starts when all players connected. +""" + +import argparse +import asyncio +import logging +from typing import Tuple + +import requests +import uvicorn +from fastapi import FastAPI + +from overcooked_simulator import ROOT_DIR +from overcooked_simulator.game_server import CreateEnvironmentConfig +from overcooked_simulator.server_results import PlayerInfo +from overcooked_simulator.utils import ( + url_and_port_arguments, + add_list_of_manager_ids_arguments, +) + +NUMBER_PLAYER_PER_ENV = 2 + +log = logging.getLogger(__name__) + + +app = FastAPI() + +game_server_url = "localhost:8000" +server_manager_id = None + + +# @app.get("/") +# async def root(response_class=HTMLResponse): +# return """ +# <html> +# <head> +# <title>Overcooked Game</title> +# </head> +# <body> +# <h1>Start Game!</h1> +# <button type="button">Click Me!</button> +# </body> +# </html> +# """ + +running_envs: dict[str, Tuple[int, dict[str, PlayerInfo], list[str]]] = {} +current_free_envs = [] + + +@app.post("/connect_to_game/{request_id}") +async def want_to_play(request_id: str): + global current_free_envs + # TODO based on study desing / internal state of request id current state (which level to play) + if current_free_envs: + current_free_env = current_free_envs.pop() + + running_envs[current_free_env][2].append(request_id) + new_running_env = ( + running_envs[current_free_env][0] + 1, + running_envs[current_free_env][1], + running_envs[current_free_env][2], + ) + player_info = running_envs[current_free_env][1][str(new_running_env[0])] + running_envs[current_free_env] = new_running_env + if new_running_env[0] < NUMBER_PLAYER_PER_ENV - 1: + current_free_env.append(current_free_env) + return player_info + else: + environment_config_path = ROOT_DIR / "game_content" / "environment_config.yaml" + layout_path = ROOT_DIR / "game_content" / "layouts" / "basic.layout" + item_info_path = ROOT_DIR / "game_content" / "item_info.yaml" + with open(item_info_path, "r") as file: + item_info = file.read() + with open(layout_path, "r") as file: + layout = file.read() + with open(environment_config_path, "r") as file: + environment_config = file.read() + creation_json = CreateEnvironmentConfig( + manager_id=server_manager_id, + number_players=NUMBER_PLAYER_PER_ENV, + environment_settings={"all_player_can_pause_game": False}, + item_info_config=item_info, + environment_config=environment_config, + layout_config=layout, + seed=1234567890, + ).model_dump(mode="json") + # todo async + env_info = requests.post( + game_server_url + "/manage/create_env/", json=creation_json + ) + + if env_info.status_code == 403: + raise ValueError(f"Forbidden Request: {env_info.json()['detail']}") + env_info = env_info.json() + print(env_info) + running_envs[env_info["env_id"]] = (0, env_info["player_info"], [request_id]) + current_free_envs.append(env_info["env_id"]) + return env_info["player_info"]["0"] + + +def main(host, port, game_server_url_, manager_id): + global game_server_url, server_manager_id + game_server_url = "http://" + game_server_url_ + server_manager_id = manager_id[0] + print(f"Use {server_manager_id=} for {game_server_url=}") + loop = asyncio.new_event_loop() + config = uvicorn.Config(app, host=host, port=port, loop=loop) + server = uvicorn.Server(config) + loop.run_until_complete(server.serve()) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser( + prog="Overcooked Simulator Study Server", + description="Study Server: Match Making, client pre and post managing.", + epilog="For further information, see https://scs.pages.ub.uni-bielefeld.de/cocosy/overcooked-simulator/overcooked_simulator.html", + ) + url_and_port_arguments(parser=parser, server_name="Study Server", default_port=8080) + add_list_of_manager_ids_arguments(parser=parser) + args = parser.parse_args() + main( + args.url, + args.port, + game_server_url_="localhost:8000", + manager_id=args.manager_ids, + ) diff --git a/overcooked_simulator/game_content/environment_config.yaml b/overcooked_simulator/game_content/environment_config.yaml index fc792099aa46b853e980e659e1fb2528328ad16c..4264c45679fdb9a66f6961c0ecd4bd8d8e0e9664 100644 --- a/overcooked_simulator/game_content/environment_config.yaml +++ b/overcooked_simulator/game_content/environment_config.yaml @@ -18,15 +18,16 @@ meals: layout_chars: _: Free - hash: Counter + hash: Counter # # A: Agent pipe: Extinguisher P: PlateDispenser C: CuttingBoard X: Trashcan - W: ServingWindow + $: ServingWindow S: Sink +: SinkAddon + at: Plate # @ just a clean plate on a counter U: Pot # with Stove Q: Pan # with Stove O: Peel # with Oven @@ -41,6 +42,15 @@ layout_chars: G: Sausage # sausaGe B: Bun M: Meat + question: Counter # ? mushroom + ↓: Counter + ^: Counter + right: Counter + left: Counter + wave: Free # ~ Water + minus: Free # - Ice + dquote: Counter # " wall/truck + p: Counter # second plate return ?? orders: @@ -88,7 +98,8 @@ player_config: player_speed_units_per_seconds: 6 interaction_range: 1.6 restricted_view: False - view_angle: 95 + view_angle: 70 + view_range: 5.5 # in grid units, can be "null" effect_manager: FireManager: diff --git a/overcooked_simulator/game_content/layouts/basic.layout b/overcooked_simulator/game_content/layouts/basic.layout index 94e5fb1dc055cefacfa6ef562ad1fe04424e726b..5238c21f86c487ed90ff8efc6a373c6213d1f6c9 100644 --- a/overcooked_simulator/game_content/layouts/basic.layout +++ b/overcooked_simulator/game_content/layouts/basic.layout @@ -1,7 +1,7 @@ #QU#FO#TNLB# #__________M |__________K -W__________I +$__________I #__A_____A_D C__________E C__________G diff --git a/overcooked_simulator/game_content/layouts/large.layout b/overcooked_simulator/game_content/layouts/large.layout index 6933567897246f90838b6a7efd8f15e48ca5b9bf..460244ca3fd685c71cdab3cc2bc6d5d4ae1c9092 100644 --- a/overcooked_simulator/game_content/layouts/large.layout +++ b/overcooked_simulator/game_content/layouts/large.layout @@ -4,7 +4,7 @@ #____________________________________# #____________________________________# #____________________________________K -W____________________________________I +$____________________________________I #____________________________________# #____________________________________# #__A_____A___________________________D diff --git a/overcooked_simulator/game_content/layouts/large_t.layout b/overcooked_simulator/game_content/layouts/large_t.layout new file mode 100644 index 0000000000000000000000000000000000000000..304e6f7746f4e0c7510f69395b55c7f691de84f6 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/large_t.layout @@ -0,0 +1,45 @@ +#QU#F###O#T#################N###L###B# +#____________________________________# +#____________________________________M +#____________________________________# +#____________________________________# +#____________________________________K +W____________________________________I +#____________________________________# +#____________________________________# +#__A_____A___________________________D +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +C____________________________________E +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +#____________________________________# +C____________________________________G +#____________________________________# +#P#####S+####X#####S+################# \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-1-far-apart.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-1-far-apart.layout new file mode 100644 index 0000000000000000000000000000000000000000..0fa1d52d170632fb5996b5baf907f502d0b1cc06 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-1-far-apart.layout @@ -0,0 +1,14 @@ +###N####U#### +#___________| +#___A___A___# +#___________S +##########__+ +P___________# +$___________# +$___________X +###C#C###@@## + +; seconds=150 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/1-1_(Overcooked!) \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-2-pedestrians.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-2-pedestrians.layout new file mode 100644 index 0000000000000000000000000000000000000000..e9ac2fb08cd9d6437eac0d30da17d6a6114f037e --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-2-pedestrians.layout @@ -0,0 +1,12 @@ +_##U#U#__###|X_# +______#____A___$ ++_____@__@_____$ +S________#_____P +____A____#______ +_##C#C#__#T#N##_ + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/1-2_(Overcooked!) +; pedestrians: down the middle road diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-3-moving-counters.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-3-moving-counters.layout new file mode 100644 index 0000000000000000000000000000000000000000..3dfb4177c4332c3263818e86c6390d562f5bc1bb --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-3-moving-counters.layout @@ -0,0 +1,15 @@ +_____________ +___U#U##$$P|_ +_#____#______ +_@__A_#___A__ +_@____#______ +_@____#______ +_X____#______ +_#C#C##NT?___ +_____________ + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=false +; link: https://overcooked.fandom.com/wiki/1-3_(Overcooked!) +; moving counters \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-4-bottleneck.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-4-bottleneck.layout new file mode 100644 index 0000000000000000000000000000000000000000..1eac27c1c4852d05ecec30d6b9ddf1942fceecde --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-4-bottleneck.layout @@ -0,0 +1,14 @@ +##S+####QQQQ# +T____###____| +M_A__###__A_# +B___________# +L____###____$ +#____###____$ +#____###____P +X____###____@ +##C#C###@@@@# + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/1-4_(Overcooked!) \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-5-circle.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-5-circle.layout new file mode 100644 index 0000000000000000000000000000000000000000..a278d9ab1c5d4214d8194d0b2ef82822dff2ae32 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-5-circle.layout @@ -0,0 +1,14 @@ +#####P$$|##### +#?NT#A_A_#S+## +#____________X +#_##########_# +#_##########_# +#_##########_# +#_#######@@@_# +#____________# +#C#C####U#U#U# + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/1-5_(Overcooked!) \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/1-6-raising-platforms.layout b/overcooked_simulator/game_content/layouts/overcooked-1/1-6-raising-platforms.layout new file mode 100644 index 0000000000000000000000000000000000000000..5af323f1c86c874cb126fb25170fe46e67cf01e3 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/1-6-raising-platforms.layout @@ -0,0 +1,14 @@ +##S+###@Q@Q@# +M___________# +T___________| +L___________$ +#___________$ +#___________P +X___________# +##C#C##Q#Q#B# + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/1-6_(Overcooked!) +; raising platforms based on earthquakes \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/2-1-moving-trucks.layout b/overcooked_simulator/game_content/layouts/overcooked-1/2-1-moving-trucks.layout new file mode 100644 index 0000000000000000000000000000000000000000..b7a4745ab23db07ddaa344b83e36ffa2840a2a07 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/2-1-moving-trucks.layout @@ -0,0 +1,17 @@ +_______________ +__#QQQ#@@@#____ +__#_______$____ +__B_______$____ +__#_______P____ +_______________ +__M__A____X____ +__L_______#____ +__T__A____C____ +__#|###C###____ +_______________ + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=false +; link: https://overcooked.fandom.com/wiki/2-1_(Overcooked!) +; moving trucks: counters and ground are moving \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/2-2-rats.layout b/overcooked_simulator/game_content/layouts/overcooked-1/2-2-rats.layout new file mode 100644 index 0000000000000000000000000000000000000000..4543c03b09012c4d8f86a88d3eb688f7230ba0cf --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/2-2-rats.layout @@ -0,0 +1,17 @@ +#####P$$|##### +#####____##### +##S+#____#S+## +X____________X +#____________# +U___@__A_@___# +#___@____@___# +#___#_A__#___# +U___#____#___# +#___#____#___# +#?N##C##C##NT# + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/2-2_(Overcooked!) +; rats: steal ingredients + meals \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/2-3-separated-conveyors.layout b/overcooked_simulator/game_content/layouts/overcooked-1/2-3-separated-conveyors.layout new file mode 100644 index 0000000000000000000000000000000000000000..6980844cd618acaf578975d98b261d0b0c6d0147 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/2-3-separated-conveyors.layout @@ -0,0 +1,15 @@ +>>>>>>>>>>>>>>>↓ +^#_____##@____#↓ +^+A____|#@_A__#↓ +^S_____Q#C____$↓ +^M_____###____$↓ +^L_____Q#C____P↓ +^B_____###____#↓ +^T_____X#@____X↓ +^<<<<<<<<<<<<<<< + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/2-3_(Overcooked!) +; conveyors \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/2-4-separated-2.layout b/overcooked_simulator/game_content/layouts/overcooked-1/2-4-separated-2.layout new file mode 100644 index 0000000000000000000000000000000000000000..d0416de526c511526c42cf430b7ae40ead2ff46b --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/2-4-separated-2.layout @@ -0,0 +1,16 @@ +#@@@##C#C###### +#_____________$ +#___A_________$ +#_____________P +####____###___# +X<<<<<<X>>>>>>X +#___###____#### +Q_____________# +#________A____# +Q_____________# +##Q#+S#|##BTLM# + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/2-4_(Overcooked!) \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/3-1-ice.layout b/overcooked_simulator/game_content/layouts/overcooked-1/3-1-ice.layout new file mode 100644 index 0000000000000000000000000000000000000000..4d47b65b2cd83bf38a0bdf9c08c6d235c4074cc9 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/3-1-ice.layout @@ -0,0 +1,19 @@ +~~~~~~~~~~~~~~~~ +~~~~~~P$$~~~~~~~ +~~~---------~~~~ +~~~-----------~~ +~~--#C#C|##----~ +~---S#####IA---~ +~---+#####K---~~ +~---#FFF##F---~~ +~~-A----@@@--~~~ +~~-----------~~~ +~~~~--------~~~~ +~~~~~~~----~~~~~ +~~~~~~~~~~~~~~~~ + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/3-1_(Overcooked!) +; ice: accelerating \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/3-2-separated-moving-counters.layout b/overcooked_simulator/game_content/layouts/overcooked-1/3-2-separated-moving-counters.layout new file mode 100644 index 0000000000000000000000000000000000000000..dc8aefbc3a7c3e149bbff07a600bd452453edee9 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/3-2-separated-moving-counters.layout @@ -0,0 +1,16 @@ +##$$####$$### +####P##______ +______?______ +______N______ +U_____T_____U +X#####X______ +U_____#_____U +______#______ +__A___#__A___ +@@@C#C#______ + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=false +; link: https://overcooked.fandom.com/wiki/3-2_(Overcooked!) +; moving counters \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/3-3-moving-trucks-2.layout b/overcooked_simulator/game_content/layouts/overcooked-1/3-3-moving-trucks-2.layout new file mode 100644 index 0000000000000000000000000000000000000000..f5385f60d38a706c609fc6f199a323f1bfeb36d1 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/3-3-moving-trucks-2.layout @@ -0,0 +1,15 @@ +__________""#NIK?TX## +__________""#__A____$ +__________""#_______$ +__________""#__A____P +__________""_________ +C_________""________C +#_________""________# +C_________""________C +#|U#U#U@@####F@F@F|## + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=false +; link: https://overcooked.fandom.com/wiki/3-3_(Overcooked!) +; moving trucks: counters and ground are moving \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/3-4-ice-moving-platforms.layout b/overcooked_simulator/game_content/layouts/overcooked-1/3-4-ice-moving-platforms.layout new file mode 100644 index 0000000000000000000000000000000000000000..389caab9fc91d253aeba25b3c9129e416139b326 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/3-4-ice-moving-platforms.layout @@ -0,0 +1,14 @@ +##F#F#~~~~@@F## +X-----~~~-----# +#-A---~~~-----# +I-----~~~-----$ +#-----~~~-----$ +K-----~~~-----P +|--A----------- +#+S##~---#C#C## + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/3-4_(Overcooked!) +; ice, moving platforms, water \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/4-1-moving-counters.layout b/overcooked_simulator/game_content/layouts/overcooked-1/4-1-moving-counters.layout new file mode 100644 index 0000000000000000000000000000000000000000..4b6fcaec5c611c09428f82fd8d116c69bee5d47c --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/4-1-moving-counters.layout @@ -0,0 +1,17 @@ +"""""#|#O#O#O#X#""" +_____#_________#""" +__#____________#""" +__####____##@@@#### +__+_______#_______$ +__S___A___#___A___$ +__#_______#_______P +__#####C#C#____#### +__#____________#""" +____"#_________#""" +"""""###DTE?G###""" + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/4-1_(Overcooked!) +; moving counters \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/4-2-dark.layout b/overcooked_simulator/game_content/layouts/overcooked-1/4-2-dark.layout new file mode 100644 index 0000000000000000000000000000000000000000..a591aa97c088a0edaa01287038770ff4df30d579 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/4-2-dark.layout @@ -0,0 +1,19 @@ +#########|U#@@@# +##S+##C##______$ +#______________$ +C______________P +#______________# +#______###X##### +U______#######T# +#__A___________# +#___________A__# +N______________# +#########______? +################ + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/4-2_(Overcooked!) +; link: https://www.trueachievements.com/game/Overcooked/walkthrough/6 +; dark: only flashlight fov \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/4-3-moving-counters.layout b/overcooked_simulator/game_content/layouts/overcooked-1/4-3-moving-counters.layout new file mode 100644 index 0000000000000000000000000000000000000000..eb704056ac8135e4e8cc11ede3dbd135f4794340 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/4-3-moving-counters.layout @@ -0,0 +1,18 @@ +###S+#####P$$##X# +#_______|_______# +C_______________Q +#_______#_______# +#_______#_______# +###_######@@@_### +B_______#_______# +T__A____#___A___# +M_______________Q +L_______#_______# +#_______#_______# +################# + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/4-3_(Overcooked!) +; moving counters \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/overcooked-1/4-4-moving-counters-separated.layout b/overcooked_simulator/game_content/layouts/overcooked-1/4-4-moving-counters-separated.layout new file mode 100644 index 0000000000000000000000000000000000000000..88bc660ac2bc1ddcbd2600b715a38613dc4d16b7 --- /dev/null +++ b/overcooked_simulator/game_content/layouts/overcooked-1/4-4-moving-counters-separated.layout @@ -0,0 +1,16 @@ +#________↓C#O##O### ++________↓________S +S________↓________+ +#________↓________# +#________↓#MBLT#@@# +#|#Q##Q#C↓________p +P________↓________$ +$_____A__↓________$ +$________↓________# +#@@#G?DE#X_________ + +; seconds=240 +; plates={c:0, d:0} +; dirty_plates=true +; link: https://overcooked.fandom.com/wiki/4-4_(Overcooked!) +; moving counters \ No newline at end of file diff --git a/overcooked_simulator/game_content/layouts/split.layout b/overcooked_simulator/game_content/layouts/split.layout index 39bace3e0a94b0594d8ef9f294daeb40aae4492f..3f29e313f63f566ad9deb846f465412949d82e0c 100644 --- a/overcooked_simulator/game_content/layouts/split.layout +++ b/overcooked_simulator/game_content/layouts/split.layout @@ -1,7 +1,7 @@ #QU#T###NLB# #__________M #____A_____# -W__________# +$__________# ############ C__________# C_____A____# diff --git a/overcooked_simulator/game_server.py b/overcooked_simulator/game_server.py index d54341a53d2a8100a0a2b9aa12c4bf3879e27f8b..84879915dce1ff120d6f7a99e405466aa9f7adce 100644 --- a/overcooked_simulator/game_server.py +++ b/overcooked_simulator/game_server.py @@ -593,17 +593,6 @@ def manage_websocket_message(message: str, client_id: str) -> PlayerRequestResul "player_hash" in message_dict ), "'player_hash' key not in message dictionary'" match request_type: - case PlayerRequestType.READY: - accepted = environment_handler.set_player_ready( - message_dict["player_hash"] - ) - return { - "request_type": request_type.value, - "msg": f"ready{' ' if accepted else ' not '}accepted", - "status": 200 if accepted else 400, - "player_hash": message_dict["player_hash"], - } - case PlayerRequestType.GET_STATE: state = environment_handler.get_state(message_dict["player_hash"]) if isinstance(state, int): @@ -616,7 +605,16 @@ def manage_websocket_message(message: str, client_id: str) -> PlayerRequestResul "player_hash": None, } return state - + case PlayerRequestType.READY: + accepted = environment_handler.set_player_ready( + message_dict["player_hash"] + ) + return { + "request_type": request_type.value, + "msg": f"ready{' ' if accepted else ' not '}accepted", + "status": 200 if accepted else 400, + "player_hash": message_dict["player_hash"], + } case PlayerRequestType.ACTION: assert ( "action" in message_dict diff --git a/overcooked_simulator/gui_2d_vis/drawing.py b/overcooked_simulator/gui_2d_vis/drawing.py index d9e5e8e22b0c3ad16df665e496c759178cd26262..fdf3d59050ec788d95e7520b34194dafcce4b3d0 100644 --- a/overcooked_simulator/gui_2d_vis/drawing.py +++ b/overcooked_simulator/gui_2d_vis/drawing.py @@ -45,11 +45,12 @@ def grayscale(img): return surface -def create_polygon(n, length): +def create_polygon(n, start_vec): if n == 1: return np.array([0, 0]) - vector = np.array([length, 0]) + vector = start_vec.copy() + angle = (2 * np.pi) / n rot_matrix = np.array( @@ -153,31 +154,66 @@ class Visualizer: direction = pygame.math.Vector2(state["view_restriction"]["direction"]) pos = pygame.math.Vector2(state["view_restriction"]["position"]) angle = state["view_restriction"]["angle"] / 2 + range = state["view_restriction"]["range"] + + angle = min(angle, 180) pos = pos * grid_size + pygame.math.Vector2([grid_size / 2, grid_size / 2]) - rect_scale = max(width, height) + rect_scale = max(width, height) * 2 + # rect_scale = 2 * grid_size left_beam = pos + (direction.rotate(angle) * rect_scale * 2) right_beam = pos + (direction.rotate(-angle) * rect_scale * 2) - pygame.draw.polygon( - screen, - colors["black"], - ( - pos - (direction * grid_size * 0.6), - left_beam - (direction * grid_size * 0.6), - left_beam + (direction.rotate(90) * rect_scale), + offset_front = direction * grid_size * 0.7 + if angle != 180: + pygame.draw.polygon( + screen, + colors["black"], + ( + pos - offset_front, + left_beam - offset_front, + left_beam + (direction.rotate(90) * rect_scale), + pos + - (direction * rect_scale * 2) + + (direction.rotate(90) * rect_scale), + pos + - (direction * rect_scale * 2) + + (direction.rotate(-90) * rect_scale), + right_beam + (direction.rotate(-90) * rect_scale), + right_beam - offset_front, + ), + ) + if range: + n_circle_points = 40 + + start_vec = np.array(-direction * range) + points = ( + np.array(create_polygon(n_circle_points, start_vec)) * grid_size + ) + pos + + circle_closed = np.concatenate([points, points[0:1]], axis=0) + + corners = [ + pos - (direction * rect_scale), + *circle_closed, + pos - (direction * rect_scale), pos - - (direction * rect_scale * 2) + - (direction * rect_scale) + (direction.rotate(90) * rect_scale), pos - - (direction * rect_scale * 2) + + (direction * rect_scale) + + (direction.rotate(90) * rect_scale), + pos + + (direction * rect_scale) + (direction.rotate(-90) * rect_scale), - right_beam + (direction.rotate(-90) * rect_scale), - right_beam - (direction * grid_size * 0.6), - ), - ) + pos + - (direction * rect_scale) + + (direction.rotate(-90) * rect_scale), + ] + + pygame.draw.polygon(screen, colors["black"], [*corners]) def draw_background( self, surface: pygame.Surface, width: int, height: int, grid_size: int @@ -503,7 +539,9 @@ class Visualizer: burnt=item["type"].startswith("Burnt"), ) elif "content_list" in item and item["content_list"]: - triangle_offsets = create_polygon(len(item["content_list"]), length=10) + triangle_offsets = create_polygon( + len(item["content_list"]), np.array([0.10]) + ) scale = 1 if len(item["content_list"]) == 1 else 0.6 for idx, o in enumerate(item["content_list"]): self.draw_item( diff --git a/overcooked_simulator/gui_2d_vis/overcooked_gui.py b/overcooked_simulator/gui_2d_vis/overcooked_gui.py index b6a62b5b5f693675560b02fb283e3e7d616277da..a537861748079b3b6aaefc828b4b7942d41483ba 100644 --- a/overcooked_simulator/gui_2d_vis/overcooked_gui.py +++ b/overcooked_simulator/gui_2d_vis/overcooked_gui.py @@ -1,10 +1,13 @@ import argparse import dataclasses +import glob import json import logging import random import sys +import uuid from enum import Enum +from pathlib import Path from subprocess import Popen import numpy as np @@ -28,8 +31,11 @@ from overcooked_simulator.utils import ( url_and_port_arguments, disable_websocket_logging_arguments, add_list_of_manager_ids_arguments, + setup_logging, ) +CONNECT_WITH_STUDY_SERVER = False + class MenuStates(Enum): Start = "Start" @@ -58,8 +64,11 @@ class PlayerKeySet: Movement keys in the following order: Down, Up, Left, Right Args: - player_name: The name of the player to control. - keys: The keys which control this player in the following order: Down, Up, Left, Right, Interact, Pickup. + move_keys: The keys which control this players movement in the following order: Down, Up, Left, Right. + interact_key: The key to interact with objects in the game. + pickup_key: The key to pick items up or put them down. + switch_key: The key for switching through controllable players. + players: The player indices which this keyset can control. """ self.move_vectors: list[list[int]] = [[-1, 0], [1, 0], [0, -1], [0, 1]] self.key_to_movement: dict[pygame.key, list[int]] = { @@ -98,6 +107,11 @@ class PyGameGUI: port: int, manager_ids: list[str], ): + pygame.init() + pygame.display.set_icon( + pygame.image.load(ROOT_DIR / "gui_2d_vis" / "images" / "fish3.png") + ) + self.game_screen: pygame.Surface = None self.FPS = 60 self.running = True @@ -117,72 +131,73 @@ class PyGameGUI: self.screen_margin = self.visualization_config["GameWindow"]["screen_margin"] self.min_width = self.visualization_config["GameWindow"]["min_width"] self.min_height = self.visualization_config["GameWindow"]["min_height"] - self.buttons_width = self.visualization_config["GameWindow"]["buttons_width"] self.buttons_height = self.visualization_config["GameWindow"]["buttons_height"] - self.order_bar_height = self.visualization_config["GameWindow"][ "order_bar_height" ] - - self.window_width = self.min_width - self.window_height = self.min_height - - self.main_window = pygame.display.set_mode( - (self.window_width, self.window_height) - ) - - # self.game_width, self.game_height = 0, 0 - + ( + self.window_width_fullscreen, + self.window_height_fullscreen, + ) = pygame.display.get_desktop_sizes()[0] + self.window_width_windowed = self.min_width + self.window_height_windowed = self.min_height + self.kitchen_width = 1 + self.kitchen_height = 1 + self.kitchen_aspect_ratio = 1 self.images_path = ROOT_DIR / "pygame_gui" / "images" + self.vis = Visualizer(self.visualization_config) + + self.fullscreen = False self.menu_state = MenuStates.Start self.manager: pygame_gui.UIManager - self.vis = Visualizer(self.visualization_config) - self.sub_processes = [] - def get_window_sizes(self, state: dict): - kitchen_width = state["kitchen"]["width"] - kitchen_height = state["kitchen"]["height"] - if self.visualization_config["GameWindow"]["WhatIsFixed"] == "window_width": - game_width = self.visualization_config["GameWindow"]["size"] - kitchen_aspect_ratio = kitchen_height / kitchen_width - game_height = int(game_width * kitchen_aspect_ratio) - grid_size = int(game_width / (kitchen_width - 0.1)) - - elif self.visualization_config["GameWindow"]["WhatIsFixed"] == "window_height": - game_height = self.visualization_config["GameWindow"]["size"] - kitchen_aspect_ratio = kitchen_width / kitchen_height - game_width = int(game_height * kitchen_aspect_ratio) - grid_size = int(game_width / (kitchen_width - 0.1)) - - elif self.visualization_config["GameWindow"]["WhatIsFixed"] == "grid": - grid_size = self.visualization_config["GameWindow"]["size"] - game_width, game_height = ( - kitchen_width * grid_size, - kitchen_height * grid_size, - ) + def get_window_sizes_from_state(self, state: dict): + self.kitchen_width = state["kitchen"]["width"] + self.kitchen_height = state["kitchen"]["height"] + self.kitchen_aspect_ratio = self.kitchen_height / self.kitchen_width + game_width = self.visualization_config["GameWindow"]["min_width"] - ( + 2 * self.screen_margin + ) + game_height = self.visualization_config["GameWindow"]["min_height"] - ( + 2 * self.screen_margin + ) + if self.kitchen_width > game_width: + self.game_height = game_width * self.kitchen_aspect_ratio + self.grid_size = game_width / self.kitchen_width else: - game_width, game_height = 0, 0 - grid_size = 0 + self.game_width = game_height / self.kitchen_aspect_ratio + self.grid_size = game_width / self.kitchen_width - window_width, window_height = ( - game_width + (2 * self.screen_margin), - game_height + (2 * self.screen_margin), # bar with orders - ) + self.window_width_windowed = self.min_width + self.window_height_windowed = self.min_height - window_width = max(window_width, self.min_width) - window_height = max(window_height, self.min_height) - return ( - int(window_width), - int(window_height), - int(game_width), - int(game_height), - grid_size, - ) + def recalc_game_size(self): + log.debug("Resizing game screen") + max_width = self.window_width - (2 * self.screen_margin) + max_height = self.window_height - (2 * self.screen_margin) + if max_width < max_height: + self.game_width = max_width + self.game_height = max_width * self.kitchen_aspect_ratio + self.grid_size = int(self.game_height / self.kitchen_height) + + else: + self.game_height = max_height + self.game_width = max_height / self.kitchen_aspect_ratio + self.grid_size = int(self.game_width / self.kitchen_width) + + self.game_width = max(self.game_width, 100) + self.game_height = max(self.game_height, 100) + self.grid_size = max(self.grid_size, 1) + + residual_x = self.game_width - (self.kitchen_width * self.grid_size) + residual_y = self.game_height - (self.kitchen_height * self.grid_size) + self.game_width -= residual_x + self.game_height -= residual_y def setup_player_keys(self, n=1, disjunct=False): if n: @@ -237,7 +252,7 @@ class PyGameGUI: current_player_name, ActionType.MOVEMENT, move_vec, - duration=1 / self.FPS, + duration=self.time_delta, ) self.send_action(action) @@ -267,7 +282,7 @@ class PyGameGUI: current_player_name, ActionType.INTERACT, InterActionData.STOP ) self.send_action(action) - if event.key == key_set.switch_key: + if event.key == key_set.switch_key and not CONNECT_WITH_STUDY_SERVER: if event.type == pygame.KEYDOWN: key_set.next_player() @@ -302,17 +317,27 @@ class PyGameGUI: ) self.quit_button.can_hover() + fullscreen_button_rect = pygame.Rect( + (0, 0), (self.buttons_width * 0.7, self.buttons_height) + ) + fullscreen_button_rect.topright = (-self.buttons_width, 0) + self.fullscreen_button = pygame_gui.elements.UIButton( + relative_rect=fullscreen_button_rect, + text="Fullscreen", + manager=self.manager, + object_id="#fullscreen_button", + anchors={"right": "right", "top": "top"}, + ) + self.fullscreen_button.can_hover() + + reset_button_rect = pygame.Rect((0, 0), (self.screen_margin * 0.75, 50)) + reset_button_rect.topright = (0, 2 * self.buttons_height) self.reset_button = pygame_gui.elements.UIButton( - relative_rect=pygame.Rect( - ( - self.window_width - (self.screen_margin * 3 // 4), - self.screen_margin, - ), - (self.screen_margin - (self.screen_margin // 4), 50), - ), + relative_rect=reset_button_rect, text="RESET", manager=self.manager, object_id="#reset_button", + anchors={"right": "right", "top": "top"}, ) self.reset_button.can_hover() @@ -357,8 +382,9 @@ class PyGameGUI: self.layout_file_paths = { str(p.name): p - for p in (ROOT_DIR / "game_content" / "layouts").glob("*.layout") + for p in [Path(f) for f in sorted((ROOT_DIR / "game_content" / "layouts").rglob("*.layout"))] } + assert len(self.layout_file_paths) != 0, "No layout files." dropdown_width, dropdown_height = 200, 40 self.layout_selection = pygame_gui.elements.UIDropDownMenu( @@ -581,8 +607,8 @@ class PyGameGUI: self.vis.draw_orders( screen=self.main_window, state=state, - grid_size=self.grid_size, - width=self.game_width, + grid_size=self.buttons_height, + width=self.window_width - self.buttons_width - (self.buttons_width * 0.7), height=self.game_height, screen_margin=self.screen_margin, config=self.visualization_config, @@ -627,18 +653,28 @@ class PyGameGUI: ( self.game_width, self.game_height, - ), + ) ) + + if self.fullscreen: + flags = pygame.FULLSCREEN + self.window_width = self.window_width_fullscreen + self.window_height = self.window_height_fullscreen + else: + flags = 0 + self.window_width = self.window_width_windowed + self.window_height = self.window_height_windowed + self.main_window = pygame.display.set_mode( ( self.window_width, self.window_height, - ) + ), + flags=flags, + display=0, ) def reset_window_size(self): - self.window_width = self.min_width - self.window_height = self.min_height self.game_width = 0 self.game_height = 0 self.set_window_size() @@ -701,41 +737,50 @@ class PyGameGUI: self.timer_label.set_text(f"Time remaining: {display_time}") def setup_environment(self): - environment_config_path = ROOT_DIR / "game_content" / "environment_config.yaml" - layout_path = self.layout_file_paths[self.layout_selection.selected_option] - item_info_path = ROOT_DIR / "game_content" / "item_info.yaml" - with open(item_info_path, "r") as file: - item_info = file.read() - with open(layout_path, "r") as file: - layout = file.read() - with open(environment_config_path, "r") as file: - environment_config = file.read() - - seed = 161616161616 - creation_json = CreateEnvironmentConfig( - manager_id=self.manager_id, - number_players=self.number_players, - environment_settings={"all_player_can_pause_game": False}, - item_info_config=item_info, - environment_config=environment_config, - layout_config=layout, - seed=seed, - ).model_dump(mode="json") - - # print(CreateEnvironmentConfig.model_validate_json(json_data=creation_json)) - env_info = requests.post( - f"{self.request_url}/manage/create_env/", - json=creation_json, - ) - if env_info.status_code == 403: - raise ValueError(f"Forbidden Request: {env_info.json()['detail']}") - env_info = env_info.json() - assert isinstance(env_info, dict), "Env info must be a dictionary" - self.current_env_id = env_info["env_id"] - self.player_info = env_info["player_info"] + if CONNECT_WITH_STUDY_SERVER: + self.player_info = requests.post( + f"http://localhost:8080/connect_to_game/{uuid.uuid4().hex}" + ).json() + self.key_sets[0].current_player = int(self.player_info["player_id"]) + self.player_info = {self.player_info["player_id"]: self.player_info} + else: + environment_config_path = ( + ROOT_DIR / "game_content" / "environment_config.yaml" + ) + layout_path = self.layout_file_paths[self.layout_selection.selected_option] + item_info_path = ROOT_DIR / "game_content" / "item_info.yaml" + with open(item_info_path, "r") as file: + item_info = file.read() + with open(layout_path, "r") as file: + layout = file.read() + with open(environment_config_path, "r") as file: + environment_config = file.read() + + seed = 161616161616 + creation_json = CreateEnvironmentConfig( + manager_id=self.manager_id, + number_players=self.number_players, + environment_settings={"all_player_can_pause_game": False}, + item_info_config=item_info, + environment_config=environment_config, + layout_config=layout, + seed=seed, + ).model_dump(mode="json") + + # print(CreateEnvironmentConfig.model_validate_json(json_data=creation_json)) + env_info = requests.post( + f"{self.request_url}/manage/create_env/", + json=creation_json, + ) + if env_info.status_code == 403: + raise ValueError(f"Forbidden Request: {env_info.json()['detail']}") + env_info = env_info.json() + assert isinstance(env_info, dict), "Env info must be a dictionary" + self.current_env_id = env_info["env_id"] + self.player_info = env_info["player_info"] state = None - for p, (player_id, player_info) in enumerate(env_info["player_info"].items()): + for p, (player_id, player_info) in enumerate(self.player_info.items()): if p < self.number_humans_to_be_added: websocket = connect(self.websocket_url + player_info["client_id"]) websocket.send( @@ -799,7 +844,7 @@ class PyGameGUI: if not self.number_humans_to_be_added: player_id = "0" - player_info = env_info["player_info"][player_id] + player_info = self.player_info[player_id] websocket = connect(self.websocket_url + player_info["client_id"]) websocket.send( json.dumps({"type": "ready", "player_hash": player_info["player_hash"]}) @@ -814,13 +859,7 @@ class PyGameGUI: ) state = json.loads(websocket.recv()) - ( - self.window_width, - self.window_height, - self.game_width, - self.game_height, - self.grid_size, - ) = self.get_window_sizes(state) + self.get_window_sizes_from_state(state) def start_button_press(self): self.menu_state = MenuStates.Game @@ -841,13 +880,11 @@ class PyGameGUI: self.setup_environment() + self.recalc_game_size() self.set_window_size() - self.init_ui_elements() log.debug("Pressed start button") - # self.api.set_sim(self.simulator) - def back_button_press(self): self.menu_state = MenuStates.Start self.reset_window_size() @@ -863,28 +900,29 @@ class PyGameGUI: def reset_button_press(self): # self.reset_gui_values() - - requests.post( - f"{self.request_url}/manage/stop_env", - json={ - "manager_id": self.manager_id, - "env_id": self.current_env_id, - "reason": "reset button pressed", - }, - ) + if not CONNECT_WITH_STUDY_SERVER: + requests.post( + f"{self.request_url}/manage/stop_env", + json={ + "manager_id": self.manager_id, + "env_id": self.current_env_id, + "reason": "reset button pressed", + }, + ) # self.websocket.send(json.dumps("reset_game")) # answer = self.websocket.recv() log.debug("Pressed reset button") def finished_button_press(self): - requests.post( - f"{self.request_url}/manage/stop_env/", - json={ - "manager_id": self.manager_id, - "env_id": self.current_env_id, - "reason": "finish button pressed", - }, - ) + if not CONNECT_WITH_STUDY_SERVER: + requests.post( + f"{self.request_url}/manage/stop_env/", + json={ + "manager_id": self.manager_id, + "env_id": self.current_env_id, + "reason": "finish button pressed", + }, + ) self.menu_state = MenuStates.End self.reset_window_size() log.debug("Pressed finished button") @@ -953,9 +991,9 @@ class PyGameGUI: json.dumps( { "type": "get_state", - "player_hash": self.player_info[str(self.key_sets[0].current_idx)][ - "player_hash" - ], + "player_hash": self.player_info[ + str(self.key_sets[0].current_player) + ]["player_hash"], } ) ) @@ -981,7 +1019,6 @@ class PyGameGUI: def start_pygame(self): """Starts pygame and the gui loop. Each frame the game state is visualized and keyboard inputs are read.""" log.debug(f"Starting pygame gui at {self.FPS} fps") - pygame.init() pygame.font.init() self.comic_sans = pygame.font.SysFont("Comic Sans MS", 30) @@ -990,6 +1027,7 @@ class PyGameGUI: clock = pygame.time.Clock() self.reset_window_size() + self.init_ui_elements() self.manage_button_visibility() @@ -999,13 +1037,22 @@ class PyGameGUI: self.running = True while self.running: try: - time_delta = clock.tick(self.FPS) / 1000.0 + self.time_delta = clock.tick(self.FPS) / 1000 + # print(clock.get_time()) for event in pygame.event.get(): if event.type == pygame.QUIT: self.running = False - # UI Buttons: + # elif event.type == pygame.VIDEORESIZE: + # # scrsize = event.size + # self.window_width_windowed = event.w + # self.window_height_windowed = event.h + # self.recalc_game_size() + # self.set_window_size() + # self.init_ui_elements() + # self.manage_button_visibility() + if event.type == pygame_gui.UI_BUTTON_PRESSED: match event.ui_element: case self.start_button: @@ -1015,6 +1062,7 @@ class PyGameGUI: ): continue self.start_button_press() + case self.back_button: self.back_button_press() self.disconnect_websockets() @@ -1022,9 +1070,11 @@ class PyGameGUI: case self.finished_button: self.finished_button_press() self.disconnect_websockets() + case self.quit_button: self.quit_button_press() self.disconnect_websockets() + case self.reset_button: self.reset_button_press() self.disconnect_websockets() @@ -1055,6 +1105,18 @@ class PyGameGUI: case self.xbox_controller_button: print("xbox_controller_button pressed.") + case self.fullscreen_button: + self.fullscreen = not self.fullscreen + if self.fullscreen: + self.window_width = self.window_width_fullscreen + self.window_height = self.window_height_fullscreen + else: + self.window_width = self.window_width_windowed + self.window_height = self.window_height_windowed + self.recalc_game_size() + self.set_window_size() + self.init_ui_elements() + self.update_selection_elements() self.manage_button_visibility() @@ -1102,7 +1164,7 @@ class PyGameGUI: case MenuStates.End: self.update_conclusion_label(state) - self.manager.update(time_delta) + self.manager.update(self.time_delta) pygame.display.flip() except (KeyboardInterrupt, SystemExit): @@ -1114,6 +1176,7 @@ class PyGameGUI: def main(url: str, port: int, manager_ids: list[str]): + setup_logging() gui = PyGameGUI( url=url, port=port, @@ -1133,4 +1196,4 @@ 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.url, args.port, args.manager_ids) diff --git a/overcooked_simulator/gui_2d_vis/visualization.yaml b/overcooked_simulator/gui_2d_vis/visualization.yaml index 428c76c01bd5567c2b420679671282bda7d74a4c..a0d7989c1c1bf9c2a96fcc28e1baf09bcd96e55e 100644 --- a/overcooked_simulator/gui_2d_vis/visualization.yaml +++ b/overcooked_simulator/gui_2d_vis/visualization.yaml @@ -1,10 +1,8 @@ # colors: https://www.webucator.com/article/python-color-constants-module/ GameWindow: - WhatIsFixed: grid # grid or window_width or window_height - size: 50 screen_margin: 100 - min_width: 700 + min_width: 900 min_height: 600 buttons_width: 180 buttons_height: 60 diff --git a/overcooked_simulator/overcooked_environment.py b/overcooked_simulator/overcooked_environment.py index a119d34c7f3d17060b91552b94ca07f5bbcb58d6..d157ce604b478f28afe1c827f4a7e71b30aa3a49 100644 --- a/overcooked_simulator/overcooked_environment.py +++ b/overcooked_simulator/overcooked_environment.py @@ -168,6 +168,9 @@ class Environment: self.player_view_angle = self.environment_config["player_config"][ "view_angle" ] + self.player_view_range = self.environment_config["player_config"][ + "view_range" + ] self.extra_setup_functions() @@ -237,8 +240,6 @@ class Environment: ) = self.parse_layout_file() self.hook(LAYOUT_FILE_PARSED) - self.counter_positions = np.array([c.pos for c in self.counters]) - self.world_borders = np.array( [[-0.5, self.kitchen_width - 0.5], [-0.5, self.kitchen_height - 0.5]], dtype=float, @@ -248,6 +249,9 @@ class Environment: "player_speed_units_per_seconds" ] self.player_radius = self.environment_config["player_config"]["radius"] + self.player_interaction_range = self.environment_config["player_config"][ + "interaction_range" + ] progress_counter_classes = list( filter( @@ -267,6 +271,8 @@ class Environment: ) """Counters that needs to be called in the step function via the `progress` method.""" + self.counter_positions = np.array([c.pos for c in self.counters]) + self.order_and_score.create_init_orders(self.env_time) self.start_time = self.env_time """The relative env time when it started.""" @@ -290,6 +296,27 @@ class Environment: env_start_time_worldtime=datetime.now(), ) + def overwrite_counters(self, counters): + self.counters = counters + self.counter_positions = np.array([c.pos for c in self.counters]) + + progress_counter_classes = list( + filter( + lambda cl: hasattr(cl, "progress"), + dict( + inspect.getmembers( + sys.modules["overcooked_simulator.counters"], inspect.isclass + ) + ).values(), + ) + ) + self.progressing_counters = list( + filter( + lambda c: c.__class__ in progress_counter_classes, + self.counters, + ) + ) + @property def game_ended(self) -> bool: """Whether the game is over or not based on the calculated `Environment.env_time_end`""" @@ -406,11 +433,13 @@ class Environment: lines = list(filter(lambda l: l != "", lines)) for line in lines: - line = line.replace("\n", "").replace(" ", "") # remove newline char + line = line.replace(" ", "") + if not line or line.startswith(";"): + break current_x: float = starting_at grid_line = [] for character in line: - character = character.capitalize() + # character = character.capitalize() pos = np.array([current_x, current_y]) assert self.counter_factory.can_map( @@ -435,7 +464,7 @@ class Environment: current_y += 1 self.kitchen_width: float = len(lines[0]) + starting_at - self.kitchen_height = len(lines) + starting_at + self.kitchen_height = current_y self.determine_counter_orientations( counters, grid, np.array([self.kitchen_width / 2, self.kitchen_height / 2]) @@ -481,7 +510,7 @@ class Environment: c.set_orientation(nearest_vec) elif grid_idx[0] == 0: - if grid_idx[1] == 0: + if grid_idx[1] == 0 or fst_counter_in_row is None: # counter top left c.set_orientation(np.array([1, 0])) else: @@ -666,8 +695,20 @@ class Environment: for idx, p in enumerate(self.players.values()): if not (new_positions[idx] == player_positions[idx]).all(): - p.turn(player_movement_vectors[idx]) - p.move_abs(new_positions[idx]) + p.pos = new_positions[idx] + p.perform_interact_stop() + + p.turn(player_movement_vectors[idx]) + + facing_distances = np.linalg.norm( + p.facing_point - self.counter_positions, axis=1 + ) + closest_counter = self.counters[facing_distances.argmin()] + p.current_nearest_counter = ( + closest_counter + if facing_distances.min() <= self.player_interaction_range + else None + ) def add_player(self, player_name: str, pos: npt.NDArray = None): """Add a player to the environment. @@ -783,6 +824,7 @@ class Environment: "position": self.players[player_id].pos.tolist(), "angle": self.player_view_angle, "counter_mask": None, + "range": self.player_view_range, } if self.player_view_restricted else None, diff --git a/overcooked_simulator/player.py b/overcooked_simulator/player.py index c367561e98862425ecc1590087ef7deb982e608e..638bc4cd5c0d537aa5823af96543a008b43c3300 100644 --- a/overcooked_simulator/player.py +++ b/overcooked_simulator/player.py @@ -37,6 +37,9 @@ class PlayerConfig: """Whether or not the player can see the entire map at once or just a view frustrum.""" view_angle: int | None = None """Angle of the players view if restricted.""" + view_range: float | None = None + """Range of the players view if restricted. In grid units.""" + class Player: """Class representing a player in the game environment. A player consists of a name, their position and what @@ -88,19 +91,7 @@ class Player: function of the environment""" self.current_movement = move_vector self.movement_until = move_until - - def move(self, movement: npt.NDArray[float]): - """Moves the player position by the given movement vector. - A unit direction vector multiplied by move_dist is added to the player position. - - Args: - movement: 2D-Vector of length 1 - """ - if self.interacting and np.any(movement): - self.perform_interact_stop() - self.pos += movement - if np.linalg.norm(movement) != 0: - self.turn(movement) + self.perform_interact_stop() def move_abs(self, new_pos: npt.NDArray[float]): """Overwrites the player location by the new_pos 2d-vector. Absolute movement. @@ -165,7 +156,7 @@ class Player: self.holding.combine(returned_by_counter) log.debug( - f"Self: {self.holding}, {counter.__class__.__name__}: {counter.occupied_by}" + f"Self: {self.holding.__class__.__name__}: {self.holding}, {counter.__class__.__name__}: {counter.occupied_by}" ) # if isinstance(self.holding, Plate): # log.debug(self.holding.clean) diff --git a/overcooked_simulator/state_representation.py b/overcooked_simulator/state_representation.py index c989aecca869846a993ce007bdff68072205ca76..fe587ca6d41c40514d0fe3d8e862c09575a6c09b 100644 --- a/overcooked_simulator/state_representation.py +++ b/overcooked_simulator/state_representation.py @@ -72,6 +72,7 @@ class ViewRestriction(BaseModel): position: list[float] angle: int # degrees counter_mask: None | list[bool] + range: float | None class InfoMsgLevel(Enum): diff --git a/overcooked_simulator/utils.py b/overcooked_simulator/utils.py index b78d44af869d9a53ec17f5f2cd8eda191e0b4e17..aa91a0a99190c177f28dec0fc036e2f001b83b8a 100644 --- a/overcooked_simulator/utils.py +++ b/overcooked_simulator/utils.py @@ -113,21 +113,21 @@ def setup_logging(enable_websocket_logging=False): logging.getLogger("websockets.client").setLevel(logging.ERROR) -def url_and_port_arguments(parser): +def url_and_port_arguments(parser, server_name="game server", default_port=8000): parser.add_argument( "-url", "--url", "--host", type=str, default="localhost", - help="Overcooked game server host url.", + help=f"Overcooked {server_name} host url.", ) parser.add_argument( "-p", "--port", type=int, - default=8000, - help="Port number for the game engine server", + default=default_port, + help=f"Port number for the {server_name}", ) @@ -168,3 +168,13 @@ class NumpyAndDataclassEncoder(json.JSONEncoder): # return getattr(obj, "__name__", "Unknown") return json.JSONEncoder.default(self, obj) + + +def create_layout(w, h): + for y in range(h): + for x in range(w): + if x == 0 or y == 0 or x == w - 1 or y == h - 1: + print("#", end="") + else: + print("_", end="") + print("") diff --git a/tests/test_start.py b/tests/test_start.py index 8efe8da9ecf1c67974f88a18b1bcdb0b1cd3786a..8531f9d81dc52cb4327c98e641406ad2d45bdec2 100644 --- a/tests/test_start.py +++ b/tests/test_start.py @@ -124,7 +124,7 @@ def test_player_reach(env_config, layout_empty_config, item_info): counter_pos = np.array([2, 2]) counter = Counter(pos=counter_pos, hook=Hooks(env)) - env.counters = [counter] + env.overwrite_counters([counter]) env.add_player("1", np.array([2, 4])) env.player_movement_speed = 1 player = env.players["1"] @@ -144,7 +144,7 @@ def test_pickup(env_config, layout_config, item_info): counter_pos = np.array([2, 2]) counter = Counter(pos=counter_pos, hook=Hooks(env)) counter.occupied_by = Item(name="Tomato", item_info=None) - env.counters = [counter] + env.overwrite_counters([counter]) env.add_player("1", np.array([2, 3])) player = env.players["1"]