Пример #1
0
def _screen_from_json(json_data: Any, sprites: Dict[str, Sprite]) -> Screen:
    background_data = json_data.get("background")
    background_color = None
    background_tilemap = None
    if background_data:
        background_color = background_data.get("background_color")
        tilemap_data = background_data.get("background_tilemap")
        if tilemap_data:
            background_tilemap = Tilemap(tilemap_id=tilemap_data["tilemap_id"],
                                         rect_uv=Rect.from_list(
                                             tilemap_data["tilemap_uv"]))

    screen_elements = []
    if json_data.get("elements"):
        screen_elements = [
            ScreenElement(position=Point.from_list(data["position"]),
                          sprite=sprites.get(data.get("sprite_ref")),
                          text=data.get("text"))
            for data in json_data["elements"]
        ]
    menu_position = None
    menu_scrollbar_rect = None
    if json_data.get("menu"):
        menu_data = json_data["menu"]
        menu_position = \
            Point.from_list(menu_data["position"]) if menu_data.get("position") else None
        menu_scrollbar_rect = \
            Rect.from_list(menu_data["scrollbar_rect"]) if menu_data.get("scrollbar_rect") else None

    return Screen(background_color=background_color,
                  background_tilemap=background_tilemap,
                  elements=tuple(screen_elements),
                  menu_position=menu_position,
                  menu_scrollbar_rect=menu_scrollbar_rect)
Пример #2
0
    def draw(self, position: Point, selected: bool = False) -> None:
        draw_text(
            position.offset(
                self._get_position(GuiPosition.LEVEL_ITEM_TITLE_POS)),
            f"LEVEL {self.level_num}", self.text_style)

        if not self.level_unlocked:
            self._get_sprite(GuiSprite.LOCKED_ICON).draw(
                position.offset(
                    self._get_position(GuiPosition.LEVEL_LOCKED_ICON_POS)))
        else:
            self._draw_level_thumbnail(
                position.offset(
                    self._get_position(GuiPosition.LEVEL_THUMBNAIL_POS)))

        if self.level_completed:
            self._get_sprite(GuiSprite.CHECKED_ICON).draw(
                position.offset(
                    self._get_position(GuiPosition.LEVEL_COMPLETED_ICON_POS)))

        if selected:
            self._draw_level_score(
                self._get_position(GuiPosition.LEVEL_SCORE_POS))

        self._draw_frame(position, selected)
Пример #3
0
    def __init__(self, level_num: int, raw_data: List[List[Tile]]) -> None:
        self.level_num = level_num
        self.tilemap_data = [Tile.VOID] * (LEVEL_WIDTH * LEVEL_HEIGHT)
        self.size = Size(len(raw_data[0]), len(raw_data))

        offset = Point((LEVEL_WIDTH - self.size.width) // 2,
                       (LEVEL_HEIGHT - self.size.height) // 2)

        for y in range(self.size.height):
            for x in range(self.size.width):
                pos = Point(x, y).offset(offset)
                self.tilemap_data[self._pos_to_offset(pos)] = raw_data[y][x]
Пример #4
0
    def with_defaults(cls, items: Tuple[MenuItem, ...],
                      layout: MenuLayout = MenuLayout()) -> "Menu":
        """Construct menu based on defaults.

        :param items: collection of menu items
        :param layout: layout information of the menu
        :return: newly created instance of Menu
        """
        item_size = reduce(max_size, [item.size for item in items])
        total_rows = -(-len(items) // layout.columns)
        calculated_rows = min(layout.rows, total_rows) if layout.rows else total_rows
        calculated_size = Size(
            layout.columns * item_size.width + (layout.columns - 1) * layout.item_space.width,
            calculated_rows * item_size.height + (calculated_rows - 1) * layout.item_space.height)
        centered_position = center_in_rect(calculated_size).position

        if layout.position:
            x = layout.position.x if layout.position.x >= 0 else centered_position.x
            y = layout.position.y if layout.position.y >= 0 else centered_position.y
            menu_position = Point(x, y)
        else:
            menu_position = centered_position

        return Menu(
            items=items,
            item_size=item_size,
            item_space=layout.item_space,
            columns=layout.columns,
            rows=calculated_rows,
            position=menu_position)
Пример #5
0
    def tile_positions(self) -> Generator[Tuple[Point, Point], None, None]:
        """Generator for iterating over all valid tile positions inside both level and Pyxel's
        mega-tilemap (from top-left to bottom-right)."""
        tilemap_rect = tilemap_rect_nth(self.level_num)
        tilemap_points = tilemap_rect.inside_points()

        for point in tilemap_points:
            yield point.offset(Point(-tilemap_rect.x, -tilemap_rect.y)), point
Пример #6
0
    def move(self, direction: Direction) -> None:
        """Move object position in given direction by one tile.

        Additionally reset offset.

        :param direction: direction of the movement
        """
        self.tile_position = self.tile_position.move(direction)
        self.offset = Point(0, 0)
Пример #7
0
    def draw(self, draw_as_secondary: bool = False) -> None:
        super().draw(draw_as_secondary)

        for i, item in enumerate(self.visible_items):
            item_space = self.menu.item_space
            calculated_item_size = self.menu.item_size.enlarge(item_space.width, item_space.height)
            position = self.menu.position.offset(Point(
                (i % self.menu.columns) * calculated_item_size.width,
                (i // self.menu.columns) * calculated_item_size.height))
            item.draw(position, (self.top_row * self.menu.columns) + i == self.selected_item)
Пример #8
0
    def update(self, dt_in_ms: float,
               stats: GameStats) -> Optional[GameAction]:
        running_action = super().update(dt_in_ms, stats)
        if running_action:
            move_direction = self.direction.opposite if self.backward else self.direction
            delta = self.elapsed_time / self.time_to_complete * TILE_SIZE
            self.game_object.position.offset = Point(
                int(delta * move_direction.dx), int(delta * move_direction.dy))

        return running_action
Пример #9
0
 def _draw_digits(self, gui_position: GuiPosition, text: str, gui_sprite: GuiSprite,
                  colon_size: Optional[int] = None) -> None:
     position = self._get_position(gui_position)
     sprite = self._get_sprite(gui_sprite)
     char_pos = position
     for char in text:
         if char.isdigit():
             sprite.draw(position=char_pos, frame=int(char))
         char_size = colon_size if (char == ":" and colon_size) else sprite.width
         char_pos = char_pos.offset(Point(char_size + 1, 0))
Пример #10
0
def create_level_templates(json_data: Any, sprite_packs: Dict[str, SpritePack]) \
        -> Tuple[LevelTemplate, ...]:
    """Create level templates from metadata.

    :param json_data: input JSON containing level template metadata
    :param sprite_packs: collection of available sprite packs
    :return: collection of level templates
    """
    return tuple([
        LevelTemplate.from_level_num(
            level_num=level_num,
            tileset_index=data["tileset"],
            draw_offset=Point.from_list(data["draw_offset"]),
            sprite_packs=LevelSpritePacks(
                robot_sprite_pack=sprite_packs[data["robot_sprite_pack_ref"]],
                crate_sprite_pack=sprite_packs[data["crate_sprite_pack_ref"]]))
        for level_num, data in enumerate(json_data)
    ])
Пример #11
0
def create_gui_consts(json_data: Any, sprites: Dict[str, Sprite]) -> GuiConsts:
    """Create Gui constants from metadata.

    :param json_data: input JSON containing Gui constants
    :param sprites: collection of available sprites
    :return: Gui constants
    """
    return GuiConsts(gui_positions=tuple([
        Point.from_list(json_data["positions"][pos.resource_name])
        for pos in list(GuiPosition)
    ]),
                     gui_colors=tuple([
                         int(json_data["colors"][color.resource_name])
                         for color in list(GuiColor)
                     ]),
                     gui_sprites=tuple([
                         sprites[json_data["sprites"][sprite.resource_name]]
                         for sprite in list(GuiSprite)
                     ]))
Пример #12
0
class ObjectPosition:
    """Position of game object in the level.

    Attributes:
        tile_position - game object position in tilemap space
        offset - position offset relative to tile_position (expressed in pixels)
    """
    tile_position: TilePosition
    offset: Point = Point(0, 0)

    def move(self, direction: Direction) -> None:
        """Move object position in given direction by one tile.

        Additionally reset offset.

        :param direction: direction of the movement
        """
        self.tile_position = self.tile_position.move(direction)
        self.offset = Point(0, 0)

    def to_point(self) -> Point:
        """Convert tile position to a point in screen space (taking into account the offset)."""
        return self.tile_position.to_point().offset(self.offset)
Пример #13
0
    def draw(self,
             position: Point,
             layer: Optional[Layer] = None,
             direction: Direction = Direction.UP,
             frame: int = 0) -> None:
        """Draw the sprite at given position using specified layer, direction and frame.

        If sprite is single-layered then it will be drawn _only on main_ layer.

        :param position: position of sprite to be drawn at
        :param layer: layer of sprite to be drawn at
        :param direction: direction-specific variant of sprite to be drawn
        :param frame: frame of sprite to be drawn
        """
        if layer and layer.layer_index >= self.num_layers:
            return

        clamped_frame = min(frame, self.num_frames - 1)
        frame_offset_v = clamped_frame * self.uv_rect.h // self.num_frames
        top_layer_offset = self.num_layers - 1

        u = self.uv_rect.x + top_layer_offset
        v = self.uv_rect.y + frame_offset_v + top_layer_offset

        if self.directional:
            u += self.uv_rect.w // Direction.num_directions(
            ) * direction.direction_index

        if layer:
            u -= layer.layer_index
            v -= layer.layer_index

        offset = layer.offset if layer else Point(0, 0)

        pyxel.blt(position.x + offset.x, position.y + offset.y,
                  self.image_bank, u, v, self.width, self.height,
                  self.transparency_color)
Пример #14
0
 def tilemap_offset(self) -> Point:
     """Offset of the tilemap, for properly centering on the screen."""
     return Point(
         (self.size.width % 2) * TILE_SIZE // 2,
         (self.size.height % 2) * TILE_SIZE // 2)
Пример #15
0
 def _offset_to_pos(offset: int) -> Point:
     return Point(offset % LEVEL_WIDTH, offset // LEVEL_WIDTH)
Пример #16
0
 def draw(self, draw_as_secondary: bool = False) -> None:
     super().draw(draw_as_secondary)
     draw_text(Point(79, 240), "(c) 2020 KRZYSZTOF FURTAK", TextStyle(color=7, shadow_color=1))
     draw_text(Point(11, 240), f"v{__version__}", TextStyle(color=1))
Пример #17
0
 def to_point(self) -> Point:
     """Convert tile position to a point in screen space."""
     return Point(self.tile_x * TILE_SIZE, self.tile_y * TILE_SIZE)
Пример #18
0
"""Module for handling critical game errors."""
import textwrap

import pyxel

from bansoko import GAME_FRAME_TIME_IN_MS
from bansoko.graphics import Point, center_in_rect, Rect
from bansoko.graphics.text import draw_text, text_size
from bansoko.gui.menu import MenuController, Menu, TextMenuItem, MenuLayout
from bansoko.gui.navigator import ScreenNavigator

PADDING = Point(8, 8)


class ErrorScreen(MenuController):
    """Screen controller for displaying game critical errors.

    It's displayed when a non-recoverable error occurs during game startup.
    """
    def __init__(self, message: str):
        self.error_message = self._build_error_message(message)
        self.frame_rect = self._get_frame_rect(self.error_message)
        menu = Menu.with_defaults(
            tuple([TextMenuItem("OK", lambda: None)]),
            MenuLayout(position=self._get_menu_position(self.frame_rect)))
        super().__init__(menu=menu)

    def draw(self, draw_as_secondary: bool = False) -> None:
        self._draw_background()
        self._draw_frame()
        super().draw(draw_as_secondary=draw_as_secondary)
Пример #19
0
"""Module defining game screen which is displayed when level is completed."""
from typing import Tuple

from bansoko.game.profile import LevelScore
from bansoko.game.screens.screen_factory import ScreenFactory
from bansoko.graphics import Point
from bansoko.graphics.text import draw_text
from bansoko.gui.menu import MenuController, TextMenuItem, MenuItem, Menu, MenuLayout

LEVEL_TIME_POS = Point(104, 75)
LEVEL_PUSHES_POS = Point(104, 84)
LEVEL_STEPS_POS = Point(104, 93)


class LevelCompletedController(MenuController):
    """Screen controller allowing player to choose what to do after completion of the level.

    Player can navigate to the next level, replay the current level
    (to get better score) or get back to Main Menu.
    """
    def __init__(self, screen_factory: ScreenFactory, level_score: LevelScore):
        current_level_num = level_score.level_num
        last_level_completed = current_level_num == screen_factory.get_bundle(
        ).last_level

        next_level = TextMenuItem(
            "PLAY NEXT LEVEL", lambda: screen_factory.get_playfield_screen(
                screen_factory.get_player_profile().next_level_to_play(
                    current_level_num)))
        finish_game = TextMenuItem("FINISH GAME",
                                   screen_factory.get_victory_screen)
Пример #20
0
 def _get_menu_position(frame_rect: Rect) -> Point:
     return Point(-1, frame_rect.bottom - PADDING.y - pyxel.FONT_HEIGHT)