Skip to content
Snippets Groups Projects
Commit f2e43c97 authored by Fabian Heinrich's avatar Fabian Heinrich
Browse files

Merge branch 'main' into 82-burnt-meals-and-ingredients

# Conflicts:
#	overcooked_simulator/gui_2d_vis/drawing.py
parents 404e8c6f 8fafd4ee
No related branches found
No related tags found
1 merge request!47Resolve "Burnt Meals and Ingredients"
...@@ -116,12 +116,20 @@ class Counter: ...@@ -116,12 +116,20 @@ class Counter:
"""The effects that currently affect the usage of the counter.""" """The effects that currently affect the usage of the counter."""
self.hook = hook self.hook = hook
"""Reference to the hook manager.""" """Reference to the hook manager."""
self.orientation: npt.NDArray[float] = np.array([0, 1], dtype=float)
"""In what direction the counter is facing."""
@property @property
def occupied(self) -> bool: def occupied(self) -> bool:
"""Is something on top of the counter.""" """Is something on top of the counter."""
return self.occupied_by is not None return self.occupied_by is not None
def set_orientation(self, orientation: npt.NDArray[float]) -> None:
if not np.isclose(np.linalg.norm(orientation), 1):
self.orientation = orientation / np.linalg.norm(orientation)
else:
self.orientation = orientation
def pick_up(self, on_hands: bool = True) -> Item | None: def pick_up(self, on_hands: bool = True) -> Item | None:
"""Gets called upon a player performing the pickup action. If the counter can give something to """Gets called upon a player performing the pickup action. If the counter can give something to
the player, it does so. In the standard counter this is when an item is on the counter. the player, it does so. In the standard counter this is when an item is on the counter.
...@@ -236,6 +244,7 @@ class Counter: ...@@ -236,6 +244,7 @@ class Counter:
"category": COUNTER_CATEGORY, "category": COUNTER_CATEGORY,
"type": self.__class__.__name__, "type": self.__class__.__name__,
"pos": self.pos.tolist(), "pos": self.pos.tolist(),
"orientation": self.orientation.tolist(),
"occupied_by": None "occupied_by": None
if self.occupied_by is None if self.occupied_by is None
else ( else (
......
##S+#
S___#
+___S
#___+
#+SP#
\ No newline at end of file
import argparse import argparse
import colorsys import colorsys
import json import json
import math
from datetime import datetime, timedelta from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
...@@ -25,6 +24,12 @@ SHOW_INTERACTION_RANGE = False ...@@ -25,6 +24,12 @@ SHOW_INTERACTION_RANGE = False
SHOW_COUNTER_CENTERS = False SHOW_COUNTER_CENTERS = False
def calc_angle(vec_a: list[float], vec_b: list[float]) -> float:
a = pygame.math.Vector2(vec_a)
b = pygame.math.Vector2(vec_b)
return a.angle_to(b)
def grayscale(img): def grayscale(img):
arr = pygame.surfarray.pixels3d(img) arr = pygame.surfarray.pixels3d(img)
mean_arr = np.dot(arr[:, :, :], [0.216, 0.587, 0.144]) mean_arr = np.dot(arr[:, :, :], [0.216, 0.587, 0.144])
...@@ -60,6 +65,14 @@ def create_polygon(n, length): ...@@ -60,6 +65,14 @@ def create_polygon(n, length):
class Visualizer: class Visualizer:
"""Class for visualizing the game state retrieved from the gameserver.
2D game screen is drawn with pygame shapes and images.
Args:
config: Visualization configuration (loaded from yaml file) given as a dict.
"""
def __init__(self, config): def __init__(self, config):
self.image_cache_dict = {} self.image_cache_dict = {}
self.player_colors = [] self.player_colors = []
...@@ -69,6 +82,12 @@ class Visualizer: ...@@ -69,6 +82,12 @@ class Visualizer:
self.fire_time_steps = 8 self.fire_time_steps = 8
def create_player_colors(self, n) -> None: def create_player_colors(self, n) -> None:
"""Create different colors for the players. The color hues are sampled uniformly in HSV-Space,
then the corresponding colors from the defined colors list are looked up.
Args:
n: Number of players to create colors for.
"""
hue_values = np.linspace(0, 1, n + 1) hue_values = np.linspace(0, 1, n + 1)
colors_vec = np.array([col for col in colors.values()]) colors_vec = np.array([col for col in colors.values()])
...@@ -86,10 +105,18 @@ class Visualizer: ...@@ -86,10 +105,18 @@ class Visualizer:
def draw_gamescreen( def draw_gamescreen(
self, self,
screen, screen: pygame.Surface,
state, state: dict,
grid_size, grid_size: int,
): ):
"""Draws the game state on the given surface.
Args:
screen: The pygame surface to draw the game on.
state: The gamestate retrieved from the environment.
grid_size: The gridsize to base every object size in the game on.
"""
width = int(np.ceil(state["kitchen"]["width"] * grid_size)) width = int(np.ceil(state["kitchen"]["width"] * grid_size))
height = int(np.ceil(state["kitchen"]["height"] * grid_size)) height = int(np.ceil(state["kitchen"]["height"] * grid_size))
self.draw_background( self.draw_background(
...@@ -110,8 +137,18 @@ class Visualizer: ...@@ -110,8 +137,18 @@ class Visualizer:
grid_size, grid_size,
) )
def draw_background(self, surface, width, height, grid_size): def draw_background(
"""Visualizes a game background.""" self, surface: pygame.Surface, width: int, height: int, grid_size: int
):
"""Visualizes a game background.
Args:
surface: The pygame surface to draw the background on.
width: The kitchen width.
height: The kitchen height.
grid_size: The gridsize to base the background shapes on.
"""
block_size = grid_size // 2 # Set the size of the grid block block_size = grid_size // 2 # Set the size of the grid block
surface.fill(colors[self.config["Kitchen"]["ground_tiles_color"]]) surface.fill(colors[self.config["Kitchen"]["ground_tiles_color"]])
for x in range(0, width, block_size): for x in range(0, width, block_size):
...@@ -133,6 +170,15 @@ class Visualizer: ...@@ -133,6 +170,15 @@ class Visualizer:
rot_angle=0, rot_angle=0,
burnt: bool = False, burnt: bool = False,
): ):
"""Draws an image on the given screen.
Args:
screen: The pygame surface to draw the image on.
img_path: The path to the image file, given relative to the gui_2d_vis directory.
size: The size of the image, given in pixels.
pos: The position of the center of the image, given in pixels.
rot_angle: Optional angle to rotate the image around.
"""
cache_entry = f"{img_path}" cache_entry = f"{img_path}"
if cache_entry + ("-burnt" if burnt else "") in self.image_cache_dict: if cache_entry + ("-burnt" if burnt else "") in self.image_cache_dict:
image = self.image_cache_dict[cache_entry + ("-burnt" if burnt else "")] image = self.image_cache_dict[cache_entry + ("-burnt" if burnt else "")]
...@@ -168,14 +214,18 @@ class Visualizer: ...@@ -168,14 +214,18 @@ class Visualizer:
): ):
"""Visualizes the players as circles with a triangle for the facing direction. """Visualizes the players as circles with a triangle for the facing direction.
If the player holds something in their hands, it is displayed If the player holds something in their hands, it is displayed
Args: state: The game state returned by the environment.
Args:
screen: The pygame surface to draw the players on.
players: The state of the players returned by the environment.
grid_size: The gridsize to rescale the drawn players to.
""" """
for p_idx, player_dict in enumerate(players): for p_idx, player_dict in enumerate(players):
player_dict: PlayerState player_dict: PlayerState
pos = np.array(player_dict["pos"]) * grid_size pos = np.array(player_dict["pos"]) * grid_size
pos += grid_size / 2 # correct for grid offset pos += grid_size / 2 # correct for grid offset
facing = np.array(player_dict["facing_direction"]) facing = np.array(player_dict["facing_direction"], dtype=float)
if USE_PLAYER_COOK_SPRITES: if USE_PLAYER_COOK_SPRITES:
pygame.draw.circle( pygame.draw.circle(
...@@ -186,8 +236,7 @@ class Visualizer: ...@@ -186,8 +236,7 @@ class Visualizer:
) )
img_path = self.config["Cook"]["parts"][0]["path"] img_path = self.config["Cook"]["parts"][0]["path"]
rel_x, rel_y = facing angle = calc_angle(facing.tolist(), [0, 1])
angle = -np.rad2deg(math.atan2(rel_y, rel_x)) + 90
size = self.config["Cook"]["parts"][0]["size"] * grid_size size = self.config["Cook"]["parts"][0]["size"] * grid_size
self.draw_image(screen, img_path, size, pos, angle) self.draw_image(screen, img_path, size, pos, angle)
...@@ -261,6 +310,7 @@ class Visualizer: ...@@ -261,6 +310,7 @@ class Visualizer:
parts: list[dict[str]], parts: list[dict[str]],
scale: float = 1.0, scale: float = 1.0,
burnt: bool = False, burnt: bool = False,
orientation: list[float] | None = None,
): ):
"""Draws an item, based on its visual parts specified in the visualization config. """Draws an item, based on its visual parts specified in the visualization config.
...@@ -270,24 +320,42 @@ class Visualizer: ...@@ -270,24 +320,42 @@ class Visualizer:
pos: Where to draw the item parts. pos: Where to draw the item parts.
parts: The visual parts to draw. parts: The visual parts to draw.
scale: Rescale the item by this factor. scale: Rescale the item by this factor.
orientation: Rotate the item to face this direction.
""" """
for part in parts: for part in parts:
part_type = part["type"] part_type = part["type"]
angle, angle_offset = 0, 0
draw_pos = pos.copy() draw_pos = pos.copy()
if "center_offset" in part:
draw_pos += np.array(part["center_offset"]) * grid_size if orientation is not None:
angle_offset = calc_angle(orientation, [0, 1])
if "rotate_image" in part.keys():
if part["rotate_image"]:
angle = calc_angle(orientation, [0, 1])
else:
angle = angle_offset
match part_type: match part_type:
case "image": case "image":
if "center_offset" in part:
d = pygame.math.Vector2(part["center_offset"]) * grid_size
d.rotate_ip(angle_offset)
draw_pos += np.array(d)
self.draw_image( self.draw_image(
screen, screen,
part["path"], part["path"],
part["size"] * scale * grid_size, part["size"] * scale * grid_size,
draw_pos, draw_pos,
burnt=burnt, burnt=burnt,
rot_angle=angle,
) )
case "rect": case "rect":
if "center_offset" in part:
d = pygame.math.Vector2(part["center_offset"]) * grid_size
d.rotate_ip(angle_offset)
draw_pos += np.array(d)
height = part["height"] * grid_size height = part["height"] * grid_size
width = part["width"] * grid_size width = part["width"] * grid_size
color = part["color"] color = part["color"]
...@@ -298,9 +366,15 @@ class Visualizer: ...@@ -298,9 +366,15 @@ class Visualizer:
width, width,
) )
pygame.draw.rect(screen, color, rect) pygame.draw.rect(screen, color, rect)
case "circle": case "circle":
if "center_offset" in part:
d = pygame.math.Vector2(part["center_offset"]) * grid_size
d.rotate_ip(-angle_offset)
draw_pos += np.array(d)
radius = part["radius"] * grid_size radius = part["radius"] * grid_size
color = colors[part["color"]] color = colors[part["color"]]
pygame.draw.circle(screen, color, draw_pos, radius) pygame.draw.circle(screen, color, draw_pos, radius)
def draw_item( def draw_item(
...@@ -349,7 +423,7 @@ class Visualizer: ...@@ -349,7 +423,7 @@ class Visualizer:
grid_size=grid_size, grid_size=grid_size,
burnt=item["type"].startswith("Burnt"), burnt=item["type"].startswith("Burnt"),
) )
#
if "progress_percentage" in item and item["progress_percentage"] > 0.0: if "progress_percentage" in item and item["progress_percentage"] > 0.0:
if item["inverse_progress"]: if item["inverse_progress"]:
percentage = 1 - item["progress_percentage"] percentage = 1 - item["progress_percentage"]
...@@ -408,7 +482,14 @@ class Visualizer: ...@@ -408,7 +482,14 @@ class Visualizer:
grid_size: float, grid_size: float,
attention: bool = False, attention: bool = False,
): ):
"""Visualize progress of progressing item as a green bar under the item.""" """Visualize progress of progressing item as a green bar under the item.
Args:
screen: The pygame surface to draw the progress bar on.
pos: The center position of a tile to draw the progress bar under.
percent: Progressed percent of the progress bar.
grid_size: Scaling of the progress bar given in pixels.
"""
bar_pos = pos - (grid_size / 2) bar_pos = pos - (grid_size / 2)
bar_height = grid_size * 0.2 bar_height = grid_size * 0.2
...@@ -427,16 +508,31 @@ class Visualizer: ...@@ -427,16 +508,31 @@ class Visualizer:
"""Visualization of a counter at its position. If it is occupied by an item, it is also shown. """Visualization of a counter at its position. If it is occupied by an item, it is also shown.
The visual composition of the counter is read in from visualization.yaml file, where it is specified as The visual composition of the counter is read in from visualization.yaml file, where it is specified as
different parts to be drawn. different parts to be drawn.
Args: counter: The counter to visualize. Args:
screen: The pygame surface to draw the counter on.
counter_dict: The counter to visualize, given as a dict from the game state.
grid_size: Scaling of the counter given in pixels.
""" """
pos = np.array(counter_dict["pos"], dtype=float) * grid_size pos = np.array(counter_dict["pos"], dtype=float) * grid_size
counter_type = counter_dict["type"] counter_type = counter_dict["type"]
pos += grid_size // 2 # correct for grid offset pos += grid_size // 2 # correct for grid offset
self.draw_thing(screen, pos, grid_size, self.config["Counter"]["parts"]) self.draw_thing(
screen,
pos,
grid_size,
self.config["Counter"]["parts"],
orientation=counter_dict["orientation"],
)
if counter_type in self.config: if counter_type in self.config:
self.draw_thing(screen, pos, grid_size, self.config[counter_type]["parts"]) self.draw_thing(
screen,
pos,
grid_size,
self.config[counter_type]["parts"],
orientation=counter_dict["orientation"],
)
else: else:
if counter_type in self.config: if counter_type in self.config:
parts = self.config[counter_type]["parts"] parts = self.config[counter_type]["parts"]
...@@ -449,6 +545,7 @@ class Visualizer: ...@@ -449,6 +545,7 @@ class Visualizer:
pos=pos, pos=pos,
parts=parts, parts=parts,
grid_size=grid_size, grid_size=grid_size,
orientation=counter_dict["orientation"],
) )
def draw_counter_occupier( def draw_counter_occupier(
...@@ -457,7 +554,16 @@ class Visualizer: ...@@ -457,7 +554,16 @@ class Visualizer:
occupied_by: dict | list, occupied_by: dict | list,
grid_size, grid_size,
pos: npt.NDArray[float], pos: npt.NDArray[float],
item_scale: float,
): ):
"""Visualization of a thing lying on a counter.
Args:
screen: The pygame surface to draw the item on the counter on.
occupied_by: The thing that occupies the counter.
grid_size: Scaling of the object given in pixels.
pos: The position of the counter which the thing lies on.
item_scale: Relative scaling of the item.
"""
# Multiple plates on plate return: # Multiple plates on plate return:
if isinstance(occupied_by, list): if isinstance(occupied_by, list):
for i, o in enumerate(occupied_by): for i, o in enumerate(occupied_by):
...@@ -466,6 +572,7 @@ class Visualizer: ...@@ -466,6 +572,7 @@ class Visualizer:
pos=np.abs([pos[0], pos[1] - (i * 3)]), pos=np.abs([pos[0], pos[1] - (i * 3)]),
grid_size=grid_size, grid_size=grid_size,
item=o, item=o,
scale=item_scale,
) )
# All other items: # All other items:
else: else:
...@@ -474,12 +581,16 @@ class Visualizer: ...@@ -474,12 +581,16 @@ class Visualizer:
grid_size=grid_size, grid_size=grid_size,
item=occupied_by, item=occupied_by,
screen=screen, screen=screen,
scale=item_scale,
) )
def draw_counters(self, screen: pygame, counters, grid_size): def draw_counters(self, screen: pygame, counters: dict, grid_size: int):
"""Visualizes the counters in the environment. """Visualizes the counters in the environment.
Args: state: The game state returned by the environment. Args:
screen: The pygame surface to draw the counters on.
counters: The counter state returned by the environment.
grid_size: Scaling of the object given in pixels.
""" """
global FIRE_STATE global FIRE_STATE
...@@ -488,11 +599,32 @@ class Visualizer: ...@@ -488,11 +599,32 @@ class Visualizer:
for counter in counters: for counter in counters:
if counter["occupied_by"]: if counter["occupied_by"]:
item_pos = np.array(counter["pos"])
item_scale = 1.0
counter_type = counter["type"]
if counter_type.endswith("Dispenser") and "Plate" not in counter_type:
if "item_offset" in self.config["Dispenser"].keys():
offset_vec = pygame.math.Vector2(
self.config["Dispenser"]["item_offset"]
)
offset_vec.rotate_ip(
offset_vec.angle_to(
pygame.math.Vector2(counter["orientation"])
)
+ 180
)
item_pos += offset_vec
if "item_scale" in self.config["Dispenser"].keys():
item_scale = self.config["Dispenser"]["item_scale"]
self.draw_counter_occupier( self.draw_counter_occupier(
screen, screen=screen,
counter["occupied_by"], occupied_by=counter["occupied_by"],
grid_size, grid_size=grid_size,
np.array(counter["pos"]) * grid_size + (grid_size / 2), pos=item_pos * grid_size + (grid_size / 2),
item_scale=item_scale,
) )
if counter["active_effects"]: if counter["active_effects"]:
for effect in counter["active_effects"]: for effect in counter["active_effects"]:
...@@ -502,19 +634,52 @@ class Visualizer: ...@@ -502,19 +634,52 @@ class Visualizer:
screen=screen, screen=screen,
item=effect, item=effect,
) )
if SHOW_COUNTER_CENTERS: if SHOW_COUNTER_CENTERS:
pygame.draw.circle( pos = np.array(counter["pos"]) * grid_size
pygame.draw.circle(screen, colors["green1"], pos, 3)
pygame.draw.circle(screen, colors["green1"], pos, 3)
facing = np.array(counter["orientation"])
pygame.draw.polygon(
screen, screen,
colors["green1"], colors["red"],
np.array(counter["pos"]) * grid_size + (grid_size / 2), (
3, (
pos[0] + (facing[1] * 0.1 * grid_size),
pos[1] - (facing[0] * 0.1 * grid_size),
),
(
pos[0] - (facing[1] * 0.1 * grid_size),
pos[1] + (facing[0] * 0.1 * grid_size),
),
pos + (facing * 0.5 * grid_size),
),
) )
self.fire_state = (self.fire_state + 1) % (3 * self.fire_time_steps) self.fire_state = (self.fire_state + 1) % (3 * self.fire_time_steps)
def draw_orders( def draw_orders(
self, screen, state, grid_size, width, height, screen_margin, config self,
screen: pygame.surface,
state: dict,
grid_size: int,
width: int,
height: int,
screen_margin: int,
config: dict,
): ):
"""Visualization of the current orders.
Args:
screen: pygame surface to draw the orders on, probably not the game screen itself.
state: The game state returned by the environment.
grid_size: Scaling of the drawn orders, given in pixels.
width: Width of the pygame window
height: Height of the pygame window.
screen_margin: Size of the space around the game screen, for buttons, ... .
config: Visualization configuration (loaded from yaml file) given as a dict.
"""
orders_width = width - 100 orders_width = width - 100
orders_height = screen_margin orders_height = screen_margin
order_screen = pygame.Surface( order_screen = pygame.Surface(
...@@ -582,6 +747,14 @@ class Visualizer: ...@@ -582,6 +747,14 @@ class Visualizer:
def save_state_image( def save_state_image(
self, grid_size: int, state: dict, filename: str | Path self, grid_size: int, state: dict, filename: str | Path
) -> None: ) -> None:
"""Saves a screenshot of the visualization of the given state.
Args:
grid_size: Scaling of the world elements given in pixels.
state: Game state returned by the environment.
filename: Filename to save the screenshot to.
"""
width = int(np.ceil(state["kitchen"]["width"] * grid_size)) width = int(np.ceil(state["kitchen"]["width"] * grid_size))
height = int(np.ceil(state["kitchen"]["height"] * grid_size)) height = int(np.ceil(state["kitchen"]["height"] * grid_size))
...@@ -593,6 +766,15 @@ class Visualizer: ...@@ -593,6 +766,15 @@ class Visualizer:
def save_screenshot(state: dict, config: dict, filename: str | Path) -> None: def save_screenshot(state: dict, config: dict, filename: str | Path) -> None:
"""Standalone function to save a screenshot. Creates a visualizer from the config and visualizes
the game state, saves it to the given filename.
Args:
state: The gamestate to visualize.
config: Visualization config for the visualizer.
filename: Filename to save the image to.
"""
viz = Visualizer(config) viz = Visualizer(config)
viz.create_player_colors(len(state["players"])) viz.create_player_colors(len(state["players"]))
pygame.init() pygame.init()
......
overcooked_simulator/gui_2d_vis/images/arrow_right.png

347 KiB | W: | H:

overcooked_simulator/gui_2d_vis/images/arrow_right.png

469 KiB | W: | H:

overcooked_simulator/gui_2d_vis/images/arrow_right.png
overcooked_simulator/gui_2d_vis/images/arrow_right.png
overcooked_simulator/gui_2d_vis/images/arrow_right.png
overcooked_simulator/gui_2d_vis/images/arrow_right.png
  • 2-up
  • Swipe
  • Onion skin
overcooked_simulator/gui_2d_vis/images/counter2.png

46.9 KiB

overcooked_simulator/gui_2d_vis/images/counter4.png

10.4 KiB

overcooked_simulator/gui_2d_vis/images/counter5.png

8.61 KiB

...@@ -22,84 +22,97 @@ Kitchen: ...@@ -22,84 +22,97 @@ Kitchen:
Counter: Counter:
parts: parts:
- type: rect # - type: rect
height: 1 # height: 1
width: 1 # width: 1
color: whitesmoke # color: whitesmoke
- type: image
path: images/counter5.png
size: 1
CuttingBoard: CuttingBoard:
parts: parts:
- type: image - type: image
path: images/cutting_board_large.png path: images/cutting_board_large.png
size: 0.9 size: 0.75
center_offset: [ 0, 0.05 ]
PlateDispenser: PlateDispenser:
parts: parts: [ ]
- type: rect # - type: rect
height: 0.95 # height: 0.95
width: 0.95 # width: 0.95
color: cadetblue1 # color: cadetblue1
Trashcan: Trashcan:
parts: parts:
- type: image - type: image
path: images/trash3.png path: images/trash3.png
size: 0.9 size: 0.88
center_offset: [ 0, 0 ] center_offset: [ 0, -0.05 ]
TomatoDispenser:
parts:
- color: orangered1
type: rect
height: 0.8
width: 0.8
LettuceDispenser:
parts:
- color: palegreen3
type: rect
height: 0.8
width: 0.8
OnionDispenser: #TomatoDispenser:
parts: # parts:
- color: deeppink3 # - color: orangered1
type: rect # type: rect
height: 0.8 # height: 0.8
width: 0.8 # width: 0.8
#
#LettuceDispenser:
# parts:
# - color: palegreen3
# type: rect
# height: 0.8
# width: 0.8
#
#OnionDispenser:
# parts:
# - color: deeppink3
# type: rect
# height: 0.8
# width: 0.8
#
#MeatDispenser:
# parts:
# - color: indianred1
# type: rect
# height: 0.8
# width: 0.8
#
#BunDispenser:
# parts:
# - color: sandybrown
# type: rect
# height: 0.8
# width: 0.8
MeatDispenser: Dispenser:
parts: parts:
- color: indianred1 - type: circle
type: rect color: black
height: 0.8 radius: 0.35
width: 0.8 center_offset: [ 0, -0.05 ]
- type: circle
color: gray83
radius: 0.33
center_offset: [ 0, -0.05 ]
BunDispenser:
parts:
- color: sandybrown
type: rect
height: 0.8
width: 0.8
Dispenser: item_offset: [ 0, -0.05 ]
parts: item_scale: 0.9
- color: gray83
type: rect
height: 0.8
width: 0.8
ServingWindow: ServingWindow:
parts: parts:
- type: image # - type: image
path: images/arrow_right.png # path: images/arrow_right.png
size: 1 # size: 1
center_offset: [ 0, 0 ] # center_offset: [ 0, 0 ]
- type: image - type: image
path: images/bell_gold.png path: images/bell_gold.png
size: 0.5 size: 0.5
center_offset: [ 0.1, -0.4 ] center_offset: [ -0.4, 0.1 ]
rotate_image: False
Stove: Stove:
parts: parts:
...@@ -115,15 +128,15 @@ Sink: ...@@ -115,15 +128,15 @@ Sink:
parts: parts:
- type: image - type: image
path: images/sink1.png path: images/sink1.png
size: 1 size: 0.85
center_offset: [ 0, -0.05 ] center_offset: [ 0, -0.12 ]
SinkAddon: SinkAddon:
parts: parts:
- type: image - type: image
path: images/drip2.png path: images/drip2.png
size: 0.85 size: 0.75
center_offset: [ 0, 0.03 ] center_offset: [ 0, -0.05 ]
# Tools # Tools
Extinguisher: Extinguisher:
...@@ -309,6 +322,7 @@ Oven: ...@@ -309,6 +322,7 @@ Oven:
color: black color: black
height: 0.8 height: 0.8
width: 0.3 width: 0.3
center_offset: [ 0, -0.1 ]
Basket: Basket:
parts: parts:
......
...@@ -353,12 +353,16 @@ class Environment: ...@@ -353,12 +353,16 @@ class Environment:
else: else:
lines = self.layout_config.split("\n") lines = self.layout_config.split("\n")
grid = []
for line in lines: for line in lines:
line = line.replace("\n", "").replace(" ", "") # remove newline char line = line.replace("\n", "").replace(" ", "") # remove newline char
current_x: float = starting_at current_x: float = starting_at
grid_line = []
for character in line: for character in line:
character = character.capitalize() character = character.capitalize()
pos = np.array([current_x, current_y]) pos = np.array([current_x, current_y])
assert self.counter_factory.can_map( assert self.counter_factory.can_map(
character character
), f"{character=} in layout file can not be mapped" ), f"{character=} in layout file can not be mapped"
...@@ -366,7 +370,9 @@ class Environment: ...@@ -366,7 +370,9 @@ class Environment:
counters.append( counters.append(
self.counter_factory.get_counter_object(character, pos) self.counter_factory.get_counter_object(character, pos)
) )
grid_line.append(1)
else: else:
grid_line.append(0)
match self.counter_factory.map_not_counter(character): match self.counter_factory.map_not_counter(character):
case "Agent": case "Agent":
designated_player_positions.append(pos) designated_player_positions.append(pos)
...@@ -374,14 +380,79 @@ class Environment: ...@@ -374,14 +380,79 @@ class Environment:
free_positions.append(np.array([current_x, current_y])) free_positions.append(np.array([current_x, current_y]))
current_x += 1 current_x += 1
grid.append(grid_line)
current_y += 1 current_y += 1
self.kitchen_width: float = len(lines[0]) + starting_at self.kitchen_width: float = len(lines[0]) + starting_at
self.kitchen_height = len(lines) + starting_at self.kitchen_height = len(lines) + starting_at
self.determine_counter_orientations(
counters, grid, np.array([self.kitchen_width / 2, self.kitchen_height / 2])
)
self.counter_factory.post_counter_setup(counters) self.counter_factory.post_counter_setup(counters)
return counters, designated_player_positions, free_positions return counters, designated_player_positions, free_positions
def determine_counter_orientations(self, counters, grid, kitchen_center):
for l in grid:
print(l)
grid = np.array(grid).T
grid_width = grid.shape[0]
grid_height = grid.shape[1]
last_counter = None
fst_counter_in_row = None
for c in counters:
grid_idx = np.floor(c.pos).astype(int)
neighbour_offsets = np.array([[0, 1], [0, -1], [1, 0], [-1, 0]], dtype=int)
neighbours_free = []
for offset in neighbour_offsets:
neighbour_pos = grid_idx + offset
if (
neighbour_pos[0] > (grid_width - 1)
or neighbour_pos[0] < 0
or neighbour_pos[1] > (grid_height - 1)
or neighbour_pos[1] < 0
):
pass
else:
if grid[neighbour_pos[0]][neighbour_pos[1]] == 0:
neighbours_free.append(offset)
if len(neighbours_free) > 0:
vector_to_center = c.pos - kitchen_center
vector_to_center /= np.linalg.norm(vector_to_center)
n_idx = np.argmin(
np.linalg.norm(vector_to_center - n) for n in neighbours_free
)
nearest_vec = neighbours_free[n_idx]
# print(nearest_vec, type(nearest_vec))
c.set_orientation(nearest_vec)
elif grid_idx[0] == 0:
if grid_idx[1] == 0:
# counter top left
c.set_orientation(np.array([1, 0]))
else:
c.set_orientation(fst_counter_in_row.orientation)
fst_counter_in_row = c
else:
c.set_orientation(last_counter.orientation)
last_counter = c
# for c in counters:
# near_counters = [
# other
# for other in counters
# if np.isclose(np.linalg.norm(c.pos - other.pos), 1)
# ]
# # print(c.pos, len(near_counters))
def perform_action(self, action: Action): def perform_action(self, action: Action):
"""Performs an action of a player in the environment. Maps different types of action inputs to the """Performs an action of a player in the environment. Maps different types of action inputs to the
correct execution of the players. correct execution of the players.
......
...@@ -42,6 +42,7 @@ class CounterState(TypedDict): ...@@ -42,6 +42,7 @@ class CounterState(TypedDict):
category: Literal["Counter"] category: Literal["Counter"]
type: str type: str
pos: list[float] pos: list[float]
orientation: list[float]
occupied_by: None | list[ occupied_by: None | list[
ItemState | CookingEquipmentState ItemState | CookingEquipmentState
] | ItemState | CookingEquipmentState ] | ItemState | CookingEquipmentState
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment