Skip to content
Snippets Groups Projects
drawing.py 38.2 KiB
Newer Older
  • Learn to ignore specific revisions
  • import os
    
    from datetime import datetime, timedelta
    
    from pathlib import Path
    
    import numpy as np
    import numpy.typing as npt
    import pygame
    
    from scipy.spatial import KDTree
    
    
    from cooperative_cuisine import ROOT_DIR
    from cooperative_cuisine.environment import Environment
    
    from cooperative_cuisine.pygame_2d_vis.game_colors import colors
    
    from cooperative_cuisine.state_representation import (
    
        PlayerState,
        CookingEquipmentState,
        ItemState,
    
    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):
        arr = pygame.surfarray.pixels3d(img)
        mean_arr = np.dot(arr[:, :, :], [0.216, 0.587, 0.144])
        mean_arr3d = mean_arr[..., np.newaxis]
        new_arr = np.repeat(mean_arr3d[:, :, :], 3, axis=2)
        new_arr = new_arr.astype(np.int8)
        surface = pygame.Surface(new_arr.shape[0:2], pygame.SRCALPHA, 32)
    
        # Copy the rgb part of array to the new surface.
        pygame.pixelcopy.array_to_surface(surface, new_arr)
        surface_alpha = np.array(surface.get_view("A"), copy=False)
        surface_alpha[:, :] = pygame.surfarray.pixels_alpha(img)
        return surface
    
    
    
    def create_polygon(n, start_vec):
    
        if n == 1:
            return np.array([0, 0])
    
    
        angle = (2 * np.pi) / n
    
        rot_matrix = np.array(
            [[np.cos(angle), -np.sin(angle)], [np.sin(angle), np.cos(angle)]]
        )
    
        vecs = [vector]
        for i in range(n - 1):
            vector = np.dot(rot_matrix, vector)
            vecs.append(vector)
    
        return vecs
    
    
    class Visualizer:
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
        """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):
            self.image_cache_dict = {}
            self.player_colors = []
            self.config = config
    
    
            self.fire_state = 0
            self.fire_time_steps = 8
    
    fheinrich's avatar
    fheinrich committed
            self.observation_screen = None
    
            self.USE_PLAYER_COOK_SPRITES = (
                config["use_player_cook_sprites"]
                if "use_player_cook_sprites" in config
                else True
            )
            self.SHOW_INTERACTION_RANGE = (
                config["show_interaction_range"]
                if "show_interaction_range" in config
                else False
            )
            self.SHOW_COUNTER_CENTERS = (
                config["show_counter_centers"]
                if "show_counter_centers" in config
                else False
            )
    
    
        def create_player_colors(self, n) -> None:
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            """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)
    
            colors_vec = np.array([col for col in colors.values()])
    
            tree = KDTree(colors_vec)
    
            color_names = list(colors.keys())
    
            self.player_colors = []
            for hue in hue_values:
                rgb = colorsys.hsv_to_rgb(hue, 1, 1)
                query_color = np.array([int(c * 255) for c in rgb])
                _, index = tree.query(query_color, k=1)
                self.player_colors.append(color_names[index])
    
        def draw_gamescreen(
            self,
    
            screen: pygame.Surface,
            state: dict,
            grid_size: int,
            controlled_player_idxs: list[int],
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            """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))
            height = int(np.ceil(state["kitchen"]["height"] * grid_size))
    
            self.draw_background(
                surface=screen,
                width=width,
                height=height,
                grid_size=grid_size,
            )
            self.draw_counters(
                screen,
    
                state["counters"],
    
            for idx, col in zip(controlled_player_idxs, [colors["blue"], colors["red"]]):
                pygame.draw.circle(
                    screen,
                    col,
    
                    np.array(state["players"][int(idx)]["pos"]) * grid_size
                    + (grid_size // 2),
    
            self.draw_players(
                screen,
    
                state["players"],
    
            if "view_restrictions" in state and state["view_restrictions"]:
                self.draw_lightcones(screen, grid_size, width, height, state)
    
        def draw_lightcones(self, screen, grid_size, width, height, state):
            view_restrictions = state["view_restrictions"]
    
            mask = pygame.Surface(screen.get_size(), pygame.SRCALPHA)
            mask.fill((0, 0, 0, 0))
            mask_color = (0, 0, 0, 0)
    
            for idx, restriction in enumerate(view_restrictions):
                direction = pygame.math.Vector2(restriction["direction"])
                pos = pygame.math.Vector2(restriction["position"])
                angle = restriction["angle"] / 2
                view_range = restriction["range"]
    
    
                pos = pos * grid_size + pygame.math.Vector2([grid_size / 2, grid_size / 2])
    
    
                rect_scale = max(width, height) * 2
    
    
                left_beam = pos + (direction.rotate(angle) * rect_scale * 2)
                right_beam = pos + (direction.rotate(-angle) * rect_scale * 2)
    
    
                cone_mask = pygame.surface.Surface(screen.get_size(), pygame.SRCALPHA)
                cone_mask.fill((255, 255, 255, 255))
    
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
                offset_front = direction * grid_size * 0.7
    
                    shadow_cone_points = [
                        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,
                    ]
                    light_cone_points = [pos - offset_front, left_beam, right_beam]
    
                        cone_mask,
                        mask_color,
                        shadow_cone_points,
    
                if view_range:
    
                    start_vec = np.array(-direction * view_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)
                        + (direction.rotate(90) * rect_scale),
    
                        + (direction * rect_scale)
    
                        + (direction.rotate(90) * rect_scale),
                        pos
    
                        + (direction * rect_scale)
    
                        + (direction.rotate(-90) * rect_scale),
    
                        pos
                        - (direction * rect_scale)
                        + (direction.rotate(-90) * rect_scale),
                    ]
    
    
                    pygame.draw.polygon(cone_mask, mask_color, corners)
    
                mask.blit(cone_mask, (0, 0), special_flags=pygame.BLEND_MAX)
    
            screen.blit(
                mask,
                mask.get_rect(),
                special_flags=pygame.BLEND_RGBA_MULT,
            )
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
        def draw_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
            surface.fill(colors[self.config["Kitchen"]["ground_tiles_color"]])
            for x in range(0, width, block_size):
                for y in range(0, height, block_size):
                    rect = pygame.Rect(x, y, block_size, block_size)
                    pygame.draw.rect(
                        surface,
                        self.config["Kitchen"]["background_lines"],
                        rect,
                        1,
                    )
    
        def draw_image(
            self,
            screen: pygame.Surface,
            img_path: Path | str,
            size: float,
            pos: npt.NDArray,
            rot_angle=0,
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            """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 pygame_2d_vis directory.
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
                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}"
    
            if cache_entry + ("-burnt" if burnt else "") in self.image_cache_dict:
                image = self.image_cache_dict[cache_entry + ("-burnt" if burnt else "")]
    
                if burnt:
                    if cache_entry in self.image_cache_dict:
                        normal_image = self.image_cache_dict[cache_entry]
                    else:
                        normal_image = pygame.image.load(
    
                            ROOT_DIR / "pygame_2d_vis" / img_path
    
                        ).convert_alpha()
                        self.image_cache_dict[cache_entry] = normal_image
                    image = grayscale(normal_image)
                    self.image_cache_dict[cache_entry + "-burnt"] = image
                else:
                    image = pygame.image.load(
    
                        ROOT_DIR / "pygame_2d_vis" / img_path
    
                    ).convert_alpha()
                    self.image_cache_dict[cache_entry] = image
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            # TODO: smoothscale or not???
    
            image = pygame.transform.scale(image, (size, size))
            if rot_angle != 0:
                image = pygame.transform.rotate(image, rot_angle)
            rect = image.get_rect()
            rect.center = pos
    
            screen.blit(image, rect)
    
        def draw_players(
            self,
            screen: pygame.Surface,
    
            players: dict,
    
            grid_size: float,
        ):
            """Visualizes the players as circles with a triangle for the facing direction.
            If the player holds something in their hands, it is displayed
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
    
            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):
    
                pos = np.array(player_dict["pos"]) * grid_size
    
                pos += grid_size / 2  # correct for grid offset
    
                facing = np.array(player_dict["facing_direction"], dtype=float)
    
                if self.USE_PLAYER_COOK_SPRITES:
    
                    pygame.draw.circle(
                        screen,
    
                        pos - facing * grid_size * 0.25,
                        grid_size * 0.2,
                    )
    
    
                    img_path = self.config["Cook"]["parts"][0]["path"]
    
                    angle = calc_angle(facing.tolist(), [0, 1])
    
                    size = self.config["Cook"]["parts"][0]["size"] * grid_size
                    self.draw_image(screen, img_path, size, pos, angle)
    
                else:
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
                    player_radius = 0.4
                    size = player_radius * grid_size
    
                    color1 = self.player_colors[p_idx]
                    color2 = colors["white"]
    
                    pygame.draw.circle(screen, color2, pos, size)
                    pygame.draw.circle(screen, colors["blue"], pos, size, width=1)
                    pygame.draw.circle(screen, colors[color1], pos, size // 2)
    
                    pygame.draw.polygon(
                        screen,
                        colors["blue"],
                        (
                            (
                                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),
                        ),
                    )
    
    
                if self.SHOW_INTERACTION_RANGE:
    
                    pygame.draw.circle(
                        screen,
                        colors["blue"],
    
                        pos + (facing * grid_size * 0.4),
    
                        1.6 * grid_size,
                        width=1,
                    )
                    pygame.draw.circle(
    
                        screen, colors["red1"], pos + (facing * grid_size * 0.4), 4
    
                    )
    
                if player_dict["holding"] is not None:
    
                    holding_item_pos = pos + (grid_size * 0.5 * facing)
    
                    self.draw_item(
                        pos=holding_item_pos,
                        grid_size=grid_size,
                        item=player_dict["holding"],
                        screen=screen,
    
                if player_dict["current_nearest_counter_pos"]:
    
                    nearest_pos = (
                        np.array(player_dict["current_nearest_counter_pos"]) * grid_size
                    )
    
    
                    pygame.draw.rect(
                        screen,
                        colors[self.player_colors[p_idx]],
                        rect=pygame.Rect(
    
    
        def draw_thing(
            self,
            screen: pygame.Surface,
            pos: npt.NDArray[float],
            grid_size: float,
            parts: list[dict[str]],
            scale: float = 1.0,
    
            orientation: list[float] | None = None,
    
        ):
            """Draws an item, based on its visual parts specified in the visualization config.
    
            Args:
    
                screen: the game screen to draw on.
                grid_size: size of a grid cell.
    
                pos: Where to draw the item parts.
                parts: The visual parts to draw.
                scale: Rescale the item by this factor.
    
                orientation: Rotate the item to face this direction.
    
            """
            for part in parts:
                part_type = part["type"]
    
                angle, angle_offset = 0, 0
    
    
                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
    
                # if "rotate_offset" in part.keys():
                #     angle_offset = 0
    
                match part_type:
                    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)
    
                        size = (
                            absolute_size
                            if absolute_size is not None
                            else part["size"] * scale * grid_size
                        )
    
                        self.draw_image(
                            screen,
                            part["path"],
    
                        if "center_offset" in part:
                            d = pygame.math.Vector2(part["center_offset"]) * grid_size
                            d.rotate_ip(angle_offset)
    
                            d[0] = -d[0]
    
    
                            draw_pos += np.array(d)
    
                        height = part["height"] * grid_size
                        width = part["width"] * grid_size
                        color = part["color"]
    
                            draw_pos[0] - (height / 2),
                            draw_pos[1] - (width / 2),
    
                        pygame.draw.rect(screen, color, 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)
    
                        radius = part["radius"] * grid_size
                        color = colors[part["color"]]
    
                        pygame.draw.circle(screen, color, draw_pos, radius)
    
            pos: npt.NDArray[float] | list[float],
            grid_size: float,
    
            item: ItemState | CookingEquipmentState | EffectState,
    
            scale: float = 1.0,
            plate=False,
            screen=None,
        ):
            """Visualization of an item at the specified position. On a counter or in the hands of the player.
            The visual composition of the item is read in from visualization.yaml file, where it is specified as
            different parts to be drawn.
    
            Args:
    
                grid_size: size of a grid cell.
    
                pos: The position of the item to draw.
                item: The item do be drawn in the game.
                scale: Rescale the item by this factor.
                screen: the pygame screen to draw on.
                plate: item is on a plate (soup are is different on a plate and pot)
            """
    
    
            if not isinstance(item, list):  # can we remove this check?w
                if item["type"] in self.config or (
                    item["type"].startswith("Burnt")
                    and item["type"].replace("Burnt", "") in self.config
                ):
    
                    item_key = item["type"]
                    if "Soup" in item_key and plate:
    
                        item_key += "Plate"
    
                    if item_key.startswith("Burnt"):
                        item_key = item_key.replace("Burnt", "")
    
    
                    if item_key == "Fire":
                        item_key = (
                            f"{item_key}{int(self.fire_state/self.fire_time_steps)+1}"
                        )
    
    
                        pos=pos,
                        parts=self.config[item_key]["parts"],
    
                        scale=scale,
                        screen=screen,
    
                        burnt=item["type"].startswith("Burnt"),
    
            if "progress_percentage" in item and item["progress_percentage"] > 0.0:
    
                if item["inverse_progress"]:
    
                    percentage = 1 - item["progress_percentage"]
                else:
                    percentage = item["progress_percentage"]
    
                    screen,
                    pos,
                    percentage,
                    grid_size=grid_size,
    
                    attention=item["inverse_progress"],
    
            if (
                "content_ready" in item
                and item["content_ready"]
    
                and (
                    item["content_ready"]["type"] in self.config
                    or (
                        item["content_ready"]["type"].startswith("Burnt")
                        and item["content_ready"]["type"].replace("Burnt", "")
                        in self.config
                    )
                )
    
                    parts=self.config[item["content_ready"]["type"].replace("Burnt", "")][
                        "parts"
                    ],
    
                    burnt=item["type"].startswith("Burnt"),
    
                )
            elif "content_list" in item and item["content_list"]:
    
                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(
                        pos=np.array(pos) + triangle_offsets[idx],
                        item=o,
                        scale=scale,
                        plate="Plate" in item["type"],
    
            if "active_effects" in item and item["active_effects"]:
                for effect in item["active_effects"]:
                    self.draw_item(pos=pos, item=effect, screen=screen, grid_size=grid_size)
    
        def draw_progress_bar(
            screen: pygame.Surface,
            pos: npt.NDArray[float],
            percent: float,
            grid_size: float,
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            """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_height = grid_size * 0.2
            progress_width = percent * grid_size
            progress_bar = pygame.Rect(
    
                bar_pos[0],
                bar_pos[1] + grid_size - bar_height,
    
                progress_width,
                bar_height,
            )
    
            pygame.draw.rect(screen, colors["red" if attention else "green1"], progress_bar)
    
    
        def draw_counter(
            self, screen: pygame.Surface, counter_dict: dict, grid_size: float
        ):
            """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
            different parts to be drawn.
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            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
    
            counter_type = counter_dict["type"]
    
    
            pos += grid_size // 2  # correct for grid offset
    
    
            self.draw_thing(
                screen,
                pos,
                grid_size,
                self.config["Counter"]["parts"],
    
                orientation=counter_dict["orientation"]
                if "orientation" in counter_dict
                else None,
    
            if counter_type in self.config:
    
                self.draw_thing(
                    screen,
                    pos,
                    grid_size,
                    self.config[counter_type]["parts"],
                    orientation=counter_dict["orientation"],
                )
    
                if counter_type in self.config:
                    parts = self.config[counter_type]["parts"]
                elif counter_type.endswith("Dispenser"):
                    parts = self.config["Dispenser"]["parts"]
                else:
                    raise ValueError(f"Can not draw counter type {counter_type}")
    
                    orientation=counter_dict["orientation"],
    
        def draw_counter_occupier(
            self,
            screen: pygame.Surface,
            occupied_by: dict | list,
            grid_size,
            pos: npt.NDArray[float],
    
            item_scale: float,
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            """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:
            if isinstance(occupied_by, list):
                for i, o in enumerate(occupied_by):
    
                        pos=np.abs([pos[0], pos[1] - (i * 3)]),
                        grid_size=grid_size,
                        item=o,
    
                        scale=item_scale,
    
            # All other items:
            else:
                self.draw_item(
                    pos=pos,
                    grid_size=grid_size,
                    item=occupied_by,
                    screen=screen,
    
                    scale=item_scale,
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
        def draw_counters(self, screen: pygame, counters: dict, grid_size: int):
    
            """Visualizes the counters in the environment.
    
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            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
    
    
            for counter in counters:
    
                self.draw_counter(screen, counter, grid_size)
    
            for counter in counters:
    
                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(
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
                        screen=screen,
                        occupied_by=counter["occupied_by"],
                        grid_size=grid_size,
    
                        pos=item_pos * grid_size + (grid_size / 2),
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
                        item_scale=item_scale,
    
                if counter["active_effects"]:
                    for effect in counter["active_effects"]:
                        self.draw_item(
                            pos=np.array(counter["pos"]) * grid_size + (grid_size / 2),
                            grid_size=grid_size,
                            screen=screen,
                            item=effect,
                        )
    
                if self.SHOW_COUNTER_CENTERS:
    
                    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(
    
                        colors["red"],
                        (
                            (
                                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)
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            self,
            screen: pygame.surface,
            state: dict,
            grid_size: int,
            width: int,
            height: int,
            screen_margin: int,
            config: dict,
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            """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_height = screen_margin
            order_screen = pygame.Surface(
                (orders_width, orders_height),
            )
    
            bg_color = colors[config["GameWindow"]["background_color"]]
            pygame.draw.rect(order_screen, bg_color, order_screen.get_rect())
    
            order_rects_start = (orders_height // 2) - (grid_size // 2)
            for idx, order in enumerate(state["orders"]):
                order_upper_left = [
    
                    order_rects_start + idx * grid_size * 1.2,
    
                    order_rects_start,
                ]
                pygame.draw.rect(
                    order_screen,
                    colors["red"],
                    pygame.Rect(
                        order_upper_left[0],
                        order_upper_left[1],
    
                    pos=center + (grid_size / 2),
    
                    screen=order_screen,
    
                    pos=center + (grid_size / 2),
    
                    plate=True,
                    screen=order_screen,
    
                )
                order_done_seconds = (
    
                    (
                        datetime.fromisoformat(order["start_time"])
                        + timedelta(seconds=order["max_duration"])
                    )
                    - datetime.fromisoformat(state["env_time"])
    
                percentage = order_done_seconds / order["max_duration"]
                self.draw_progress_bar(
    
                    pos=center + (grid_size / 2),
    
                    percent=percentage,
                    screen=order_screen,
                    grid_size=grid_size,
    
    
            orders_rect = order_screen.get_rect()
            orders_rect.center = [
                screen_margin + (orders_width // 2),
                orders_height // 2,
            ]
            screen.blit(order_screen, orders_rect)
    
    
        def save_state_image(
            self, grid_size: int, state: dict, filename: str | Path
        ) -> None:
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
            """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))
            height = int(np.ceil(state["kitchen"]["height"] * grid_size))
    
            flags = pygame.HIDDEN
            screen = pygame.display.set_mode((width, height), flags=flags)
    
    
            self.draw_gamescreen(screen, state, grid_size, [0 for _ in state["players"]])
    
        def get_state_image(self, grid_size: int, state: dict) -> npt.NDArray[np.uint8]:
    
            width = int(np.ceil(state["kitchen"]["width"] * grid_size))
            height = int(np.ceil(state["kitchen"]["height"] * grid_size))
    
            flags = pygame.HIDDEN
    
    
    fheinrich's avatar
    fheinrich committed
            if not self.observation_screen:
    
                self.observation_screen = pygame.display.set_mode(
                    (width, height), flags=flags
                )
    
            self.draw_gamescreen(
                self.observation_screen, state, grid_size, [0 for _ in state["players"]]
            )
    
    fheinrich's avatar
    fheinrich committed
            red = pygame.surfarray.array_red(self.observation_screen)
            green = pygame.surfarray.array_green(self.observation_screen)
            blue = pygame.surfarray.array_blue(self.observation_screen)
    
            res = np.stack([red, green, blue], axis=2)
            return res
    
    
        def draw_recipe_image(
            self, screen: pygame.Surface, graph_dict, width, height, grid_size
        ) -> None:
    
            screen.fill(self.config["GameWindow"]["background_color"])
            positions_dict = graph_dict["layout"]
    
            positions = np.array(list(positions_dict.values()))
            positions = positions - positions.min(axis=0)
            positions[positions == 0] = 0.000001
            positions = (
                positions / positions.max(axis=0) * (np.array([width, height]) - grid_size)
            )
            positions += grid_size / 2
    
            positions_dict = {
                name: pos for name, pos in zip(positions_dict.keys(), positions)
            }
    
    
            for start, end in graph_dict["edges"]:
                pygame.draw.line(
                    screen,
                    "black",
                    positions_dict[start],
                    positions_dict[end],
                    width=5,
                )
            for name, pos in positions_dict.items():
                key = name.split("_")[0]
    
                if key in [
                    "Chips",
                    "FriedFish",
                    "Burger",
                    "Salad",
                    "TomatoSoup",
                    "OnionSoup",
                    "FishAndChips",
                    "Pizza",
                ]:
                    self.draw_thing(
                        screen,
                        np.array(pos),
                        grid_size,
                        self.config["Plate"]["parts"],
                        absolute_size=grid_size,
                    )
                if "Soup" in key:
                    self.draw_thing(
                        screen,
                        np.array(pos),
                        grid_size,
                        self.config[key + "Plate"]["parts"],
                    )
                else:
                    viz = self.config[key]["parts"]
                    self.draw_thing(
                        screen, np.array(pos), grid_size, viz, absolute_size=grid_size
                    )
    
    
    def save_screenshot(state: dict, config: dict, filename: str | Path) -> None:
    
    Fabian Heinrich's avatar
    Fabian Heinrich committed
        """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.create_player_colors(len(state["players"]))
        pygame.init()
        pygame.font.init()
        viz.save_state_image(grid_size=40, state=state, filename=filename)
    
    
    
    def generate_recipe_images(config: dict, folder_path: str | Path):
        os.makedirs(ROOT_DIR / folder_path, exist_ok=True)
        env = Environment(
    
            env_config=str(ROOT_DIR / "configs" / "environment_config.yaml"),
            layout_config=str(ROOT_DIR / "configs" / "layouts" / "basic.layout"),
            item_info=str(ROOT_DIR / "configs" / "item_info.yaml"),
    
            as_files=True,
            env_name="0",